互斥量是一种同步原语,是一种线程同步的手段,用来保护多线程同时访问共享数据。C++11中提供如下4种语义的互斥量(mutex):
一般是通过lock()方法来阻塞线程,直到获得互斥量的所有权为止,当线程获得互斥量并完成任务之后,必须使用unlock()来解除对互斥量的占用。try_lock()尝试锁定互斥量,如果成功则返回true,否则返回false,它是非阻塞的。
//std::mutex基本用法
#include<iostream>
#include<thread>
#include<mutex>
#include<chrono>
std::mutex g_lock;
void func()
{
g_lock.lock();//lock
std::cout<<"entered thread "<<std::this_thread::get_id()<<std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout<<"leaving thread "<<std::this_thread::get_id()<<std::endl;
g_lock.unlock();//unlock
}
int main()
{
std::thread t1(func);
std::thread t2(func);
std::thread t3(func);
t1.join();
t2.join();
t3.join();
return 0;
}
结果输出(使用CLion 2018.1.5版本):
entered thread 2
leaving thread 2
entered thread 3
leaving thread 3
entered thread 4
leaving thread 4
lock_guard可以简化lock/unlock,因为lock_guard在构造时会自动锁定互斥量,在离开作用域后进行析构就会自动解锁。
lock_guard用到了RAII技术,这种技术在类的构造函数中分配资源,在析构函数中释放资源,保证资源在出了作用域之后就释放。
void func()
{
std::lock_guard::mutex> locker(g_lock);//出作用域后自动解锁
std::cout<<"entered thread "<::this_thread::get_id()<<std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout<<"leaving thread "<::this_thread::get_id()<<std::endl;
}
递归锁允许同一线程多次获得该互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁的问题。如下所示,一个线程多次获取同一个互斥量时会发生死锁:
struct Complex
{
std::mutex mutex;
int i;
Complex():i(0){}
void mul(int x)
{
std::lock_guard<std::mutex> lock(mutex);
i*=x;
}
void div(int x)
{
std::lock_guard<std::mutex> lock(mutex);
i/=x;
}
void both(int x,int y)
{
std::lock_guard<std::mutex> lock(mutex);//此处获取互斥量
mul(x);//再次获取互斥量就会发生死锁
div(y);
}
};
int main()
{
Complex complex;
complex.both(32,23);
return 0;
}
上面例子运行后就会发生死锁,因为在调用both时获取了互斥量,之后在调用mul又要获取相同的互斥量,但是这个互斥量已经被当前线程获取了,无法释放,就会发生死锁。解决这个死锁问题,一个简单的方法就是使用递归锁:std::recursive_mutex,它允许同一线程多次获得互斥量,代码如下:
struct Complex
{
std::recursive_mutex mutex;
int i;
Complex():i(0){}
void mul(int x)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
i*=x;
}
void div(int x)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
i/=x;
}
void both(int x,int y)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
mul(x);
div(y);
}
};
int main()
{
Complex complex;
complex.both(32,23);//因为同一线程可以多次获取同一互斥量,不会死锁
return 0;
}
注意:尽量不要使用递归锁,原因如下:
std::time_mutex是超时的独占锁,std::recursive_timed_mutex是超时的递归锁,主要用在获取锁时增加超时等待等功能,因为有时候不知道获取锁需要等多久,为了不至于一直在等待获取互斥量,就设置一个等待超时时间,在超时后还可以做其他事情。
std::time_mutex比std::mutex多了两个超时获取锁的接口:try_lock_for和try_lock_until,这两个接口用来设置获取互斥量的超时时间,使用时可以用一个while循环去不断地获取互斥量。std::time_mutex的基本用法如下:
std::time_mutex mutex;
void work()
{
std::chrono::milliseconds timeout(100);
while(true)
{
if(mutex.try_lock_for(timeout))
{
std::cout<::this_thread::get_id()<<": do work with the mutex"<<std::endl;
std::chrono::milliseconds sleepDuration(250);
std::this_thread::sleep_for(sleepDuration);
mutex.unlock();
std::this_thread::sleep_for(sleepDuration);
}
else
{
std::cout<::this_thread::get_id()<<": do work without mutex"<<std::endl;
std::chrono::milliseconds sleepDuration(100);
std::this_thread::sleep_for(sleepDuration);
}
}
}
int main()
{
std::thread t1(work);
std::thread t2(work);
t1.join();
t2.join();
return 0;
}
std::recursive_timed_mutex可以看做在std::recursive_timed_mutex的基础上加了超时功能。
声明:以上主要来源于深入应用C++11 这本书,强烈推荐