进程、线程间的通信以及线程安全

进程间通信

进程的虚拟地址互相独立,没法使他们的虚拟内存有交集,所以在物理内存上开辟一块空间,让两个进程在相同的该块内存中实现交互

不同的通信方式应对不同应用场景:管道、共享内存、消息队列、信号量

管道:

实现数据资源传输,本质:内核中的一块缓冲区,通信的本质是访问同一块缓冲区

虚拟地址空间分为内核空间和用户空间,进程只能访问用户空间,访问内核空间只能通过操作系统提供的系统调用接口进行访问

内核空间只有一块,所有进程共有

管道既是进程在内核空间中开辟一块缓冲区,所有能够访问该块缓冲区的进程可以实现通信

管道分类:
匿名管道:

内核中的缓冲区没有标识符,其他进程没法找到内核中相同的缓冲区,只能通过子进程复制父进程的方式访问管道,所以只能用于亲缘关系进程间通信

int pipe(int pipefd[2]);//创建匿名管道,管道的操作句柄通过参数返回
pipefd[0]-用于从管道读取数据
pipefd[1]-用于向管道写入数据

pipe()是用户与内核的操作桥梁。在内核空间中开辟缓冲区后,向用户空间返回fd[0](读数据)、fd[1](写数据)两个操作句柄(也是文件描述符)

复制子进程后也复制了用户空间的句柄,可以访问同一块内核空间

命名管道:

内核中的缓冲区具有标识符,其他进程可以通过相同标识符找到管道,主机上任意进程可以通信,是一个可见于文件系统的管道文件

int mkfifo(char *filename, mode_t mode);
管道特性:

半双工通信:可以选择方向的单向通信

自带同步与互斥

互斥:同一时间的唯一访问,保证访问安全。体现:操作管道时操作大小不超过PIPE_BUF(4096个字节),保证操作原子性,最小单位不会被打断

同步:按照某种秩序访问管道,保证合理性。管道缓冲区大小有限制,满了继续write写入会阻塞;没有数据继续read就会阻塞

提供字节流服务:提供有序的、安全的、基于连接的、可靠的以字节为单位的传输方式

基于连接的:如果所有读端被关闭,则write触发异常,导致进程退出;如果所有写端被关闭,则read完数据不再会阻塞,返回0

管道生命周期跟随进程:管道的缓冲区在所有进程退出后就会释放

共享内存

开辟一块物理内存,在内核中进行各种描述(名字、标识符),多个进程找到同一块物理内存,通过页表将其映射到自己的虚拟地址空间中,通过自己的虚拟地址进行访问

其他的进程通过页表将同一块物理内存映射到自己的虚拟地址空间中,多个进程访问各自虚拟地址,实则访问同一块物理内存

操作流程:

1.创建/打开共享内存

int shmget(key_t key, int size, int flag)标识符,大小,访问权限

2.与共享内存建立映射关系

int shmat(int shmid, void *addr, int)创建/打开内存的标识符,映射首地址,标志位(读写权限)

3.内存操作

4.解除映射关系

int shmdt(void *addr)

5.删除共享内存

int shmctl(int shmid, int cmd, struct shmid_ds* buf)标识符,对共享内存的操作,获取共享内存信息、一般置空
特性:

最快的进程间通信,直接通过虚拟地址访问数据,相较于其他方式少了用户态与内核态直接的拷贝操作

共享内存声明周期随内核,不随着进程退出销毁

操作注意事项:共享内存操作并非安全,需要自己进行同步、互斥(用信号量)

消息队列

本质是内核中一个优先级队列,多个进程通过相同的标识符访问同一个队列

通过向队列插入/获取节点实现通信

特性:

生命周期随内核

自带同步与互斥(队列有大小)

信号量

作用:实现进程间的同步与互斥,

本质:是一个计数器,统计资源数量,实现资源访问合理性(通过计数判断,有资源才能获取)

实现互斥(安全性):同一时间对资源唯一访问,保证资源数量只有一个,只有一个进程能访问,访问完毕计数还原

操作:

P操作:计数-1,判断是否<0,表示没有资源,使进程等待

V操作:计数+1,判断有没有等待的进程,有的话就唤醒一个

实现同步(合理性):信号量本身常规用于实现同步,用计数进行资源获取合理性判断

进程间通信总结:

进程有独立性,无法直接通信,所有操作系统提供了几种通信方式,针对不同的应用场景使用不同的方式

线程安全
概念:

指线程对临界资源的访问操作是安全的

临界资源:多个线程都能访问到的资源,即各线程的交叉点资源

临界区:访问临界资源的这段代码

实现:

通过互斥与同步

互斥:同一时间对临界资源的唯一访问,实现访问安全性

同步:让线程对临界资源按秩序访问,实现访问合理性

互斥的实现:互斥锁

互斥锁:本质是只有0或1的计数器,标记临界资源访问状态,控制能否访问

互斥锁自身也是一个临界资源,其操作本身是线程安全的,操作都是原子操作

流程:

1.创建互斥锁

2.初始化互斥锁

3.在临界资源访问前加锁,加锁:先判断资源是否可以访问,可以访问正确返回并置为不可访问状态,不可访问阻塞线程或报错

4.资源访问完毕后解锁,解锁:将资源状态置为可访问状态a’a

