作业:多线程实现文件拷贝,线程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错误码。
线程产生的竞态现象:
加入静态互斥锁解决竞态
加入动态互斥锁解决竞态
思维导图