多线程编程环境下,保护共享数据很重要。最简单的方式就是给需要保护的数据加锁,CPlusPlus实现加锁的工具是mutex类。在同一时刻,多个线程可以给同一个mutex对象加锁,但只有一个线程可以加锁成功,也就是说同一时刻只有一个线程对共享数据进行操作,其它没有加锁成功的线程则会阻塞等待,并不断尝试为该对象加锁。
在定义了mutex对象后,通常使用lock()和unlock()函数实现加解锁:
#include<mutex>
std::mutex m_mutex;
mutex.lock(); //加锁
mutex.unlock(); //解锁
lock()和unlock()成对出现,类似于new和delete。但为了防止忘记解锁的情况,CPlusPlus提供了lock_guard类模板:
std::lock_guard<std::mutex>lg_mutex(m_mutex);
在实例化lg_mutex对象时,系统在lock_guard的构造函数中为m_mutex互斥量加锁,相当于m_mutex.lock(),当超出lg_mutex生命周期后,系统调用lock_guard的析构函数为m_mutex解锁,相当于m_mutex.unlock()。
lock_guard类模板可以有第二个参数adopt_lock:
std::lock_guard<std::mutex>lg_mutex1(m_mutex,std::adopt_lock);
该参数起到标记作用,表示m_mutex之前已经被锁上了。
使用lock_guard类模板加锁互斥量不够灵活,因为不能手动解锁相关互斥量,故CPlusPlus提供了另一个类模板unique_lock,其完全可以替代lock_guard,但会牺牲一些运行效率和内存。
unique_lock类模板也可以有第二个参数:
std::unique_lock<std::mutex>ul1(m_mutex,std::adopt_lock);
std::unique_lock<std::mutex>ul2(m_mutex,std::try_to_lock);
std::unique_lock<std::mutex>ul3(m_mutex,std::defer_lock);
std::adopt_lock也是用来标记m_mutex已经被加锁了;std::try_to_lock则表示如果无法锁定该互斥量,也会立刻返回,而不会一直阻塞,可以腾出时间做其它的事情;std::defer_lock则表示延迟加锁,并在后续合适的地方再加锁,故unique_lock定义了一些成员函数:
ul3.lock(); //给m_mutex加锁,如果不成功则阻塞等待
ul3.try_lock(); //尝试给m_mutex加锁,如果不成功则立即返回false
ul3.release(); //相当于释放ul3对m_mutex的管理权
ul3.unlock(); //手动给m_mutex解锁
unique_lock对互斥量的管理权类似于unique_ptr对指针的所有权,也可以进行传递:
std::unique_lock<std::mutex>ul1(m_mutex);
std::unique_lock<std::mutex>ul2(std::move(ul1)); //ul1与m_mutex无关了