mutex是多线程编程时经常用到的,在C++11中需要包含<mutex>模块。而在该文件中还有其他和mutex协作的类和函数,使得多线程编程时非常方便。
1 mutex类
mutex对象是一个lockable的对象,当关键区域需要被互斥访问的时候被用来当作信号。mutex对象提供互斥的拥有权限,并且不支持递归。
实际就是mutex是用来进行线程同步的。常用的成员函数有lock()和unlock()
lcok()
调用该函数后,调用函数会锁定mutex,在有些情况下调用函数会阻塞。
1、如果mutex当前没有被任何其他线程locked,则调用线程lock这个mutex对象(从此刻到直到其成员函数unlock被调用,当前线程拥有mutex对象)。
2、如果mutex目前被其他线程locked,则当前线程阻塞直到mutex被其他线程unlock。
3、如果mutex目前被当前线程lock,则会产生死锁错误。大部分情况会崩溃,因为mutex不支持递归。
unlock()
unlock mutex对象,释放mutex对象的所有权。通常和lock成对使用。
调用该函数后,如果有其他线程因为lock同一个mutex对象而阻塞,则他们中的一个会获得mutex的所有权,从而继续执行。
使用案例如下:
不使用mutex同步的情况下:
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
using namespace std;
void fn1()
{
for (int i = 0; i < 5; i++)
{
cout << "线程" << this_thread::get_id() << ":" << "The thread1 is running !" << endl;
}
}
void fn2()
{
for (int i = 0; i < 5; i++)
{
cout << "线程" << this_thread::get_id() << ":" << "The thread2 is running !" << endl;
}
}
int main()
{
thread t1(fn1);
thread t2(fn2);
t1.detach();
t2.detach();
this_thread::sleep_for(chrono::milliseconds(1000));
getchar();
return 0;
}
因为cout是标准输出的接口,所以两个线程同时输出的时候会对cout进行抢占,而两个线程并没有对cout进行同步,所以可能会出现输出错乱的情况。输出结果如下:
从上图可以看出输出比较杂乱的。
使用mutex对cout进行互斥访问,如下:
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
using namespace std;
mutex mtx;
void fn1()
{
for (int i = 0; i < 5; i++)
{
mtx.lock();
cout << "线程" << this_thread::get_id() << ":" << "The thread1 is running !" << endl;
mtx.unlock();
}
}
void fn2()
{
for (int i = 0; i < 5; i++)
{
mtx.lock();
cout << "线程" << this_thread::get_id() << ":" << "The thread2 is running !" << endl;
mtx.unlock();
}
}
int main()
{
thread t1(fn1);
thread t2(fn2);
t1.detach();
t2.detach();
this_thread::sleep_for(chrono::milliseconds(1000));
getchar();
return 0;
}
输出如下:
从上图中可以看出,输出比较工整了,因为我们对cout作了访问保护,当一个线程输出的时候,另一个线程是不能输出的,只有当一个线程输出完毕了,另一个线程才能输出。而这一切是通过mutex来实现的。
除了常用的lock()和unlock()函数之外,还有一个函数可能会用到,那就是 try_lock()
try_lock()
试图去lock mutex对象,但是不会产生阻塞,通常有一下集中情况。
1、如果mutex没有被任何线程lock,则调用线程会lock
2、如果mutex被其他线程lock,则该函数会失败,返回false,但是不会造成阻塞。
3、如果mutex被与调用该函数的线程相同的线程lock,则会产生一个死锁,因为mutex不支持递归,一般情况下会崩溃。
2 unique_lock
在<mutex>文件中除了经常使用的mutex外还有一些其他的与mutex写作的类和函数,其中用的比较多的是unique_lock。
一个unique_lock对象管理一个mutex对象,并且在locked和unlocked两种状态下拥有该对象的唯一所有权。
在构造unique_lock对象时,获取一个mutex对象,并负责该mutex对象的locking和unlocking操作。
unique_lock类保证在析构是unlcok mutex对象(尽管不通过显式调用)。有一点要区分清楚的是unique_lock对象不会管理mutex对象的生命周期,对mutex的所有权一直持续到unique_lock对象被析构。
示例代码如下:
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
using namespace std;
mutex mtx;
void fn1()
{
for (int i = 0; i < 5; i++)
{
unique_lock<mutex> lock(mtx);
cout << "线程" << this_thread::get_id() << ":" << "The thread1 is running !" << endl;
}
}
void fn2()
{
for (int i = 0; i < 5; i++)
{
unique_lock<mutex> lock(mtx);
cout << "线程" << this_thread::get_id() << ":" << "The thread2 is running !" << endl;
}
}
int main()
{
thread t1(fn1);
thread t2(fn2);
t1.detach();
t2.detach();
this_thread::sleep_for(chrono::milliseconds(1000));
getchar();
return 0;
}
输出结果如下: