IO Day6

进程线程之间的通信
进程线程之间的区别
进程和线程的根本区别是进程是操作系统(OS)资源分配的基本单位,而线程是处理器(CPU)任务调度和执行的基本单位
地址空间:线程共享本进程的地址空间,而进程之间是独立的地址空间。
资源:线程共享本进程的资源如内存、I/O、cpu等,不利于资源的管理和保护,而进程之间的资源是独立的,能很好的进行资源管理和保护。
健壮性:多进程要比多线程健壮,一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。
执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口,执行开销大。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,执行开销小。
可并发性:两者均可并发执行。
任务切换时:进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。
同步和异步通信
同步通信:面向比特流的通信发送方和接收方在同一时刻进行数据传输。为了实现这一点,通常需要一个时钟信号来协调数据的发送和接收。常见的同步通信协议包括I2C和SPI。时序一致性,响应确认,高传输速率。
异步通信:面向字节(8位)的通信,发送方和接收方之间没有严格的时序要求,它们可以独立进行操作,而无需等待对方的响应。数据一旦在发送方准备好,就可以立即发送,接收方在收到数据后进行处理。常见的异步通信协议包括UART。无时钟信号,适合长距离通信和不规则数据传输,潜在的延迟和速率。
阻塞IO和非阻塞IO
阻塞IO:内核IO操作彻底完成后,才返回到用户空间执行用户的操作。阻塞是指用户空间的执行状态。调用线程一直在等待,不能干别的事情。
非阻塞IO:用户空间的程序不需要等待内核IO操作彻底完成,可以立即返回用户空间执行用户操作,即处于非阻塞IO状态,内核空间会立即返回给用户一个状态值。调用线程拿到内核返回的状态值后,IO操作能执行就执行,不能执行就执行别的线程。
并发和并行
并发:系统在同一时间段内处理多个任务的能力。并发关注的是任务之间的交替执行,任务之间可能并不真正同时运行,而是通过任务的分时调度机制,使得多个任务在时间上交错进行,从而给用户一种“同时”执行的感觉
并行:系统同时执行多个任务的能力。并行是指在多个处理器或者多核处理器上,真正同时地运行多个任务。并行化的目标是提高程序的执行效率,特别是在需要处理大量数据或计算密集型任务时,并行化可以显著减少任务的完成时间。
死锁
死锁是指在多线程多进程环境中,两个或多个进程(或线程)互相持有对方所需资源,导致它们都无法继续执行的一种状态。
避免嵌套锁:尽量避免在持有一个锁的时候去申请另一个锁,因为这样可能会导致死锁的发生。统一获取锁的顺序:如果多个线程需要获取多个锁,可以约定获取锁的顺序,这样可以避免出现死锁。设置超时时间:在获取锁的时候可以设置超时时间,如果超过一定时间还没有获取到锁,就放弃获取锁并释放已经获取的锁。死锁检测:通过检测程序的锁状态来判断是否存在死锁,并进行相应的处理。减少锁的持有时间:尽量减少持有锁的时间,使得锁的争用时间变短,从而减少死锁的发生概率。
进程间的通信方式
无名管道:亲缘进程通信
有名管道:任意进程通信
信号:用户可以给某个进程发送信号,一个进程也能给另一个进程发送信号,内核也可以给某个进程发送信号,信号三种默认操作:默认,捕获,忽略。
消息队列:多个进程可同时向一个消息队列发送消息,也可以同时从一个消息队列中接收消息。发送进程把消息发送到队列尾部,接受进程从消息队列头部读取消息,消息一旦被读出就从队列中删除
共享内存:共享内存是将物理空间的一片区域映射到内核空间,在将映射的内核空间与用户空间的区域进行连接。共享内存的操作不是一次性的,当共享内存段中的数据被读取后,依然存在。共享内存是所有进程间通信方式中效率最高的,原因是,操作共享内存段时,无需进行用户空间和内核空间的切换。
信号灯集:信号号灯集中的每一个灯,都控制一个进程信号灯集中的等的编号都是从0开始,信号灯集中的每个灯都维护了一个value值,当value为0时,处于阻塞状态,等待其他进程将该资源的值加到1。
套接字:内核提供的函数,用于创建一个套接字,并返回当前套接字的文件描述符,通过套接字文件描述符可以实现多个进程之间的通信,可以通过文件描述符进程进程之间的绑定。
线程中的同步互斥
- 线程可以通过全局变量和局部变量实现数据通信
- 进程运行空间都是独立的,不能进行数据通信
- 多线程间数据共享有可能执行同一个动作,导致数据异常,所以要加上互斥锁
- 多线程除了互斥之外还有同步,也就是一个线程完成任务后,交给第二个线程完成
- 线程访问的全局变量的资源,全局变量属于临界资源
- 访问临界资源的代码属于临界区
- 多个线程访问临界资源时无法确定谁先访问谁后访问,属于竞态
线程互斥-互斥锁
- 一个线程执行任务时其他线程处于等待状态,执行任务后第二个线程再进来执行,但是执行不分先后顺序
- 互斥锁本质上也是一个临界资源,线程获取到锁资源后执行
- 定义和初始化互斥锁,线程上锁,解锁,销毁锁资源
- 临界资源实质上是一个全局变量,所有的线程都共享这个资源
相关函数
#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错误码。
线程同步-无名信号量
- 无名信号量,本质也是一个临界资源(全局变量),维护了一个value值,限制线程的访问个数
- value值是1时线程获取临界资源,并将value值 -1
- 当线程执行结束后重新将value值+1,释放资源
相关函数
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化无名信号量
参数1:无名信号量的地址
参数2:
0:同一进程下的线程共享信号量
非0:不同进程之间共享
参数3:无名信号量的初始值。
返回值:成功返回0,失败返回-1,并置位错误码。
int sem_post(sem_t *sem);
功能:释放无名信号量,实质sem+=1操作
参数:无名信号量地址
返回值:成功返回0,失败信号量的值没有+1操作,返回-1,并置位错误码。
int sem_wait(sem_t *sem);
功能:申请无名信号量,实质sem-=1操作
参数:无名信号量地址
返回值:成功返回0,失败信号量的值没有+1操作,返回-1,并置位错误码。
int sem_destroy(sem_t *sem);
功能:销毁无名信号量
参数:无名信号量的地址
返回值:成功返回0,失败返回-1,并置位错误码。
线程同步-条件变量
例如:生产者先让消费者组成一个队列,生产者生产了一台劳斯莱斯,唤醒第一个消费者来消费,然后再生产第二台劳斯莱斯,唤醒第二个消费者来消费,这样可以精确化的控制生产者线程和消费者线程的协同。
条件变量的本质,维护了一个队列,每次唤醒一个消费者来消费。但是队列中线程之间要排队,进入队列时也会产生竞态现象,所以,条件变量需要加上互斥锁一起使用才行。
相关函数
#include <pthread.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//静态初始化条件变量
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
功能:动态初始化条件变量
参数1:条件变量的地址
参数2:条件变量的默认属性填NULL
返回值:成功返回0,失败返回非0
int pthread_cond_signal(pthread_cond_t *cond);
功能:唤醒一个处于等待状态(等待条件变量)的线程
参数:条件变量的地址。
返回值:成功返回0,失败返回非0
int pthread_cond_broadcast(pthread_cond_t *cond);
功能:唤醒所有处于等待状态(等待条件变量)的线程
参数:条件变量的地址。
返回值:成功返回0,失败返回非0
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
功能:等待被其他线程唤醒
参数1:条件变量的地址
参数2:互斥锁的地址
返回值:成功返回0,失败返回非0
int pthread_cond_destroy(pthread_cond_t *cond);
功能:销毁条件变量
参数:条件变量的地址
返回值:成功返回0,失败返回非0
作业
一:使用无名信号量实现输出春夏秋冬
#include <myhead.h>
//定义无名信号量
sem_t sem[4];
void *fun1()
{
int i = 0;
while (i < 4)
{
sem_wait(&sem[0]); //wait信号0
printf("春\n");
sem_post(&sem[1]); //释放信号1
i++;
}
}
void *fun2()
{
int i = 0;
while (i < 4)
{
sem_wait(&sem[1]); //同上
printf("夏\n");
sem_post(&sem[2]);
i++;
}
}
void *fun3()
{
int i = 0;
while (i < 4)
{
sem_wait(&sem[2]); //同上
printf("秋\n");
sem_post(&sem[3]);
i++;
}
}
void *fun4()
{
int i = 0;
while (i < 4)
{
sem_wait(&sem[3]); //同上
printf("冬\n");
sem_post(&sem[0]);
i++;
}
}
int main(int argc, const char *argv[])
{
//定义线程号
pthread_t tid[4];
//初始化无名信号量
sem_init(&sem[0], 0, 1); //信号sem[0] 设置为一个线程可用状态, 其余均无使用线程数
sem_init(&sem[1], 0, 0);
sem_init(&sem[2], 0, 0);
sem_init(&sem[3], 0, 0);
//创建四个线程
if (-1 == pthread_create(&tid[0], NULL, fun1, NULL))
{
perror("pthread_create");
return -1;
}
if (-1 == pthread_create(&tid[1], NULL, fun2, NULL))
{
perror("pthread_create");
return -1;
}
if (-1 == pthread_create(&tid[2], NULL, fun3, NULL))
{
perror("pthread_create");
return -1;
}
if (-1 == pthread_create(&tid[3], NULL, fun4, NULL))
{
perror("pthread_create");
return -1;
}
//回收四个进程
for (int i = 0; i < 4; i++)
{
pthread_join(tid[i], NULL);
}
//销毁无名型号量
for (int i = 0; i < 4; i++)
{
sem_destroy(&sem[i]);
}
return 0;
}
运行结果

