引言
针对面试常考点,本文对进程线程通信方式进行简单总结,同时额外补充锁相关知识
目录
-
进程
- 管道
- 无名管道
- 有名管道
- 系统IPC
- 消息队列
- 共享内存
- 信号量
- 套接字
- 管道
-
线程
- 互斥锁
- 信号量
- 条件变量
- 读写锁
- 自旋锁
-
补充
- 死锁
- 活锁
- 公平锁
- 非公平锁
- 乐观锁
- 悲观锁
一、 进程
提要:
进程的创建方式有两种:一种由操作系统创建(系统进程),一种由父进程创建(fork()函数),对于第二种创建方式,子进程完全复制了父进程的地址空间的内容,包括数据段和堆栈段的内容,但子进程却和父进程仅仅只共享代码段,不共享数据段和堆栈段,对于其他变量需要进行进程通信才可以实现交互,所以进程间的通信更关注的是两个进程间进行数据交互
1. 管道
操作系统在内核中开辟一块缓冲区进行管道通信(两个进程同一时刻进行单向通信)
实现原理:父子进程各自拥有一个文件描述符表,但是两者的内容是一样的,既然内容一样,那么指向的就是同一个管道,即父子进程看到了同一份公共资源
注:如果要双向通信,需建立两个管道
无名管道是不可见的文件,所以只允许有亲缘关系的进程进行通信
有名管道可以通过同一个路径名而看到同一份资源,所以允许无亲缘关系进程间的通信
特点:
- 管道通信依赖于文件系统,即管道的生命周期随进程
- 管道的通信被称为面向字节流,与通信格式没有关系
- 管道自带同步机制,保证读写顺序一致
- 缓冲区大小受到较大的限制
2. 系统IPC——消息队列
消息队列是一个双向通信的方式,其可以克服管道通信传递信息少、只能承载无格式字节流以及缓冲区大小受限等缺点
实现原理:内核地址空间中的内部链表,消息可以顺序地发送到队列中,并以不同方式从队列中获取
3. 系统IPC——共享内存
不同进程通过访问同一个逻辑内存,实现通信
注:
- 共享内存并未提供同步机制,所以可能会发生出错,需要加入信号量机制
- 因为共享内存通信方式是直接在内存上操作,所以速度最快
4. 系统IPC——信号量
信号量通过计数器机制,可以实现防止多个进程同时访问一块资源
注:信号量的生命周期并不随进程的结束而结束,而是随内核的
5. 套接字
socket套接字可以实现多机通信
二、线程
所有线程都可以共享进程的资源,如代码段、数据段,但是每个线程都有自己独立的资源,如栈、寄存器,所以线程通信更关注的是互斥与同步
1. 互斥锁
确保同一时间只有一个线程能访问被互斥锁保护的资源,互斥锁只有上锁和解锁两种状态
互斥锁一般被设置成全局变量,锁定互斥量的线程与解锁互斥量的线程必须是同一个线程
2. 信号量
机制同进程通信中的信号量
3. 条件变量
条件变量是用来等待而不是用来上锁的,其通常允许线程挂起,直到共享数据上的某些条件得到满足,避免忙等待带来的不必要消耗
通常条件变量和互斥锁同时使用,以避免出现条件竞争,原因如下:
条件变量这个变量其实本身不包含条件信息,需要外面进行条件判断,这个条件通常是多个线程或进程的共享变量,因此必须对共享变量加上互斥锁
4. 读写锁
在任何时刻只能有一个线程访问资源,获取锁操作失败时,不会进入睡眠,而是原地自旋,直到锁被释放,这样节省了线程从睡眠到被唤醒的时间消耗,提高效率。
5. 自旋锁
当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环,避免类似互斥锁进入睡眠状态,省去唤醒的开销
三、补充
1. 死锁与活锁
死锁:指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象
产生原因
(1)互斥条件:进程对所分配到的资源不允许其他进程访问,若其他进程访问,只能等待,直到占有该资源的进程使用完成后释放该资源
(2)请求保持条件:进程获得一定资源后,又对其他资源发出请求,但该资源被其他进程占有,此时请求阻塞,但该进程不会释放自己已经占有的资源
(3)不可剥夺条件:进程已获得的资源,只能自己释放,不可剥夺
(4)环路等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
解决办法
(1)请求保持:资源一次性分配
(2)不可剥夺:当进程新的资源未得到满足时,释放已有的资源
(3)环路等待:资源按序号递增,进程请求按递增请求,释放则相反
活锁: 线程1可以使用资源,它让其他线程先使用资源,线程2也可以使用资源,它让其他线程先使用资源,最后两个线程都无法使用资源
2. 公平锁与非公平锁
公平锁:在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己
非公平锁:直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式
注意:非公平锁的优点在于吞吐量比公平锁大
3. 乐观锁与悲观锁
乐观锁:假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,乐观锁适用于多读的应用类型,这样可以提高吞吐量
乐观锁适用于写比较少的情况下(多读场景)
悲观锁:假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁,悲观算适用于共享资源,每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程
悲观锁适用于写比较多的情况下(多写场景)