1. 竞态条件
2. 临界区
3. 原子操作
4. 互斥
5. 进程通信方式
5.1 管道
5.2 信号
5.3 共享内存
6. 同步与异步
7. 堵塞与非堵塞
8. 死锁、饥饿锁、活锁
8.1 死锁
8.2 饥饿锁
8.3 活锁
9. 线程安全与不安全和可重入函数
9.1 线程安全
9.2 线程不安全
9.3 可重入函数
10. 条件变量
多个进(线程同时对同一个资源进行访问(一般是对资源进行写入), 造成程序运行结果错误. 这种错误我们称为竞态.
竞态发生可能不会那么频繁, 但是一旦发生, 就绝对会造成程序运行结果错误, 并且排查这种错误也是比较困难.
正是因为它们的发生并不频繁,所以场景重新变得非常不容易. 找到并消除一个竞态条件很可能会让人耗费很长时间.
造成竞态条件的根本原因在于进(线)程在进行某些操作的时候被中断了. 虽然进(线程)重新运行时其状态会恢复如初,
但是外界环境很有可能已经在这极短的时间内发生了改变.
导致竟态条件发生的代码区域称作临界区
或者说只能被串行化访问、执行的的某个资源或某段代码称为临界区
临界区的目的
临界区对是否可以被中断没有强制的规定. 它只要保证一个访问者在临界区中时, 其他访问者不会被放进来就可以了.
执行过程中不能中断的操作称为原子操作
原子操作由一个单一的汇编指针表示, 并且得到芯片级支持.
保证只有一个访问者在临界区中的做法叫互斥
也可以说: 在同一时刻, 只允许一个线程处于临界区之内的约束称为互斥
互斥目的
让并发改为串行,牺牲了运行效率,但保证了数据安全和程序结果的正确性
互斥量
在同一时刻, 每个线程在进入临界区前, 都必须先锁定某个对象, 只有成功锁定对象才会允许进入临界区,
否则就会堵塞,这个对象称为互斥对象或互斥量
互斥量有2种状态: 已锁定状态和未锁定状态
互斥量每次只能锁定一次, 通俗的说, 处于已锁定状态的互斥量不能再次锁定, 除非它已解锁.
否则任何线程都不能对它进行二次加锁.
如果对一个已锁定的互斥量进行加锁操作, 那么这个操作一定会失败. 会导致加锁线程堵塞而进入休眠状态.
成功锁定互斥量的线程会成为该互斥量的所有者, 只有互斥量的所有者才能进行解锁.
进程通信: 保证两个或者多个正在运行的进(线)程之间共享和传递数据的方式
1. 无名管道(pipe): 管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用.
进程的亲缘关系通常是指父子进程关系
2. 有名管道(named pipe): 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信
信号是一种异步通信方式, 它本质是用软件来模拟硬件的中断机制, 用于通知接收进程某个事件已经发生
多个进(线)程可以访问的同一块内存空间就是共享内存
共享内存只是相当于一个指针来指向一块内存,在当前进程下用户可以随意的访问
同步: 调用者必须等待被调用的方法调用返回后, 才能继续后续的行为.
异步: 调用者调用立即返回, 让调用者可以继续后续的操作.
堵塞: 调用者必须等待被调用的数据没准备好的情况下的等待称为堵塞
非堵塞: 调用者不必等待被调用的数据没准备好的情况下的立即返回称为非堵塞
小张喜欢喝咖啡,同时养了好多狗;
出场:
1. 小张:相当于我们的客户端进程
2. 小狗大黑:阻塞处理的IO函数
3. 小狗大黄:非阻塞处理的IO函数
4. 小狗大白、大红:异步处理的IO函数
同步阻塞:小张派大黑去看咖啡煮好没,大黑等咖啡煮开了才回来;
同步非阻塞:小张派大黄去看咖啡煮好没,大黄看了一眼就回来了,过了一会,再大黄再去看看咖啡煮好没;
异步非阻塞:小张派大白和大红去看咖啡煮好没,大白和大红到了厨房后,大白就回来告诉小张,大红已经到厨房啦;过了一会咖啡煮好了,大红回到客厅告诉小张
异步阻塞:(这个太傻了,目前还没遇到)小张派大白和大红去看咖啡煮好没,大白和大红到了厨房后,一起在那等着;过了一会咖啡煮好了,大红大白一起回到客厅告诉小张
有些人常把同步阻塞和异步非阻塞联系起来, 但实际上经过分析, 阻塞与同步, 非阻塞和异步的定义是不一样的.
同步和异步的区别是遇到长时间任务调用是否等待.
阻塞和非阻塞的区别是数据没准备好的情况下是否立即返回.
同步可能是阻塞的,也可能是非阻塞的,而非阻塞的有可能是同步的,也有可能是异步的.
死锁: 一组互相竞争资源的线程因为互相等待,导致“永久”阻塞的现象.
死锁原因:
1. 根本原因是获取锁的顺序不一致
2. 同一个线程2次加锁
如何避免死锁:
1. 固定顺序锁: 加锁时按照固定按照顺序加锁
2. 锁超时: 给锁加上超时, 如果超时则放弃执行
饥饿锁: 一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行
活锁: 线程不断重复执行相同的操作, 由于某些条件没有满足, 但每次操作的结果都是失败的
线程安全: 一个代码块它可以被多个线程并发执行, 并且总是能够产生预期的结果
线程不安全: 一个代码块它可以被多个线程并发执行, 可能产生错误预期的结果
可重入函数: 多个线程可以并发的调用一个函数, 无论该函数以任意顺序调用,
该函数都能够产生预期的结果. 那么该函数就是可重入函数
条件变量(condition variable)是利用线程间共享的全局变量进行同步的一种机制.
主要包括两个动作:
一个线程等待某个条件为真,而将自己挂起
另一个线程使的条件成立,并通知等待的线程继续.
为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起
条件变量目的
为了防止竞争或者减少竞争次数
在使用互斥锁的基础上引入条件变量可以使程序的效率更高,因为条件变量的引入明显减少了线程取竞争互斥锁的次数.