何时需要锁
一个函数能否被两个线程同时调用?
函数本身只是代码,代码是只读的,无论多少个线程同时调都无所谓。但是函数里面总要用到数据,如果数据属于线程(比如函数参数、局部变量,存在栈上,每个线程都有自己的栈),那么同时调还是没关系,因为是本线程的数据;但是如果用了一些全局数据,比如全局变量,同时操作一个数据结构,那就不行了,这时候这需要用到锁机制。
<mutex>:该头文件主要声明了与互斥量(mutex)相关的类,包括 std::mutex 系列类,std::lock_guard, std::unique_lock, 以及其他的类型和函数。
std::mutex
C++ 11提供了4中不同的锁,这里只介绍std::mutex。std::mutex只有几个成员函数,我们这里只需关注lock和unlock函数即可。
std::lock_guard
std::mutex的加锁和解锁相当简单,无需多言。但有一个bug:如果第一个的线程加锁后抛出异常,其申请的锁将无法得到解锁,其它线程就永远无法申请到锁。
std::lock_guard与mutex配合使用,把锁放到lock_guard中时,mutex自动上锁,lock_guard析构时,同时把mutex解锁。
#include <thread>
#include <mutex>
int g_i = 0;
std::mutex g_i_mutex;
void safe_increment()
{
std::lock_guard<std::mutex> lock(g_i_mutex);
++g_i;
// g_i_mutex is automatically released when lock
// goes out of scope
}
int main()
{
std::thread t1(safe_increment);
std::thread t2(safe_increment);
t1.join();
t2.join();
}
std::lock_guard是一个局部变量,创建时,g_i_mutex 上锁,析构时g_i_mutex解锁。
这个功能在函数体比较长,尤其是存在多个分支的时候很有用。
std::unique_lock
std::lock_guard完美展现了RAII,但std::lock_guard限制得太死了,只有构造和析构函数,没法通过它的成员函数加锁和解锁。为此,C++11提供了灵活的std:unique_lock模板类。std::unique_lock提供lock和unlock函数,因此可以在适当的时候加解锁。这样可以降低锁的粒度。
默认情况下,std::unique_lock的构造函数会对mutex进行加锁,在析构的时候会对mutex进行解锁。为了避免重复解锁,std::unique_lock内部会记录mutex的状态,在析构函数中根据mutex的状态决定是否需要解锁。
#include<thread>
#include<iostream>
#include<mutex>
std::mutex g_mutex;
void threadFunc(size_t num)
{
std::unique_lock<std::mutex> ul(g_mutex);
//do something
ul.unlock();//解锁,降低锁的粒度
//do something
ul.lock();
std::cout << "thread id " << std::this_thread::get_id() << std::endl;
//ul的析构函数会解锁
}
int main()
{
std::thread th1(threadFunc, 100);
std::thread th2(threadFunc, 200);
th1.join();
th2.join();
return 0;
}
std::lock
C++11提供了一个模板函数std::lock()使得很容易原子地对多个锁进行加锁。
template <class Mutex1, class Mutex2, class... Mutexes>
void lock (Mutex1& a, Mutex2& b, Mutexes&... cde);
std::lock函数只要求参数有lock操作即可,也就是说可以传一个std::mutex或者std::unique_lock变量给std::lock。std::lock_guard变量则不行,因为其没有lock()函数。
class BigData
{
public:
void swap(BigData &bg)
{
if (this == &bg)
return;
std::unique_lock<std::mutex> ul1(m_mutex, std::defer_lock);
std::unique_lock<std::mutex> ul2(bg.m_mutex, std::defer_lock);
std::lock(ul1, ul2);
m_vec.swap(bg.m_vec);
}
private:
std::mutex m_mutex;
std::vector<int> m_vec;
};
std::defer_lock是告诉std::unique_lock的构造函数,在构造函数不要给mutex上锁.
除了std::defer_lock,还可以用std::adopt_lock作为第二个参数。std::adopt_lock和std::defer_lock相反,它表示已经上锁了,不需要再加锁了。std::lock_guard也是支持std::adopt_lock的,但不支持std::defer_lock.