在阅读本文前,你需要一点c++知识和一点线程知识
为什么需要锁
我们考虑一个这样的场景,有两个线程都需要修改同一个全局变量,都对他进行n次加一的操作。
#include<pthread.h>
#include<iostream>
using namespace std;
int x=0;
int n=100;
void *pthread_child(void *arg){
for(int i=0;i<n;i++)//对x加100次
x++;
return nullptr;
}
int main(){
pthread_t p1,p2;
pthread_create(&p1, NULL, pthread_child, NULL);
pthread_create(&p2, NULL, pthread_child, NULL);
pthread_join(p1,NULL);
pthread_join(p2,NULL);
cout<<"x="<<x<<endl;
}
编译运行完结果是
x=200
当次数还是100次的时候,一切还很正常,我们尝试着把这个数字变大,比如一万次或者十万次。你可以自己尝试一下。
当我修改成一万次之后,x的值是两万,目前还是很正常。可是当我们把次数修改成十万次之后,奇怪的事情发生了(如果奇怪的事情没有发生,可以再把数字加大),x的值仿佛不受控制了,每次运行的值都不是二十万,而且还不是一个确定的值。
这是为什么呢?
让我们先进入底层看看发生了什么:
线程1运行,对x进行一次加一操作。
在底层,
第一步:线程1需要将内存里面x的值复制到自己的eax寄存器。
第二步:对eax寄存器里面的值进行加一操作。
第三步:再把eax寄存器里面的值复制回内存里面的x
mov 0x8049a1c, %eax
add $0x1, %eax
mov %eax, 0x8049a1c
当线程1开开心心地执行到第二步之后(线程1的eax是1),意外的事情发生了,时钟中断,线程2开始运行,线程2也把x加载到自己的eax寄存器,然后执行第二步,此时的线程2的eax也是1,第三步,把1存回了x。此时线程1终于杀了回来,继续上一次运行,执行第三步,也把1存回了x。
两个线程都执行完了一次x++。但是x只增加了1。
== 细心的小伙伴可能要问了,eax寄存器不是只有一个吗,为什么线程1和线程2一人一个呢。这因为操作系统虚拟化了寄存器,简单来说,就是每一个线程有一组数据,这组数据包含各个寄存器的值,在操作系统切换线程时,把当前各个寄存器的值保存到线程这组数据中,然后再把下一个线程的数据加载到寄存器中 ==
如何解决呢
yes!!!此时就需要我们的锁子哥上场了,锁就是保证了临界区操作的“原子性”。为什么加引号呢,因为锁只保证在同一个锁下的原子性。
我们给上述代码加上锁之后就不会出现
如下:
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
void *pthread_child(void *arg){
for(int i=0;i<n;i++)
{
pthread_mutex_lock(&m);
x++;
pthread_mutex_unlock(&m);
}
return nullptr;
}
这个锁的位置我是随便加的,可能性能不好,但是大家可以根据自己的程序选定一个临界区