在上一节里面,我们学习了如何通过c++来创建和管理线程,
这一节我们主要学习如何同步多线程中的资源。
什么是同步资源呢?简单来说就是实现多线程共享资源的安全访问,举个例子来说,如果两个线程都需要对同一个变量进行修改,如果不加控制的话,这个变量最后的结果可能就不是我们预期的结果了,大家先来看以下的代码示例:
- 无线程同步例子
#include<thread>
#include<iostream>
using namespace std;
int global_value = 0;
void myfunc1()
{
for(int i=0;i<20;i++)
{
global_value++;
cout<<"t1:"<<global_value<<endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void myfunc2()
{
for(int i=0;i<20;i++)
{
global_value++;
cout<<"t2:"<<global_value<<endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
public static void main()
{
thread t1(myfunc1);
thread t2(myfunc2);
t1.join();
t2.join();
cout<<"global_value at last:"<<global_value<<endl;
}
- 有线程同步的例子
#include<thread>
#include<mutex>
#include<iostream>
using namespace std;
std::mutex data_mutex
int global_value = 0;
void myfunc1()
{
for(int i=0;i<20;i++)
{
data_mutex.lock();
global_value++;
data_mutex.unlock();
cout<<"t1:"<<global_value<<endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void myfunc2()
{
for(int i=0;i<20;i++)
{
data_mutex.lock();
global_value++;
data_mutex.unlock();
cout<<"t2:"<<global_value<<endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
public static void main()
{
thread t1(myfunc1);
thread t2(myfunc2);
t1.join();
t2.join();
cout<<"global_value at last:"<<global_value<<endl;
}
大家可以把上面的例子先拷贝到自己的c++运行环境里面,运行结果对比一下。
大家会发现第一个例子中的值是多少呢?
第二个例子中的值又是多少呢?
为什么是这样的结果?
c++中线程同步的方法:
- 互斥锁mutex
在上面的第二个例子中,我们使用了mutex来同步数据,mutex什么意思呢?mutex就是互斥锁,它的作用就是在访问一个变量之前先对其进行“上锁”,举个例子就好像我们大家都去试衣间试衣服的时候,我们会在门口挂一个牌子,告诉别人这个试衣间在使用中,那么别人要使用同一个试衣间都需要等到试衣间空闲的时候。互斥锁也是这个作用,当我们使用互斥锁对某一段代码加锁的后,如果另外的线程也要访问同一代码段的时候,它也使用同一个互斥锁去加锁,这个时候发现已经有其他线程加锁了,那么就一直等待,直到其他线程完成解锁操作以后,这个线程才可能加锁成功,进行相关代码段的访问。
参见下面代码片段:
#include<thread>
#include<mutex>
void myfunc2()
{
for(int i=0;i<20;i++)
{
data_mutex.lock();
global_value++;
data_mutex.unlock();
cout<<"t2:"<<global_value<<endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
c++ 里面申明mutex对象:
std::mutex data_mutex;
加锁操作:data_mutex.lock()
解锁操作:data_mutex.unlock()
这个操作需要lock和unlock配对操作,否则可能导致永远无法加锁成功。
可以简化加锁和解锁的配对操作,使用c++提供的另外一个包装类:std::lock_guard
使用std::lock_guard 改造上面的例子:
#include<thread>
#include<mutex>
void myfunc2()
{
for(int i=0;i<20;i++)
{
std::lock_guard<std::mutex> guard(data_mutex);
global_value++;
cout<<"t2:"<<global_value<<endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
可以看出我们不用手动调用unlock函数,当每次for循环一次完成的时候,程序会自动解锁。
使用std::lock_guard对象可以有效避免忘了unlock导致的问题。
OK,下一节我们继续讲解其他的数据同步方式。