01 简介
互斥量是一种同步原语,是
一种线程同步的手段,用于保护多线程同时访问的共享数据
。
C++11 头文件 <
mutex
> 包含:
-
Mutex 类系列
-
std::mutex
独占的互斥量,不能递归使用。 -
std::timed_mutex
带超时的独占互斥量,不能递归使用。 -
std::recursive_mutex
递归互斥量。 -
std::recursive_timed_mutex
带超时的递归互斥里昂。
-
-
Lock 类
-
std::lock_guard
与 Mutex RAII 相关,方便线程对互斥量上锁。 -
std::unique_lock
与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。
-
-
其它类型
-
std::once_flag
-
std::adopt_lock_t
-
std::defer_lock_t
-
std::try_to_lock_t
-
-
函数
-
std::try_lock
尝试同时对多个互斥量上锁。 -
std::lock
可以同时对多个互斥量上锁。 -
std::call_once
如果多个线程需要同时调用某个函数,call_once 可以保证多个线程对该函数只调用一次。
-
02 Mutex 类
2.1 std::mutex
互斥量是用于
保护共享数据免受从多个线程同时访问
的同步对象。
互斥量的
基本用法
如下:
-
lock
该方法阻塞线程,直到获得互斥量的所有权位置。 -
unlock
该方法用于在获得互斥量并执行完任务之后除对互斥量的占用。 -
try_lock
该方法尝试锁定互斥量,如果成功返回 true, 失败返回 false, 它并不会阻塞线程。线程调用 try_lock 方法会出现如下 3 种情况:- 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量。
- 如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉。
- 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。
lock 和 unlock 方法必须成对使用。
-
成员函数
lock unlock try_lock
-
注意事项
- lock 和 unlock 必须成对使用。
-
lock 会
阻塞线程
直到获的互斥量;而 try_lock 返回 false 时不会线程
不会被阻塞
。 -
使用独占的互斥量 std::mutex 可能会出现死锁现象:如使用 lock 对已经上锁的互斥量上锁,因此
不建议直接使用 lock, unlock
后续会介绍更好控制上锁解锁行为的
std::lock_guard
,
std::unique_lock
。
-
应用实例
/*************************************************************** * lock * unlock * ************************************************************/ int count{ 0 }; std::mutex mtx01; void printNum(int num) { mtx01.lock(); // 临界区独占 std::cout std::cout << count++ << ": " << num << std::endl; mtx01.unlock(); } std::thread th01(printNum, 10); std::thread th02(printNum, 15); th01.join(); th02.join(); /* // --------------------------------------------------------- // output // --------------------------------------------------------- 0: 10 1: 15 */ /*************************************************************** * try_lock * ************************************************************/ volatile int index{ 0 }; std::vector<int> numVec{ 1, 2, 3, 4, 5 }; std::mutex mtx01; void printNum(int thIndex, int numIndex, int sleepTime) { std::this_thread::sleep_for(std::chrono::seconds(sleepTime)); if (mtx01.try_lock()) { std::cout << index << ": thread0" << thIndex << " read num " << numVec[numIndex] << std::endl; ++index; mtx01.unlock(); } } std::vector<std::thread> thVec(5); for (int i = 0; i < thVec.size(); ++i) { thVec[i] = std::thread(printNum, i + 1, i, 5 - i); } for (auto& th : thVec) { th.join(); } /* // --------------------------------------------------------- // output // --------------------------------------------------------- 0: thread05 read num 5 1: thread04 read num 4 2: thread03 read num 3 3: thread02 read num 2 4: thread01 read num 1 */
2.2 std::recursive_mutex
std::recursive_mutex 允许
同一线程获得该互斥锁
,可以用于解决同一线程多次获取互斥量时的死锁问题。
std::recursive_mutex 在线程种使用 lock 和 unlock 的次数必须相同。
std::recursive_mutex recMtx01;
void printNum01(const int n)
{
recMtx01.lock();
std::cout << n << std::endl; // 10
recMtx01.unlock();
}
void threadFunc01(int n)
{
recMtx01.lock();
printNum01(n);
recMtx01.unlock();
}
std::thread th01(threadFunc01, 10);
th01.join();
2.3 std::timed_mutex & std::recursive_timed_mutex
程序在实际运行种有时不知道获取锁需要多久,为了不至于一直等待获取互斥量就设置一个等待超时时间。
带超时时间的互斥量增加了两个方法:
try_lock_for // 尝试锁定互斥,若互斥在指定的时限中不可用则返回
try_lock_until // 尝试锁定互斥,若直至抵达指定时间点互斥不可用则返回
应用实例如下:
/***************************************************************
* try_lock_for
* ************************************************************/
volatile int index{ 0 };
std::vector<int> numVec{ 1, 2, 3, 4, 5 };
std::timed_mutex tmdMtx01;
void printNum(int thIndex, int numIndex)
{
// 循环尝试锁定互斥量,每次尝试的超时时间为 0.5s
while (!tmdMtx01.try_lock_for(std::chrono::milliseconds(500)))
{
// 等待 1s 之后重新尝试
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
// 互斥量上锁成功之后执行临界区的操作
std::cout << index++ << ": thread0" << thIndex << " read num " << numVec[numIndex] << std::endl;
// 任务执行完成之后记得解锁互斥量
tmdMtx01.unlock();
}
std::vector<std::thread> thVec(5);
for (int i = 0; i < thVec.size(); ++i)
{
thVec[i] = std::thread(printNum, i + 1, i);
}
for (auto& th : thVec)
{
th.join();
}
/*
// ---------------------------------------------------------
// output
// ---------------------------------------------------------
0: thread03 read num 3
1: thread01 read num 1
2: thread02 read num 2
3: thread04 read num 4
4: thread05 read num 5
*/
/***************************************************************
* try_lock_until [参见 std::this_thread::sleep_until]
* ************************************************************/
03 Lock 类
3.1 lock_guard
lock_guard 是互斥体包装器: 创建 lock_guard 对象时,它试图接收给定互斥的所有权。控制离开创建 lock_guard 对象的作用域时,销毁 lock_guard 并释放互斥(RAII)。
lock_guard 特点如下:
- 创建即加锁,作用域结束之后自动析构并解锁,不需要手动上锁解锁。
- 在 lock_guard 对象作用域内不能对互斥量解锁。
- 不能进行赋值相关的操作,但是可以进行移动相关的操作。
int count{ 0 };
std::mutex mtx01;
void printNum(int num)
{
// lock_guard 构造的时获取互斥量
std::lock_guard<std::mutex> glckMtx01(mtx01);
// 临界区独占 std::cout
std::cout << count++ << ": " << num << std::endl;
// lock_guard 离开作用域析构时解锁互斥量
}
std::thread th01(printNum, 10);
std::thread th02(printNum, 15);
th01.join();
th02.join();
/*
// ---------------------------------------------------------
// output
// ---------------------------------------------------------
0: 10
1: 15
*/
3.2 unique_lock
unique_lock 是一个通用的互斥量锁定包装器:它允许
延迟锁定
,
限时深度锁定
,
递归锁定
,
锁定所有权的转移
以及
与条件变量一起使用
。
unique_lock 是 lock_guard 的增强版,它具有 lock_guard 的所有功能,同时又支持上锁、解锁操作。
unique_lock 特点如下:
-
创建时可以通过指定第二个参数实现之创建而不锁定互斥量。
-
可以随时执行上锁、解锁操作。
-
unique_lock 对象析构时和 lock_guard 操作相似,也会自动解锁。
-
不能进行赋值相关的操作,但是可以进行移动相关的操作。
-
条件变量需要 unique_lock 类型的锁作为参数。
-
成员函数
(constructor) (destructor) lock try_lock try_lock_for try_lock_until unlock operator= swap // 与另外一个 std::unique_lock 交换状态 release // 释放关联互斥量但是不解锁它 owns_lock // 检查锁是否占有其相关互斥 operator bool // 检查锁是否占有其相关互斥 mutex // 返回指向互斥量的指针
-
应用实例
-
构造函数
-
default
unique_lock() noexcept;
新创建的 unique_lock 对象不管理任何 mutex 对象。
-
locking
explicit unique_lock(mutex_type& m);
新创建的 unique_lock 对象管理 mutex 对象 m, 并尝试调用 m.lock() 对 mutex 对象进行上锁。如果此时某一个 unique_lock 已经管理了该 mutex 对象的 m 则当前线程将会被阻塞。
-
try locking
unique_lock(mutex_type& m, try_to_lock_t tag);
新创建的 unique_lock 对象管理 mutex 对象 m, 并尝试调用 m.try_lock() 对 mutex 对象进行上锁。如果上锁不成功将不会阻塞当前线程。
-
deferred
unique_lock(mutex_type& m, defer_lock_t tag) noexcept;
新创建的 unique_lock 对象管理 mutex 对象 m, 但是在初始化时并不对 mutex 对象上锁, m 应该是一个没有被当前线程锁住的 mutex 对象。
-
adopting
unique_lock(mutex_type& m, adopt_lock_t tag);
新创建的 unique_lock 对象管理 mutex 对象 m, m 应该是一个已经被当前线程锁住的 mutex 对象。
-
locking for
template <class Rep, class Period> unique_lock(mutex_type& m, const chrono::duration<Rep,Period>& rel_time);
新创建的 unique_lock 对象管理 mutex 对象 m, 并试图通过调用 m.try_lock_for(rel_time) 来锁住 mutex 对象 rel_time 长度的时间。
-
locking until
template <class Clock, class Duration> unique_lock(mutex_type& m, const chrono::time_point<Clock,Duration>& abs_time);
新创建的 unique_lock 对象管理 mutex 对象 m, 并试图通过调用 m.try_lock_until(abs_time) 在时间点 abs_time 之前锁住 mutex 对象。
-
copy [deleted]
-
move (9)
unique_lock(unique_lock&& x);
新创建的 unique_lock 对象获得了由 x 所管理的 mutex 对象的所有权(包括当前 mutex 对象的状态)。调用 move 构造之后, x 对象如同通过默认构造函数所创建的,就不再管理任何 mutex 对象了。
/*************************************************************** * (constructor) * lock * unlock * try_lock * try_lock_for * operator= * ************************************************************/ std::mutex mtx01, mtx02, mtx03, mtx04; std::timed_mutex tmtx01, tmtx02; void func01() { // std::unique_lock<std::mutex> ulck01; std::unique_lock<std::mutex> ulck02(mtx01); // std::unique_lock<std::mutex> ulck03(ulck02); // error: 拷贝构造被禁止 // std::unique_lock<std::mutex> ulck04 = ulck02; // error: 赋值操作被禁止 std::unique_lock<std::mutex> ulck06(std::move(ulck02)); // ok: 移动构造 std::unique_lock<std::mutex> ulck07; ulck07 = std::move(ulck02); // ok: 移动赋值 // 构造时尝试调用 mtx02.try_lock() 对互斥量 mtx02 上锁,如果上锁失败不会阻塞线程 std::unique_lock<std::mutex> ulck08(mtx02, std::try_to_lock); // 构造时尝试不对互斥量 mtx03 上锁 std::unique_lock<std::mutex> ulck09(mtx03, std::defer_lock); mtx04.lock(); // 使用 std::adopt_lock 时应该提前上锁互斥量 // 构造时尝试不对互斥量 mtx04 上锁,但是在此之前当前线程应该已经对互斥量 mtx04 成功上锁 std::unique_lock<std::mutex> ulck10(mtx04, std::adopt_lock); ulck09.try_lock(); // 使用 std::defer_lock 时应该在后续语句中对互斥量上锁 // try_lock_for std::unique_lock<std::timed_mutex> ulck11(tmtx01, std::defer_lock); // 循环尝试对互斥量 mtx05 上锁每次尝试超时时间为 0.5s 直到上锁成功 while (!ulck11.try_lock_for(std::chrono::milliseconds(500))) { // 等待 1s 之后重新尝试 std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } } std::thread th01(func01); th01.join();
-
-
其它成员
// 暂时没有好的代码作为示例
-