文章目录Mutex互斥量(互斥锁)概要1.Mutex系列类) 4种)std:mutex )此类表示普通互斥锁,不能递归使用。 std:recursive_mutex :此类表示递归(再入)互斥锁。 递归独占锁可以在同一线程中多次锁定,以获得对互斥锁对象的多层所有权。 std:time_mutex :此类表示计时器(有限时间)独占锁,不能递归使用。 STD :3360 recursive _ timed _ mutex :带计时器的递归互斥。 2.Lock类(四种)一:lock_guard (锁定保护(二) scoped_lock )范围)三) unique_lock (独占,唯一)四) shared_lock
Mutex排他量(排他锁)简介
C11标准库头文件中定义了独占访问的类和方法等。 mutex也称为排他量。
1.Mutex系列类(四种)std:mutex )此类表示普通互斥锁,不能递归使用。 std:mutex是C 11最基本的互斥量,用于保护共享数据免受多个线程同时访问的同步原语的影响。STD:3360mutex对象提供独占(独占)所有权的特性,不支持递归锁定STD33603360mutex对象
std::mutex 的成员函数
(1).构造函数和析构函数:
std::mutex 不允许拷贝构造,也不允许 move 拷贝,第一个生成的mutex对象处于未锁定状态。 std:~mutex )放弃互斥锁。 如果独占锁被线程占用,或者线程在拥有独占锁的所有权时终止,则不会定义行为。
(2)加锁函数:lock(),调用线程锁定此排他量。 当线程调用此函数时,会出现以下三种情况:
.如果当前未锁定此排他量,则调用线程将锁定此排他量,并且在调用unlock之前,线程将一直保持锁定状态。
如果当前排他量被其他线程锁定,则当前调用线程将被阻止。
如果当前独占量被当前调用线程锁定,则会发生死锁(deadlock )。
(3) 尝试加锁 try_lock()尝试锁定排他量,如果排他量被其他线程占用,则当前线程也不会阻塞。 线程调用此函数也有以下三种情况:
当前排他量未被其他线程占用时,在该线程调用unlock释放排他量之前,该线程将锁定排他量。
如果当前排他量被其他线程锁定,当前调用的线程将返回false而不会被阻塞。
当前的排他量被当前的调用线程锁定时,会发生死锁(deadlock )
(4)解锁函数: unlock(),解除锁定,释放对互斥量的所有权。
std:mutex g_mtx; //创建程序的执行,开始全局生存期。 voidthread_fun({g_MTX.lock ); //所有权//g_mtx.unlock (; //释放所有权(} std:recursive_mutex )此类表示递归(再入)独占锁。 递归独占锁可以在同一线程中多次锁定,以获得对互斥锁对象的多层所有权。该类表示递归(再入)互斥锁。递归互斥锁可以被同一个线程多次加锁,以获得对互斥锁对象的多层所有权例如,如果同一个线程的多个函数访问临界区域,则可以分别锁定它们,然后在执行后分别解除锁定。 std:recursive_mutex要释放互斥量,必须调用与该锁定级别深度相同次数的unlock ()。 也就是说,lock ) )次数和unlock ) )次数相同。 这样,当线程申请递归排他锁时,如果该递归排他锁已经被当前调用线程锁定,就不会发生死锁。 此外,std:recursive_mutex的功能与std:mutex基本相同。
std:time_mutex :此类表示计时器(有限时间)独占锁,不能递归使用。std::timed_mutex 比 std::mutex 多了两个成员函数:
)1) try_lock_for ) )函数参数表示线程未锁定时保持阻塞的时间范围。 如果在此期间其他线程解锁,则该线程可以获得互斥锁。 如果超时(在指定的时间范围内没有获取锁),函数调用将返回false。
)2) try_lock_until ) :函数自变量表示某个时刻,在该时刻之前如果线程没有获得锁,则保持阻塞状态; 如果到目前为止其他线程解除了锁定,则该线程可以获得互斥锁。 如果超过指定时间未获得锁,则函数调用返回false
std:timed_mutex mtx; //(有限时间锁定) voidfireworks(charch ) { while (! MTX.try _ lock _ for (STD :3360 chrono : milliseconds (200 ) ) )//如果在200毫秒内无法获得锁,则返回false {
std::cout << ch; } std::this_thread::sleep_for(std::chrono::milliseconds(1000)); std::cout << "*n"; mtx.unlock(); } int main() { const int n = 3; std::thread threads[n]; char ch = 'A'; // spawn n threads: for (int i = 0; i < n; ++i) { threads[i] = std::thread(fireworks, ch + i); } for (auto& th : threads) { th.join(); } return 0;} ④ std::recursive_timed_mutex:带定时的递归互斥锁。 2.Lock 类(四种) 一 :lock_guard (锁保护):lock_guard 类不可复制(拷贝和赋值)。只有构造和析构函数。
类 lock_guard 是互斥体包装器,为在作用域块期间占有互斥提供便利 RAII 风格机制。创建 lock_guard 对象时,它试图接收给定互斥的所有权。控制离开创建 lock_guard 对象的作用域时,销毁 lock_guard 并释放互斥。不可用在函数参数传递或者返回过程,只能用在简单的临界区代码段的互斥操作中
scoped_lock 类不可复制(拷贝和赋值)。只有构造和析构函数。
类 scoped_lock 是提供便利 RAII 风格机制的互斥包装器,它在作用域块的存在期间占有一或多个互斥。创建scoped_lock 对象时,它试图取得给定互斥的所有权。控制离开创建 scoped_lock 对象的作用域时,析构 scoped_lock 并以逆序释放互斥。若给出数个互斥,则使用免死锁算法,如同以 std::lock 。
有锁定,修改和观察器函数。
类 unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和条件变量一同使用。类 unique_lock 可移动,但不可复制——它满足可移动构造 (MoveConstructible) 和可移动赋值 (MoveAssignable) 但不满足可复制构造 (CopyConstructible) 或可复制赋值 (CopyAssignable) 。他不仅可以使用在简单的临界区代码段的互斥操作中,还能用在函数调用过程中
有锁定,修改和观察器函数。
类 shared_lock 是通用共享互斥所有权包装器,允许延迟锁定、定时锁定和锁所有权的转移。锁定 shared_lock ,会以共享模式锁定关联的共享互斥( std::unique_lock 可用于以排他性模式锁定)。
shared_lock 类可移动,但不可复制——它满足可移动构造 (MoveConstructible) 与可移动赋值 (MoveAssignable) 的要求,但不满足可复制构造 (CopyConstructible) 或可复制赋值 (CopyAssignable) 。共享所有权模式等待于共享互斥,可使用 std::condition_variable_any ( std::condition_variable 要求std::unique_lock 故而只能以唯一所有权模式等待)。
std::lock_guard、scoped_lock,std::unique_lock 和 std::shared_lock 类模板在构造时是否加锁是可选的,
C++11 提供了 3 种加锁策略。
下表列出了互斥对象管理类模板对各策略的支持情况。
死锁是指两个或两个以上的进程(或线程)在运行过程中因争夺资源而造成的一种僵局(Deadly-Embrace) ) ,若无外力作用,这些进程(线程)都将无法向前推进。
std::mutex mtxa;std::mutex mtxb;int countA = 1000;int countB = 1000;void thread_func1(int money) // A ——-- > B{std::scoped_lock lk(mtxa, mtxb); //同时获得两把锁来解决死锁问题//unique_lock<std::mutex> lca(mtxa); //单用该锁会造成死锁cout << "线程1获得A账户的锁" << endl;std::this_thread::sleep_for(std::chrono::milliseconds(1000));if (countA >= money){cout << "线程1将要获得B账户的锁" << endl;//unique_lock<std::mutex> lcb(mtxb); //单用该锁会造成死锁countA -= money;countB += money;cout << "转账成功A --—-- > B: " << money << endl ;}else {cout << "A账号余额不足" << endl;}}void thread_func2(int money) // A ——-- > B{std::scoped_lock lk(mtxa, mtxb); //同时获得两把锁来解决死锁问题//unique_lock<std::mutex> lca(mtxb); //单用该锁会造成死锁cout << "线程2获得B账户的锁" << endl;std::this_thread::sleep_for(std::chrono::milliseconds(1000));if (countB >= money){cout << "线程2将要获得A账户的锁" << endl;//unique_lock<std::mutex> lcb(mtxa); //单用该锁会造成死锁countB -= money;countA += money;cout << "转账成功B --—-- > A: " << money << endl;}else {cout << "B账号余额不足" << endl;}}int main(){thread t1(thread_func1, 200);thread t2(thread_func2, 500);t1.join();t2.join();return 0;} 死锁产生的 4 个必要条件1、互斥: 某种资源一次只允许一个 线程 访问,即该资源一旦分配给某个 线程 ,其他 线程 就不能再访问,直到该 线程 访问结束。
2、占有且等待: 一个 线程 本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他 线程 释放该资源。
3、不可抢占: 线程已获得的资源,在末使用完之前,不能强行剥夺。
4、循环等待: 存在一个线程链,使得每个线程都占有下一个线程所需的至少一种资源。
当以上四个条件均满足,必然会造成死锁,发生死锁的进程无法进行下去,它们所持有的资源也无法释放。这样会导致 CPU 的吞吐量下降。所以死锁情况是会浪费系统资源和影响计算机的使用性能的。那么,解决死锁问题就是相当有必要的了。
避免死锁的方法a 、破坏“占有且等待”条件
b 、破坏“不可抢占”条件
c 、破坏“循环等待”条件