互斥
std::mutex(c++11)
作用:互斥锁,提供一种原子操作,保护共享数据被多个线程访问的安全性
#include <mutex>
std::mutex mutex;
{
std::lock_guard<std::mutex> lock(mutex);
// operate data
};
成员函数
lock 锁定互斥,若互斥不可用则阻塞
try_lock 尝试锁定互斥,若互斥不可用则返回
unlock 解锁互斥
note:
一般不推荐直接使用其成员函数实现代码逻辑,推荐使用std::unique_lock、std::lock_guard 或 std::scoped_lock (C++17 起) 以更加异常安全的方式管理锁定。
std::lock_guard 是一种RAII风格的机制,它可以利用变量生命周期的作用域机制,在构造函数中实现mutex.lock(),析构函数中实现mutex.unlock()
std::timed_mutex(c++11)
作用:同mutex作用一样,只不过增加定时功能,尝试锁定互斥,若互斥在指定的时限段或时间点中不可用则返回false, 否则返回true
成员函数
lock 锁定互斥,若互斥不可用则阻塞
try_lock 尝试锁定互斥,若互斥不可用则返回[立即返回]
try_lock_for 尝试锁定互斥,若互斥在指定的时限时期中不可用则返回[时间段]
try_lock_until 尝试锁定互斥,若直至抵达指定时间点互斥不可用则返回[时间点]
unlock 解锁互斥
使用方法
if(mutex.try_lock_for(100ms)){
// do something
mutex.unlock();
}else{
std::cout<<"failed";
}
std::recursive_mutex(c++11)
作用:递归锁,允许同一个线程多次获取该互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁的问题。
成员函数
lock 锁定互斥,若互斥不可用则阻塞
try_lock 尝试锁定互斥,若互斥不可用则返回
unlock 解锁互斥
使用场景:在类的成员函数可能相互调用的可能情况下,保护类中的共享数据,防止死锁
#include <iostream>
#include <thread>
#include <mutex>
struct Complex
{
std::recursive_mutex mutex;
int i;
Complex() : i(0){}
void mul(int x)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
i *= x;
}
void div(int x)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
i /= x;
}
void both(int x, int y)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
mul(x);
div(y);
}
};
int main(void)
{
Complex complex;
complex.both(32, 23); //因为同一线程可以多次获取同一互斥量,不会发生死锁
std::cout << "main finish\n";
return 0;
}
std::recursive_timed_mutex(c++11)
作用:递归限时锁
成员函数
lock 锁定互斥,若互斥不可用则阻塞
try_lock 尝试锁定互斥,若互斥不可用则返回[立即返回]
try_lock_for 尝试锁定互斥,若互斥在指定的时限时期中不可用则返回[时间段]
try_lock_until 尝试锁定互斥,若直至抵达指定时间点互斥不可用则返回[时间点]
unlock 解锁互斥
std::shared_mutex(c++17)
在<shared_mutex>定义
相当unix系统API中的:读写锁
与其他独占性的mutex不同的,share_mutex拥有两个层次的访问:
- 共享(std::share_lock)- 多个线程能共享同一mutex的所有权
- 独占性(std::unique_lock)- 仅一个线程能占有mutex
若一个线程已经获取独占性锁(通过lock、try_lock),则无其他线程能获取该锁(包括 共享的)
仅当任何线程均为取得独占性锁时,共享锁能被多个线程获取(通过lock_shared、try_lock_shared)
在一个线程内,同一时刻只能获取一个锁(共享或独占性)
share mutex在能由任何数量的线程同时读取共享数据,但一个线程只能在无其他线程同时读写时,写数据
(即读权限任意开放,写权限只允许同一时间原子操作,且写权限高于读权限,当写操作进行时,接下来的读操作将被阻塞)
成员函数
独占性锁(排他性锁定)
lock 锁定互斥,若互斥不可用则阻塞
try_lock 尝试锁定互斥,若互斥不可用则返回
unlock 解锁互斥
共享锁定
lock_shared 为共享所有权锁定互斥,若互斥不可用则阻塞
try_lock_shared 尝试为共享所有权锁定互斥,若互斥不可用则返回
unlock_shared 解锁互斥,即共享所有权
使用案例
int tick = 0;
// 定义一个share mutex
std::shared_mutex _mutex_lock;
void read(const std::string &thread_name)
{
while()
{
{
//读锁:std::share_lock
std::shared_lock read_lock(_mutex_lock);
std::cout<<thread_name<<"read:"<<tick<<std::endl;
}
using namespace std::chrono_literals;
std::this_thread::sleep_for(50ms);
}
}
void write(const std::string &thread_name)
{
while(true)
{
{
//写锁:std::unique_lock
std::unique_lock write_lock(_mutex_lock);
std::cout<< thread_name << "write:" << ++tick << std::endl;
}
using namespace std::chrono_literals;
std::this_thread::sleep_for(50ms);
}
}
int main()
{
std::thread read01{read,std::string("read01")},read02{read,std::string("read02")};
std::thread write01{write,std::string("write01")};
read01.join();
read02.join();
write01.join();
return 0;
}
shared_timed_mutex(C++14)
<shared_mutex>
参考share_mutex
共享互斥通常用于多个读线程能同时访问同一资源而不导致数据竞争,但只有一个写线程能访问的情形。
成员函数
独占性锁(排他性锁定)
lock 锁定互斥,若互斥不可用则阻塞
try_lock 尝试锁定互斥,若互斥不可用则返回
try_lock_for 尝试锁定互斥,若互斥在指定的时限时期中不可用则返回
try_lock_until 尝试锁定互斥,若直至抵达指定时间点互斥不可用则返回
unlock 解锁互斥
共享锁定
lock_shared 为共享所有权锁定互斥,若互斥不可用则阻塞
try_lock_shared 尝试为共享所有权锁定互斥,若互斥不可用则返回
try_lcok_shared_for 尝试为共享所有权锁定互斥,若互斥在指定的时限时期中不可用则返回
try_lock_shared_until 尝试为共享所有权锁定互斥,若直至抵达指定时间点互斥不可用则返回
unlock_shared 解锁互斥,即共享所有权
通用互斥(mutex)管理
std::lock_guard(c++11)
作用:严格实现基于作用域的mutex所有权包装器,缺点不灵活
定义如下,使用见上文std::mutex
使用了RAII,在其构造函数中使用mutex.lock(),析构函数中使用mutex.unlock();
template< class Mutex >
class lock_guard;
//模板形参
Mutex - 要锁定的互斥。类型必须满足可基本锁定 (BasicLockable) 要求
std::scoped_lock(c++17)
作用:用于管理多个mutex,它在作用域的存在期间占有一或多个互斥,它使用了一个称为"死锁避免算法",可使用它避免死锁的算法
{
std::scoped_lock lock(mutexes_[0], mutexes_[1], mutexes_[2], mutexes_[3], mutexes_[4]);
// do something
}
// 等价代码 1 (用 std::lock 和 std::lock_guard )
// std::lock(e1.m, e2.m);
// std::lock_guard<std::mutex> lk1(e1.m, std::adopt_lock);
// std::lock_guard<std::mutex> lk2(e2.m, std::adopt_lock);
// 等价代码 2 (若需要 unique_lock ,例如对于条件变量)
// std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);
// std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);
// std::lock(lk1, lk2);
std::unique_lock(C++11)
作用:实现可移动的mutex所有权包装器,对mutex进行更加灵活的权限管理
成员函数
锁定
lock 锁定关联互斥
try_lock 尝试锁定关联互斥,若互斥不可用则返回
try_lock_for 试图锁定关联的定时可锁互斥,若互斥在给定时长中不可用则返回
try_lock_until 尝试锁定关联可定时锁互斥,若抵达指定时间点互斥仍不可用则返回
unlock 解锁关联互斥
修改器
swap 与另一个std::unique_lock交换状态
release 将关联互斥解关联而不解锁它
观察器
mutex 返回指向关联互斥的指针
owns_lock 测试锁是否占有其关联互斥
operator bool 同上
unique_lock和lock_guard的区别
- unique_lock与lock_guard都能实现自动加锁和解锁,但是前者更加灵活,能实现更多的功能
- unique_lock可以进行临时解锁和再上锁,如在构造对象之后使用lck.unlock()就可以进行解锁, lck.lock()进行上锁,而不必等到析构时自动解锁。lock_guard是不支持手动释放的
- 一般来说,使用unique_lock比较多,除非追求极致的性能才会考虑使用lock_guard
使用场景
//std::lock_guard的缺陷
class LogFile {
std::mutex _mu;
ofstream f;
public:
LogFile() {
f.open("log.txt");
}
~LogFile() {
f.close();
}
void shared_print(string msg, int id) {
{
std::lock_guard<std::mutex> guard(_mu);
//do something 1
}
//do something 2
{
std::lock_guard<std::mutex> guard(_mu);
// do something 3
f << msg << id << endl;
cout << msg << id << endl;
}
}
};
//std::unique_lock的灵活处理
class LogFile {
std::mutex _mu;
ofstream f;
public:
LogFile() {
f.open("log.txt");
}
~LogFile() {
f.close();
}
void shared_print(string msg, int id) {
std::unique_lock<std::mutex> guard(_mu);
//do something 1
guard.unlock(); //临时解锁
//do something 2
guard.lock(); //继续上锁
// do something 3
f << msg << id << endl;
cout << msg << id << endl;
// 结束时析构guard会临时解锁
// 这句话可要可不要,不写,析构的时候也会自动执行
// guard.ulock();
}
};
std::shared_lock(c++14)
作用:实现可移动的共享mutex所有权封装器
std::share_lock是从共享(shared)的方式来进行加锁,当share mutex没有被其他线程锁持有或者以share_lock共享的方式持有,则该线程可以以共享的方式立即获取这把锁(对这把锁进行共享式上锁)
详情见std::shared_mutex的使用
用于指定锁定策略的标签类型(C++11)
defer_lock_t
try_to_lock_t
adopt_lock_t
用于指定锁定策略的标签常量(c++11)
std::defer_lock
、 std::try_to_lock
和 std::adopt_lock
分别是空结构体标签类型 std::defer_lock_t 、 std::try_to_lock_t 和 std::adopt_lock_t 的实例。
它们用于为 std::lock_guard 、 std::unique_lock 及 std::shared_lock 指定锁定策略。
defer_lock
不获得互斥的所有权
std::unique_lock < std::mutex > guard(_mu, std::defer_lock);
在guard的构造函数过程中,不对_mu进行 _mu.lock()操作,因此需要自己手动的使用其成员函数lock()
try_to_lock
尝试获得互斥的所有权而不阻塞
std::unique_lock < std::mutex > guard(_mu, std::try_to_lock_t);
adopt_lock
假设调用方线程已拥有互斥的所有权
std::lock_guardstd::mutex lock1(mutex,std::adopt_lock_t);
{
// 锁定两个互斥而不死锁, 注意:from.m&&to.m已经在这里上锁lock()
std::lock(from.m, to.m);
// 保证二个已锁定互斥在作用域结尾解锁,将已经上锁的mutex的管理起来,在作用域结束的时候,自动unlock()
std::lock_guard<std::mutex> lock1(from.m, std::adopt_lock);
std::lock_guard<std::mutex> lock2(to.m, std::adopt_lock);
}
通用锁定算法
std::try_lock
尝试给每个可锁定的对象,调用try_lock。若try_lock失败,则对已经上锁的对象unclok,并返回第i个可锁对象的下标i,成功为-1.
std::mutex foo_count_mutex;
std::mutex bar_count_mutex;
c
if(-1 == std::try_lock(foo_count_mutex, bar_count_mutex.....))
{
//do something
foo_count_mutex.unlock();
bar_count_mutex.unlock();
}
std::lock
锁定指定的互斥体,若任何一个不可用则阻塞.
note: 锁定给定的可锁对象
lock1
、lock2
、...
、lockn
,用免死锁算法避免死锁。
std::lock(e1.m, e2.m);
std::lock_guard<std::mutex> lk1(e1.m, std::adopt_lock);
std::lock_guard<std::mutex> lk2(e2.m, std::adopt_lock);
// 等价代码(若需要 unique_locks ,例如对于条件变量)
// std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);
// std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);
// std::lock(lk1, lk2);