C++11中提供了std::mutex互斥量,共包含四种类型:ios
std::mutex:最基本的mutex类。
std::recursive_mutex:递归mutex类,能屡次锁定而不死锁。
std::time_mutex:定时mutex类,能够锁定必定的时间。
std::recursive_timed_mutex:定时递归mutex类。多线程
另外,还提供了两种锁类型:app
std::lock_guard:方便线程对互斥量上锁。函数
std::unique_lock:方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。性能
以及相关的函数:
std::try_lock:尝试同时对多个互斥量上锁。
std::lock:能够同时对多个互斥量上锁。
std::call_once:若是多个线程须要同时调用某个函数,call_once能够保证多个线程对该函数只调用一次。this
std::mutex.net
std::mutex是C++中最基本的互斥量,提供了独占全部权的特性,std::mutex提供了如下成员函数线程
构造函数:std::mutex不容许拷贝构造,也不容许move拷贝,最初产生的mutex对象是处于unlocked状态的。
lock():调用线程将锁住该互斥量,线程调用该函数会发生如下3种状况:
(1)若是该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用unlock以前,该线程一直拥有该锁。
(2)若是当前互斥量被其余线程锁住,则当前的调用线程被阻塞住。
(3)若是当前互斥量被当前调用线程锁住,则会产生死锁,,也就是说同一个线程中不容许锁两次。
unlock():解锁,释放对互斥量的全部权。
try_lock():尝试锁住互斥量,若是互斥量被其余线程占有,则当前线程也不会被阻塞,线程调用该函数会出现下面3种状况:
(1)若是当前互斥量没有被其余线程占有,则该线程锁住互斥量,直到该线程调用unlock释放互斥量。
(2)若是当前互斥量被其余线程锁住,则当前调用线程返回false,而并不会被阻塞掉。
(3)若是当前互斥量被当前调用线程锁住,则会产生死锁。code
int counter = 0; std::mutex mtx; void func() { for (int i = 0; i < 10000; ++i) { mtx.lock(); ++counter; mtx.unlock(); } } int main() { std::thread threads[10]; for (int i = 0; i < 10; ++i) { threads[i] = std::thread(func); } for (auto& th : threads) { th.join(); } std::cout << counter << std::endl; // 100000 return 0; }
以上代码实现了对counter的同步操做,同时只有一个线程能对counter操做。咱们再看一下try_lock的结果:对象
void func() { for (int i = 0; i < 10000; ++i) { if (mtx.try_lock()) { ++counter; mtx.unlock(); } } } int main() { std::thread threads[10]; for (int i = 0; i < 10; ++i) { threads[i] = std::thread(func); } for (auto& th : threads) { th.join(); } std::cout << counter << std::endl; // 不固定 return 0; }
能够看到,使用try_lock对counter进行控制,最终结果是不固定的。由于try_lock在其余线程占有互斥量的状况下会返回false可是不阻塞,因此会跳过一部分的++counter操做。
std::recursive_mutex
std::recursive_mutex与std::mutex相似,可是它可以进行屡次lock,这样可以规避一些死锁问题:
int counter = 0; std::recursive_mutex mtx; void func2() { mtx.lock(); counter++; mtx.unlock(); } void func1() { mtx.lock(); func2(); counter++; mtx.unlock(); } int main() { std::thread t(func1); t.join(); std::cout << counter << std::endl; // 2 return 0; }
如上面的代码所示,有时候会在两个函数中分别对数据进行lock,若是在一个函数中又调用了另外一个函数,此时若是使用std::mutex将会死锁,而用std::recursive_mutex则不会。看起来std::recursive_mutex很不错,可是使用的时候也须要多注意,lock和unlock的数量必须相等,不然会出错。
另外还有性能的问题,std::recursive_mutex的性能会比较差一些,从下面的例子中能够看出来,性能上要差了1倍左右。
int counter = 0; std::recursive_mutex rmtx; std::mutex mtx; int main() { auto begin = std::chrono::system_clock::now(); for (int i = 0; i < 10000; ++i) { mtx.lock(); counter++; mtx.unlock(); } auto end = std::chrono::system_clock::now(); std::cout << (end - begin).count() << std::endl; // 330 begin = std::chrono::system_clock::now(); for (int i = 0; i < 10000; ++i) { rmtx.lock(); counter++; rmtx.unlock(); } end = std::chrono::system_clock::now(); std::cout << (end - begin).count() << std::endl; // 597 return 0; }
std::time_mutex和std::recursive_timed_mutex
这两种互斥量类型和不带time的相比,多了两个成员函数:
try_lock_for():函数参数表示一个时间范围,在这一段时间范围以内线程若是没有得到锁则保持阻塞;若是在此期间其余线程释放了锁,则该线程可得到该互斥锁;若是超时(指定时间范围内没有得到锁),则函数调用返回false。
try_lock_until():函数参数表示一个时刻,在这一时刻以前线程若是没有得到锁则保持阻塞;若是在此时刻前其余线程释放了锁,则该线程可得到该互斥锁;若是超过指定时刻没有得到锁,则函数调用返回false。
首先来看看try_lock_for的用法,下面的例子能够看出,try_lock_for等待指定时间后没有获取到锁,会返回false。
#include <iostream> // std::cout #include <chrono> // std::chrono::milliseconds #include <thread> // std::thread #include <mutex> // std::timed_mutex std::timed_mutex mtx; void fireworks(int n) { // 为这个锁等待200ms while (!mtx.try_lock_for(std::chrono::milliseconds(200))) { std::string out = std::to_string(n); out += "wait\n"; std::cout << out; } // 获取锁后等待700ms再解锁 std::this_thread::sleep_for(std::chrono::milliseconds(700)); std::cout << "end" << std::endl; mtx.unlock(); } int main () { std::thread threads[3]; for (int i = 0; i < 3; ++i) { threads[i] = std::thread(fireworks, i); // 为了保证线程按照顺序开始,保证输出一致 std::this_thread::sleep_for(std::chrono::milliseconds(10)); } for (auto& th : threads) { th.join(); } return 0; } // 输出结果 1wait 2wait 1wait 2wait 1wait 2wait end 2wait 2wait 2wait end end
try_lock_until的做用相似,不一样的是传入参数是时间点:
void fireworks(int n) { auto now = std::chrono::steady_clock::now(); while (!mtx.try_lock_until(now + std::chrono::milliseconds(200))) { std::string out = std::to_string(n); out += "wait\n"; std::cout << out; now = std::chrono::steady_clock::now(); } std::this_thread::sleep_for(std::chrono::milliseconds(700)); std::cout << "end" << std::endl; mtx.unlock(); }
std::recursive_timed_mutex和std::recursive_mutex的区别也是同样的,多了两个成员函数。
std::lock_guard和std::unique_lock
std::lock_guard是一个模板类,模板类型能够是以上的四种锁,用于自动锁定解锁,直到对象做用域结束。
std::mutex mtx; int main () { { std::lock_guard<std::mutex> guard(mtx); std::cout << mtx.try_lock() << std::endl; // 0,互斥量被占用 } std::cout << mtx.try_lock() << std::endl; // 0,互斥量释放 mtx.unlock(); return 0; }
std::unique_lock也支持std::lock_guard的功能,可是区别在于它提供跟多的成员函数使用更加灵活,而且可以和condition_variable一块儿使用来控制线程同步,内容较多之后有机会在作介绍,有兴趣的朋友能够本身了解。
std::try_lock、std::lock、std::call_once
std::try_lock支持尝试对多个互斥量进行锁定,尝试锁定成功返回-1,不然返回锁定失败的互斥量的位置,例如第一个锁定失败返回0、第二个失败返回1。
int main () { std::mutex mtx1; std::mutex mtx2; if (-1 == std::try_lock(mtx1, mtx2)) { std::cout << "locked" << std::endl; mtx1.unlock(); mtx2.unlock(); } return 0; }
std::lock支持对多个锁锁定,而且避免死锁的出现,如下代码运行时有可能出现死锁的状况:
void func(std::mutex* mtx1, std::mutex* mtx2, int index) { std::lock_guard<std::mutex> lock1(std::adopt_lock); std::lock_guard<std::mutex> lock2(std::adopt_lock); std::cout << index << "out\n"; } int main () { std::mutex mtx1; std::mutex mtx2; // 两个线程的互斥量锁定顺序不一样,可能形成死锁 std::thread t1(func, &mtx1, &mtx2, 1); std::thread t2(func, &mtx2, &mtx1, 2); t1.join(); t2.join(); return 0; }
而用std::lock能避免多个锁出现死锁:
void func(std::mutex* mtx1, std::mutex* mtx2, int index) { std::lock(*mtx1, *mtx2); // 同时锁定 // std::adopt_lock做用是声明互斥量已在本线程锁定,std::lock_guard只是保证互斥量在做用域结束时被释放 std::lock_guard<std::mutex> lock1(*mtx1, std::adopt_lock); std::lock_guard<std::mutex> lock2(*mtx2, std::adopt_lock); // 等价方法: //std::unique_lock<std::mutex> lock1(from.m, std::defer_lock); //std::unique_lock<std::mutex> lock2(to.m, std::defer_lock); //std::lock(lock1, lock2); std::cout << index << "out\n"; } int main () { std::mutex mtx1; std::mutex mtx2; std::thread t1(func, &mtx1, &mtx2, 1); std::thread t2(func, &mtx2, &mtx1, 2); t1.join(); t2.join(); return 0; }
std::call_once的做用是即便在多线程的状况下,也只执行一次指定的可调用对象(能够是函数、成员函数、函数对象、lambda函数),须要经过配合std::once_flag实现。具体的细节以下:
若在调用 call_once 的时刻, flag 指示已经调用了f指定的可调用对象,则 call_once 当即返回,就是说再也不执行可调用对象。
不然,call_once 会调用指定的可调用对象。若该调用对象抛异常,则传播异常给 call_once 的调用方,而且不翻转 flag ,让下一次调用仍然执行。若该调用正常返回,则翻转 flag ,并保证同一 flag不在执行可调用对象。
std::once_flag flag1, flag2; void simple_do_once() { std::call_once(flag1, [](){ std::cout << "Simple example: called once\n"; }); } void may_throw_function(bool do_throw) { if (do_throw) { std::cout << "throw: call_once will retry\n"; // this may appear more than once throw std::exception(); } std::cout << "Didn't throw, call_once will not attempt again\n"; // guaranteed once } void do_once(bool do_throw) { try { std::call_once(flag2, may_throw_function, do_throw); } catch (...) { } } int main() { std::thread st1(simple_do_once); std::thread st2(simple_do_once); std::thread st3(simple_do_once); std::thread st4(simple_do_once); st1.join(); st2.join(); st3.join(); st4.join(); std::thread t1(do_once, true); std::thread t2(do_once, true); std::thread t3(do_once, false); std::thread t4(do_once, true); t1.join(); t2.join(); t3.join(); t4.join(); } // 输出结果: Simple example: called once throw: call_once will retry throw: call_once will retry Didn't throw, call_once will not attempt again