-
先来一个不用lock的例子
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <pthread.h> // pthread头文件
#include <unistd.h> // sleep的头文件
using namespace std;
int g_total = 0;
// 两个线程
pthread_t thread1;
pthread_t thread2;
// 加10次,每次加1,总共加10
void *add_1(void *arg)
{
int i = 0;// 故意使用局部变量临时保存g_total以增大出错的可能性
for(int j = 0; j < 10; j++)
{
i = g_total;
i++;
sleep(0.5);
g_total = i;
cout << __func__ <<" "<< g_total << endl;
}
return NULL;
}
// 加5次,每次加2,总共加10
void *add_2(void *arg)
{
int i = 0;
for(int j = 0; j < 5; j++)
{
i = g_total;
i += 2;
sleep(0.5);
g_total = i;
cout << __func__ <<" "<< g_total << endl;
}
return NULL;
}
int main()
{
// 创建两个个线程。
pthread_create(&thread1, NULL, &add_1, NULL);// 给全局变量+10
pthread_create(&thread2, NULL, &add_2, NULL);// 全局变量再+10
// 阻塞主进程,等待两个线程
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
cout << __func__<<" "<< g_total << endl;
return 0;
}
理论上最终g_total的结果应该是20,因为两个线程每个都给他加了10。不过最终的输出结果其实是不确定的,每次执行结果都不一样。其中一次的执行结果如下:
原因就在于没有对全局变量做保护。
- 主进程创建出两个线程,在这之后两个线程同时运行;
- 假设thread1先执行进循环体,thread2比她稍慢;
- thread1先获取全局变量,并将其赋值给i,稍后thread2也做了同样的事,所以当前两个线程里的局部变量i都拿到了全局变量的初始值;
- 然后,thread1开始对i做加1运算,稍后thread2也做了同样的事;此时两个线程里的局部变量i的值不相等,一个为1,一个为2;
- 两个线程都将i赋值给全局变量,thread1先来,所以g_total变成1,thread2慢一点,g_total变成又再一次被thread2里的i赋值,变成了2;
- 所以此时,虽然经过了两次相加的操作,但是g_total并没有先加1再加2,而是加2的操作覆盖了加1的操作,这一点从打印出来的结果上也能看出来,g_total先在thread1里变1,然后在thread2里变2,而不是变3(本来1+2应该变3的)
-
接下来试试加lock的情况:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <pthread.h> // pthread头文件
#include <unistd.h> // sleep的头文件
using namespace std;
int g_total = 0;
pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;
// 两个线程
pthread_t thread1;
pthread_t thread2;
void *add_1(void *arg)
{
int i = 0;
for(int j = 0; j < 10; j++)
{
pthread_mutex_lock(&mymutex);
i = g_total;
i++;
sleep(0.5);
g_total = i;
pthread_mutex_unlock(&mymutex);
sleep(0.5);
cout << __func__ <<" "<< g_total << endl;
}
return NULL;
}
void *add_2(void *arg)
{
int i = 0;
for(int j = 0; j < 5; j++)
{
pthread_mutex_lock(&mymutex);
i = g_total;
i += 2;
sleep(0.5);
g_total = i;
pthread_mutex_unlock(&mymutex);
sleep(0.5);
cout << __func__ <<" "<< g_total << endl;
}
return NULL;
}
int main()
{
// 创建两个个线程。
pthread_create(&thread1, NULL, &add_1, NULL);
pthread_create(&thread2, NULL, &add_2, NULL);
// 阻塞主进程,等待两个线程
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
cout << __func__<<" "<< g_total << endl;
return 0;
}
注意:
要使用mutex_lock,必须先定义一个pthread_mutex_t类型的全局变量作为标志使用。两个线程都是通过判断这个全局变量的状态来决定是否等待的。
pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;
我们给获取全局变量以及给全局变量赋值那一段代码加了锁。之后,在同一个时刻总是只有一个线程可以运行到这块加锁的代码。当thread1运行到循环体内时,thread2就只能等着;同样,当thread2运行到循环体时,thread1也只能等着。
所以,当thread1给全局变量赋值时,thread2什么都没做;当thread2再获取全局变量时,其获取到的就是已经被thread1修改后的变量,而不会使修改前的版本。
例子里我们故意写两个不同的函数,也就是为了说明这种为两个线程加锁的操作,不一定是非要对同一段代码进行,而可以两个线程对应两段不同的代码。线程看到的只是那个mutex_t变量,这是它的唯一判断标准。
加锁之后的执行接过如下:
这回可以看到虽然两个线程交替进行,但是g_total的值一直增加,不会减小了。并且最终的结果也是我们预测的20.