接口:
pthread_mutex_t mutex;//创建互斥锁变量
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);//互斥锁初始化。传入互斥锁,属性信息(一般置空)
int pthread_mutex_lock(pthread_mutex_t *mutex);//阻塞加锁,不能加锁则等待
int pthread_mutex_trylock(pthread_mutex_t *mutex);//非阻塞加锁,不能加锁报错返回
int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);//销毁互斥锁
死锁:

描述程序流程卡死无法继续推进的情况

互斥锁使用中产生死锁原因:多个资源对锁进行争抢操作,因为推进顺序不当,造成互相等待

死锁产生的必要条件:

1.互斥条件,同一时间只有一个线程能加锁

2.不可剥夺条件,自己加的锁只能自己解

3.请求与保持条件,请求拿不到的锁,保持已经拿到的锁

4.环路等待条件,第一个线程拿到了A锁,等待B锁,第二个线程拿到了B锁,请求A锁,造成互相等待

预防死锁:预防必要条件,加锁解锁顺序一致避免环路,加不上第二个锁释放已有的锁

避免死锁:银行家算法,死锁检测算法

同步的实现:条件变量

提供了两个接口:一个pcb等待队列,和使线程阻塞、唤醒阻塞的接口

在线程不满足资源获取的情况下,通过阻塞接口使线程阻塞,将线程加入pcb等待队列,等到条件满足时唤醒

条件变量不控制阻塞、唤醒的时间,由程序员决定访问秩序,条件变量搭配互斥锁一起使用,因为判断时需要在多个线程中判断,该判断的依据是临界资源

接口:

pthread_cond_t cond;//创建条件变量
int pthread_cond_t_init(pthread_cond_t *cond, pthread_condattr_t *attr);//初始化条件变量。条件变量,守信信息
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);//线程阻塞接口。条件变量,互斥锁
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex);//限制等待时长,超时报错返回
int pthread_cond_signal(pthread_cond_t *cond);//唤醒阻塞状态,唤醒至少一个
int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒阻塞状态,唤醒所有pcb
int pthread_cond_destroy(pthread_cond_t *cond);//销毁条件变量
使用注意事项

1.条件变量实现流程控制的时候,条件判断需要使用循环判断(防止不满足条件但是if语句执行完毕继续运行,访问资源),被唤醒后重新判断是否满足条件,不满足重新进入休眠

2.多个角色,需要使用多个条件变量(多个pcb等待队列),不同角色分开等待分开唤醒,防止唤醒角色错误

生产者消费者模型

一种典型的设计模式

应用场景:

有大量的数据产生并进行数据处理的场景

每一个模块负责生产数据和处理数据,假设处理数据比较慢,为了及时处理数据,启用大量线程,每一个线程负责生产数据并处理数据,但是线程并不是越多越好(更多内存占用,cpu在线程间轮流切换消耗时间导致更低内存利用率),并且有可能在压力峰值时,因为数据过多来不及处理导致数据被丢弃。

概念:

将生产数据和处理数据职责的模块分开,负责生产数据的模块将数据放入一个任务队列中,负责处理数据的模块不断从任务队列中获取数据来处理。对资源的利用更加合理

优点:

1.解耦合2.支持忙闲不均(通过任务队列完成数据的缓冲)3.在线程安全的情况下支持并发,原先是一对一生产-处理数据,生产者消费者模型可以多对多的处理数据

信号量

用于实现同步与互斥,可以用于线程间也可用于进程间,不需要搭配互斥锁

本质:

是一个计数器,对资源进行计数,以及提供一个pcb等待队列,向外提供P/V操作接口,实现同步与互斥(本质实现同步,也能实现互斥)

P操作:在临界资源访问之前进行P操作,使计数-1,判断计数如果<0则不符合访问条件,使进程或线程阻塞,否则正确返回获取资源

V操作:在生产资源以后进行V操作,计数+1,唤醒一个等待的进程或线程

互斥实现:

计数为1,表示资源只有1个,在资源访问之前进行P操作计数-1为0,其他线程或进程无法访问,访问完毕之后进行V操作计数+1(相当于解锁)

流程:

1.创建信号量;2.初始化信号量;3.在获取资源之前P操作;4.生产资源之后V操作;5.销毁信号量

创建

sem_t;

初始化

sem_init();

等待/尝试加锁/限制时长等待

sem_wait/sem_trywait/sem_timedwait;

V操作,唤醒一个等待线程

sem_post;

销毁

sem_destroy
接口:
int sem_init(sem_t *sem, int pshared, int value)信号量变量;控制本次信号量用于线程还是进程,线程会创建全局变量,进程创建共享内存;信号量资源数初值
sem_wait/sem_trywait/sem_timedwait(sem_t *sem);
sem_post(sem_t *sem);
sem_destroy(sem_t *sem);
线程池

线程的池子

线程池中有很多线程,这些线程针对任务进行处理

1.线程数量有最大上限

2.线程不需要随着任务创建、销毁,(有固定数量和自适应的线程池,固定数量的线程的总数不会改变)

作用:

1.线程数量不会无限制增长,防止资源耗尽系统崩溃

2.工作线程会一直获取任务进行处理,节省了任务处理中线程创建和销毁的时间成本。因为任务处理总时间T=线程创建时间+任务处理时间+线程销毁时间

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值