2024.12.13(线程)

作业:多线程实现文件拷贝,线程1拷贝一半,线程2拷贝另一半,主线程回收子线程资源。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

typedef struct
{
    char *p1;    // 源文件路径
    char *p2;    // 目标文件路径
    int len;    // 拷贝的长度
    int start;  // 开始位置
} Node;

void *copy_file_half(void *arg)
{
    Node *p = (Node *)arg;
    FILE *fp1 = fopen(p->p1, "r");
    if (fp1 == NULL)
	{
        perror("fopen");
        return NULL;
    }
    FILE *fp2 = fopen(p->p2, "w");
    if (fp2 == NULL) 
	{
        perror("fopen");
        fclose(fp1);
        return NULL;
    }

    fseek(fp1, p->start, SEEK_SET); // 移动到指定的开始位置

    char buff[1024];
    int len1 = 0;
    int res;

    while ((p->len) > len1) 
	{
        res = fread(buff, 1, sizeof(buff), fp1);
        if (res <= 0) break;
        fwrite(buff, 1, res, fp2);
        len1 += res;
    }

    fclose(fp1);
    fclose(fp2);
    return NULL;
}

int main(int argc, const char *argv[]) 
{
    if (argc != 3)
	{
        fprintf(stderr, " %s \n", argv[0]);
        return -1;
    }

    Node p1, p2;
    char *src_file = argv[1];
    char *dst_file = argv[2];

    FILE *fp1 = fopen(src_file, "r");
    if (fp1 == NULL) 
	{
        perror("fopen");
        return -1;
    }
    fseek(fp1, 0, SEEK_END);
    long len = ftell(fp1);
    fclose(fp1);

    p1.p1 = src_file;
    p1.p2 = dst_file;
    p1.len = len / 2;
    p1.start = 0;

    p2.p1 = src_file;
    p2.p2 = dst_file;
    p2.len = len - p1.len;
    p2.start = len / 2;

    pthread_t tid1, tid2;

    if (pthread_create(&tid1, NULL, copy_file_half, &p1) != 0) 
	{
        perror("pthread_create");
        return -1;
    }

    if (pthread_create(&tid2, NULL, copy_file_half, &p2) != 0) 
	{
        perror("pthread_create");
        pthread_cancel(tid1);
        return -1;
    }

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    printf("完成\n");
    return 0;
}

3、线程数间据传递

1)、全局变量:子主线程共享全局变量资源。

2)、局部变量作为参数地址传递,子主线程共享局部变量资源。

3)、结构体

4、pthread_self获取线程号

#include <pthread.h>

pthread_t pthread_self(void);

功能:获取调用线程的线程号

参数:无

返回值:返回调用线程的线程号。

5、pthread_exit线程退出

#include <pthread.h>

void pthread_exit(void *retval);

功能:退出调用线程

参数:任意类型的变量,存储检索后的内容。

返回值:无

6、pthread_join/pthread_detach线程回收和挂起

        #include <pthread.h>

                int pthread_join(pthread_t thread, void **retval);

        功能:阻塞回收子线程的资源。

        参数1:要退出的线程号

        参数2:线程退出时的状态,一般不接收填NULL。

        返回值:成功返回0,失败返回一个错误码。

        #include <pthread.h>

                int pthread_detach(pthread_t thread);

        功能:非阻塞将线程设置为分离态,设置为分离态的线程由系统回收资源。

        参数:要设置为分离态的线程号

        返回值:成功返回0,失败返回一个错误码。

1)、验证设置为分离态的子线程是否共享主线程资源结论:依然共享资源。

2)、验证主线程退出后,子线程是否立即退出结论:不会立即退出。

7、pthread_cancel,pthread_setcancelstate,pthread_setcanceltype。

        #include <pthread.h>

                int pthread_cancel(pthread_t thread);

        功能:向线程发送取消信号。

        参数:线程号

        返回值:成功返回0,失败返回非0错误码。

        #include <pthread.h>

                int pthread_setcancelstate(int state, int *oldstate);

        功能:设置调用线程是否可被取消

        参数1:   PTHREAD_CANCEL_ENABLE:默认线程可被取消                         PTHREAD_CANCEL_DISABLE:线程不可被取消

        参数2:线程原始的状态。

        返回值:成功返回0,失败返回非0错误码。

                int pthread_setcanceltype(int type, int *oldtype);

        功能:设置线程延迟取消。

        参数1: PTHREAD_CANCEL_DEFERRED:默认延迟取消                       PTHREAD_CANCEL_ASYNCHRONOUS:不延迟,立刻取消。

        参数2:线程原始的类型。

        返回值:成功返回0,失败返回非0错误码。

设置子线程拒绝取消状态:

设置子线程延迟取消:

1、进程线程间通信

1)、进程线程之间的区别:

        进程和线程的根本区别是进程是操作系统(OS)资源分配的基本单位,而线程是处理器(CPU)任务调度和执行的基本单位

        地址空间:线程共享本进程的地址空间,而进程之间是独立的地址空间。

        资源:线程共享本进程的资源如内存、I/O、cpu等,不利于资源的管理和保护,而进程之间的资源是独立的,能很好的进行资源管理和保护。

        健壮性:多进程要比多线程健壮,一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。

        执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口,执行开销大。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,执行开销小。

        可并发性:两者均可并发执行。

        任务切换时:进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。

2)、同步和异步通信:

        同步通信:面向比特流的通信发送方和接收方在同一时刻进行数据传输。为了实现这一点,通常需要一个时钟信号来协调数据的发送和接收。常见的同步通信协议包括I2C和SPI。时序一致性,响应确认,高传输速率。

        异步通信:面向字节(8位)的通信,发送方和接收方之间没有严格的时序要求,它们可以独立进行操作,而无需等待对方的响应。数据一旦在发送方准备好,就可以立即发送,接收方在收到数据后进行处理。常见的异步通信协议包括UART。无时钟信号,适合长距离通信和不规则数据传输,潜在的延迟和速率。