二:生产者消费者模型使用条件变量实现一遍
#include <myhead.h>
//产品数量
int k = 0;
//定义互斥锁
pthread_mutex_t mutex;
//定义条件变量
pthread_cond_t cond;
//生产线程
void *producer()
{
for (int n = 0; n < 10; n++)
{
printf("生产一个产品\n");
k++;
}
//唤醒cond队列中所有等待线程
pthread_cond_broadcast(&cond);
printf("共有%d个\n", k);
pthread_exit(NULL);
}
void *consumer()
{
pthread_mutex_lock(&mutex); //互斥锁上锁
pthread_cond_wait(&cond, &mutex); //等待唤醒
k--;
printf("消费了一个产品\n");
printf("剩余%d个产品\n", k);
pthread_mutex_unlock(&mutex); //互斥锁解锁
}
int main(int argc, const char *argv[])
{
pthread_t pro, con[10];
//初始化互斥锁
pthread_mutex_init(&mutex, NULL);
//初始化条件变量
pthread_cond_init(&cond, NULL);
//生成生产者线程
if (pthread_create(&pro, NULL, producer, NULL) == -1)
{
perror("pthread_create");
return -1;
}
//生成消费者线程
for (int i = 0; i < 10; i++)
{
if (pthread_create(&con[i], NULL, consumer, NULL) == -1)
{
perror("pthread_create");
return -1;
}
}
//回收线程
pthread_join(pro, NULL);
for (int i = 0; i < 10; i++)
{
pthread_join(con[i], NULL);
}
//销毁锁和条件变量
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
运行结果


808

被折叠的 条评论
为什么被折叠?



