mutex又称互斥量,C++ 11中与 mutex相关的类(包括锁类型)和函数都声明在#include头文件中,所以如果你需要使用 std::mutex,就必须包含#include头文件。
C++11提供如下4种语义的互斥量(mutex) :
std::mutex,独占的互斥量,不能递归使用。
std::time_mutex,带超时的独占互斥量,不能递归使用。
std::recursive_mutex,递归互斥量,不带超时功能。
std::recursive_timed_mutex,带超时的递归互斥量。
std::mutex 是C++11 中最基本的互斥量,std::mutex 对象提供了独占所有权的特性,即不支持递归地对 std::mutex 对象上锁,而 std::recursive_lock 则可以递归地对互斥量对象上锁。
#include
#include
#include
#define TRY_MUTEX 0
#define MY_MUTEX 1
volatile int counter(0); // non-atomic counter
std::mutex mtx;
void increases10k()
{
for(int i=0;i<10000;i++)
{
#if TRY_MUTEX
if(mtx.try_lock())
{
++counter;
mtx.unlock();
}
#elif MY_MUTEX
mtx.lock();
++counter;
mtx.unlock();
#endif
}
}
int main(int argc,char **argv)
{
std::thread threads[10];
for(int i=0;i<10;i++)
{
threads[i]=std::thread(increases10k);
}
for(auto& th:threads)
th.join();
std::cout << " successful increases of the counter " << counter <<std::endl;
return 0;
}
编译:
g++ -o mutex_test mutex_test.c -std=c++11 -lpthread
执行效果:
# try_lock
$ ./mutex_test
successful increases of the counter 53612
# lock
$ vim mutex_test.c
successful increases of the counter 100000
可以看到try_lock只是尝试加锁,不管是否成功都不阻塞,而lock如果加锁失败会一直阻塞直到加锁成功。
相对于手动lock和unlock,可以使用RAII(通过类的构造析构)来实现更好的编码方式。 RAII:也称为“资源获取就是初始化”,是c++等编程语言常用的管理资源、避免内存泄露的方法。它保证 在任何情况下,使用对象时先构造对象,最后析构对象。
#include
#include
#include
#include
std::mutex mtx;
void print_event(int x)
{
if(x%2==0)
std::cout << x << " is even\n";
else
throw (std::logic_error("not even"));
}
void print_thread_id(int id)
{
try{
std::unique_lock<std::mutex> lck(mtx);
print_event(id);
}
catch(std::logic_error&)
{
std::cout << "[exception caught]\n";
}
}
int main(int argc,char **argv)
{
std::thread threads[10];
for(int i=0;i<10;i++)
{
threads[i]=std::thread(print_thread_id,i+1);
}
for (auto& th : threads) th.join();
return 0;
}
编译和执行:
$ g++ -o mutex_test lock_test.cc -std=c++11 -lpthread
$ ./mutex_test
[exception caught]
6 is even
[exception caught]
4 is even
[exception caught]
2 is even
[exception caught]
8 is even
[exception caught]
10 is even
这里的unique_lock换成lock_guard是一样的。
#include
#include
#include
#include
#include
#include
std::deque<int> q;
std::mutex mtx;
std::condition_variable cond;
int count=0;
void func1()
{
while(true)
{
// {
std::unique_lock<std::mutex> locker(mtx);
q.push_front(count++);
locker.unlock();//如果是lock_guard,不支持手动解锁
cond.notify_one();
sleep(1);
}
}
void func2()
{
while(true)
{
std::unique_lock<std::mutex> locker(mtx);
cond.wait(locker,[](){return !q.empty();});
auto data=q.back();
q.pop_back();
std::cout << "thread2 get value form thread1: " << data << std::endl;
}
}
int main(int atgc,char ** argv)
{
std::thread t1(func1);
std::thread t2(func2);
t1.join();
t2.join();
return 0;
}
条件变量的目的就是为了,在没有获得某种提醒时长时间休眠; 如果正常情况下, 我们需要一直循环 (+sleep), 这样的问题就是CPU消耗+时延问题,条件变量的意思是在cond.wait这里一直休眠直到 cond.notify_one唤醒才开始执行下一句; 还有cond.notify_all()接口用于唤醒所有等待的线程。
那么这里为什么必须使用unique_lock呢? 原因是条件变量在wait时会进行unlock再进入休眠, lock_guard并无该操作接口。
lock_guard:
std::unique_lock:
虽然递归锁能解决这种情况的死锁问题,但是尽量不要使用递归锁,主要原因如下:
递归锁允许同一个线程多次获取该互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁的问题。
死锁示例:
#include
#include
#include
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(void)
{
Complex complex;
complex.both(32, 23);
return 0;
}
运行后出现死锁的情况。在调用both时获取了互斥量,在调用mul时又要获取互斥量,但both的并没有 释放,从而产生死锁。
使用递归锁解决同一个线程的死锁问题:
#include
#include
#include
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(void)
{
Complex complex;
complex.both(32, 23); //因为同一线程可以多次获取同一互斥量,不会发生死锁
std::cout << "main finish\n";
return 0;
}
std::timed_mutex比std::mutex多了两个超时获取锁的接口:try_lock_for和try_lock_until。
#include
#include
#include
#include
std::timed_mutex mutex;
void work()
{
std::chrono::milliseconds timeout(100);
while (true)
{
if (mutex.try_lock_for(timeout))
{
std::cout << std::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 << std::this_thread::get_id() << ": do work without the mutex" << std::endl;
std::chrono::milliseconds sleepDuration(100);
std::this_thread::sleep_for(sleepDuration);
}
}
}
int main(void)
{
std::thread t1(work);
std::thread t2(work);
t1.join();
t2.join();
std::cout << "main finish\n";
return 0;
}
互斥锁mutex是为了临界资源的安全访问,C++11的unique_lock和lock_guard可以防止释放锁导致死锁问题。一般工作中比较少用recursive_mutex、std::timed_mutex和 std::recursive_timed_mutex。