线程安全
多个线程同时运行,访问临界资源,不会导致程序结果产生二义性,这样的情况是线程安全的。
临界资源:多线程执行流共享的资源叫临界资源。
临界区:每个线程内部访问临界资源的代码
访问:在临界区当中对临界资源进行非原子操作
原子操作:不会为任何调度机制打断的操作,该操作只有两种状态,要么完成,要么未完成。
线程不安全的原理
举个栗子:我们规定一个场景
- 假设现在在同一个程序当中有两个线程,线程A和线程B,并且有一个int类型全局变量,值为10;线程A和线程B在各自的入口函数当中对这样一个变量进行++操作。
- 线程A拥有CPU之后,对全局变量进行++操作,并非是原子操作,也就是意味着线程A,在执行++过程中有可能会被打断。假设线程A刚将全局变量的数值10读到CPU的寄存器当中来,就被切换出去了(程序计数器保存下一条执行的指令,上下文信息当中保存寄存器的值,这两个是用来当线程A再次拥有CPU的时候恢复现场使用的)
- 这时有可能线程B拥有了CPU资源,对全局变量进行++操作,并将10加成11放回到了内存当中
- 线程A再次拥有CPU之后,根据程序计数器和上下文信息恢复现场,继续往下执行,从寄存器当中读到的值仍然是10,++操作之后还是11
线程A和线程B各自对全局变量进行了++操作,理论上全局变量应为12,但现在程序计算的结果有是11,这样的线程就是不安全的,访问临界资源对程序的结果造成了二义性。
线程不安全怎么解决
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区访问临界资源。
同步:为了在互斥的基础上追求资源的合理分配,在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源。
互斥锁
使用互斥锁来保证互斥属性,互斥锁的底层是互质量,互斥量本质是一个计数器,该计数器只有两个取值0或者1,当线程获取互斥锁的时候,如果计数器当中的值为0,表示当前线程获取不到互斥锁,反之则表示当前线程可以获取到互斥锁。
原理
互斥锁如何保证操作的原子性?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fQ9yGOIk-1613641108316)(image/互斥锁.png)]
- 加锁的时候
- 寄存器当中的值直接赋值为1
- 将寄存器当中的值和计数器当中的值交换(互换指令xchgb)
- 判断当前寄存器中的值,得出加锁结果
- 当寄存器的值为1时,表示可以加锁。
- 当寄存器的值为0时,表示不可以加锁。
- 解锁的时候
- 将寄存器的值赋为1
- 交换寄存器和计数器当中的值手机果然很奇妙。
使用
定义互斥锁
pthread_mutex_t;//互斥锁变量类型
初始化互斥锁
int pthread_mutex_init(pthread_mutex_t
*restrict mutex,const pthread_mutexattr_t *restrict attr);
- mutex:传入互斥锁变量的地址
- attr:属性,一般传递NULL,采用默认属性
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
-
pthread_mutex_t本身是一个结构体类型,PTHREAD_MUTEX_INITIALIZER宏定义是一个结构体的值
-
#define PTHREAD_MUTEX_INITIALIZER { {0,0,0,0,0,_PTHREAD_SPINS,{0,0}}};
加锁
-
int <