3)、阻塞IO和非阻塞IO:

        阻塞IO:内核IO操作彻底完成后,才返回到用户空间执行用户的操作。阻塞是指用户空间的执行状态。

        非阻塞IO:用户空间的程序不需要等待内核IO操作彻底完成,可以立即返回用户空间执行用户操作,即处于非阻塞IO状态,内核空间会立即返回给用户一个状态值。

        阻塞IO:调用线程一直在等待,不能干别的事情。

        非阻塞IO:调用线程拿到内核返回的状态值后,IO操作能执行就执行,不能执行就执行别的线程。

4)、并发和并行:

        并发:系统在同一时间段内处理多个任务的能力。并发关注的是任务之间的交替执行,任务之间可能并不真正同时运行,而是通过任务的分时调度机制,使得多个任务在时间上交错进行,从而给用户一种“同时”执行的感觉

        并行:系统同时执行多个任务的能力。并行是指在多个处理器或者多核处理器上,真正同时地运行多个任务。并行化的目标是提高程序的执行效率,特别是在需要处理大量数据或计算密集型任务时,并行化可以显著减少任务的完成时间。

5)、什么是死锁,如何避免死锁?

        死锁是指在多线程多进程环境中,两个或多个进程(或线程)互相持有对方所需资源,导致它们都无法继续执行的一种状态。

        避免嵌套锁:尽量避免在持有一个锁的时候去申请另一个锁,因为这样可能会导致死锁的发生。统一获取锁的顺序:如果多个线程需要获取多个锁,可以约定获取锁的顺序,这样可以避免出现死锁。设置超时时间:在获取锁的时候可以设置超时时间,如果超过一定时间还没有获取到锁,就放弃获取锁并释放已经获取的锁。死锁检测:通过检测程序的锁状态来判断是否存在死锁,并进行相应的处理。减少锁的持有时间:尽量减少持有锁的时间,使得锁的争用时间变短,从而减少死锁的发生概率。

6)、进程间的通信方式有哪些

        无名管道:亲缘进程通信

        有名管道:任意进程通信

        信号:用户可以给某个进程发送信号,一个进程也能给另一个进程发送信号,内核也可以给某个进程发送信号,信号三种默认操作:默认,捕获,忽略。

        消息队列:多个进程可同时向一个消息队列发送消息,也可以同时从一个消息队列中接收消息。发送进程把消息发送到队列尾部,接受进程从消息队列头部读取消息,消息一旦被读出就从队列中删除

        共享内存:共享内存是将物理空间的一片区域映射到内核空间,在将映射的内核空间与用户空间的区域进行连接。共享内存的操作不是一次性的,当共享内存段中的数据被读取后,依然存在。共享内存是所有进程间通信方式中效率最高的,原因是,操作共享内存段时,无需进行用户空间和内核空间的切换。

        信号灯集:信号号灯集中的每一个灯,都控制一个进程信号灯集中的等的编号都是从0开始,信号灯集中的每个灯都维护了一个value值,当value为0时,处于阻塞状态,等待其他进程将该资源的值加到1。

        套接字:内核提供的函数,用于创建一个套接字,并返回当前套接字的文件描述符,通过套接字文件描述符可以实现多个进程之间的通信,可以通过文件描述符进程进程之间的绑定。

2、线程间的同步互斥

1、线程可以通过全局变量和局部变量实现数据通信。

2、进程运行空间都是独立的,不能进行数据通信

3、多线程间数据共享有可能执行同一个动作,导致数据异常,所以要加上互斥锁。

4、多线程除了互斥之外还有同步,也就是一个线程完成任务后,交给第二个线程完成。

5、线程访问的全局变量的资源,全局变量属于临界资源。

6、访问临界资源的代码属于临界区。

7、多个线程访问临界资源时无法确定谁先访问谁后访问,属于竞态。

3、线程互斥之互斥锁

1、一个线程执行任务时其他线程处于等待状态,执行任务后第二个线程再进来执行,但是执行不分先后顺序。

2、互斥锁本质上也是一个临界资源,线程获取到锁资源后执行。

3、定义和初始化互斥锁,线程上锁,解锁,销毁锁资源。

4、临界资源实质上是一个全局变量,所有的线程都共享这个资源。

        #include <pthread.h>

                pthread_mutex_t fastmutex =

                                PTHREAD_MUTEX_INITIALIZER;静态初始化快速锁                 

                pthread_mutex_t recmutex

                                PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;静态初始化递归锁

                pthread_mutex_t errchkmutex =                                 PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;静态初始化差错锁

                int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t                                 *mutexattr); 概念:动态初始化互斥锁。

        参数1:互斥锁地址

        参数2:互斥锁属性,一般默认属性填NULL

        返回值:返回0,不会失败。

                int pthread_mutex_lock(pthread_mutex_t *mutex);

        功能:给临界区(访问临界资源的代码)上锁

        参数:互斥锁地址

        返回值:成功返回0,失败返回非0错误码。

                int pthread_mutex_unlock(pthread_mutex_t *mutex);

        功能:给临界区(访问临界资源的代码)解锁

        参数:互斥锁地址

        返回值:成功返回0,失败返回非0错误码。

                int pthread_mutex_destroy(pthread_mutex_t *mutex);

        功能:销毁锁资源

        参数:互斥锁地址

        返回值:成功返回0,失败返回非0错误码。

线程产生的竞态现象:

加入静态互斥锁解决竞态

加入动态互斥锁解决竞态

思维导图

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值