标签:std thread lock guard 线程 mutex unique loop
目录
前言
前两篇的博文分别介绍了标准库里面的线程和锁,这一次的博文将会介绍锁的管理。
锁在多线程编程中非常常用,但是一旦使用不谨慎就会导致很多问题,最常见的就是死锁问题。
lock_guard
std::lock_guard
是最常见的管理锁的类,它会在初始化的时候自动加锁,销毁的时候自动解锁,需要锁的对象满足BasicLockable
,即存在lock
和unlock
方法。测试代码:
void thread_func(int thread_id) {
{
std::lock_guard<std::mutex> guard(global_mutex);
std::cout << "Test 1:" << thread_id << std::endl;
std::this_thread::sleep_for(1s);
std::cout << "Test 2:" << thread_id << std::endl;
}
std::this_thread::sleep_for(0.5s);
std::cout << "Test 3:" << thread_id << std::endl;
}
std::lock_guard
在线程一开始的代码块就进行了初始化,global_mutex
加锁,所以Test 1和Test 2会一起输出,而之后代码块结束,std::lock_guard
作为区域变量,也在此时析构释放锁。其输出为
除此之外,std::lock_guard
还允许输入第二个参数std::adopt_lock_t
,这个参数表明该线程已经获取了该锁,所以在创建对象的时候不需要再获取锁。
std::lock_guard<std::mutex> lk(mutex1, std::adopt_lock);
scoped_lock (C++17)
这个类和std::lock_guard
类似,不过其可以同时管理多把锁,可以同时给多把锁加锁。这个方法可以很好的解决哲学家就餐问题\(^1\)。
void thread_func(int thread_id, std::mutex &mutex1, std::mutex &mutex2) {
std::scoped_lock lock(mutex1, mutex2);
std::cout << "Thread " << thread_id << " is eating." << std::endl;
std::this_thread::sleep_for(1s);
std::cout << "Thread " << thread_id << " over." << std::endl;
}
std::vector<std::shared_ptr<std::thread>> philosopher;
std::vector<std::mutex> tableware_mutex(5);
for (int loop_i = 0; loop_i < 5; ++loop_i) {
philosopher.push_back(
std::make_shared<std::thread>(thread_func, loop_i, std::ref(tableware_mutex[loop_i]), std::ref(tableware_mutex[(loop_i + 1) % 5]))
);
}
for (int loop_i = 0; loop_i < 5; ++loop_i) {
philosopher.at(loop_i)->join();
}
这里我们初始化了五个哲学家(线程)和五个餐具(锁),每个哲学家需要两个相邻的餐具来进食。其结果很简单:
可以看到,这帮哲学家们很有序的进食,没有产生冲突。而如果我们把对应的std::scoped_lock lock(mutex1, mutex2);
,改为两个std::lock_guard
,肉眼可见的会出现恶心的死锁问题。
和std::lock_guard
类似的,它也有一个参数std::adopt_lock_t
,表明线程已经获取到锁,构造时不需要获取锁,不过这个参数位于第一个。
std::scoped_lock lk(std::adopt_lock, mutex1, mutex2);
unique_lock
std::unique_lock
相比较与std::lock_guard
更为自由,除了std::adopt_lock_t
参数外,其还支持try_to_lock_t
,defer_lock_t
,其中try_to_lock_t
为非阻塞型加锁,defer_lock_t
不在初始化的时候加锁。
std::unique_lock<std::mutex> lk(mutex, std::adopt_lock);
std::unique_lock<std::mutex> lk(mutex, std::try_to_lock);
std::unique_lock<std::mutex> lk(mutex, std::defer_lock);
std::unique_lock
支持超时加锁:
std::unique_lock<std::timed_mutex> lk(mutex, 1s);
std::unique_lock
支持移动语义,所以可以作为返回值
std::unique_lock<std::mutex> get_lock() {
std::unique_lock<std::mutex> lk(mutex);
return lk;
}
void thread_func(int thread_id) {
std::unique_lock<std::mutex> lk = get_lock();
std::cout << "Test 1: " << thread_id << std::endl;
std::this_thread::sleep_for(1s);
std::cout << "Test 2: " << thread_id << std::endl;
}
由于其允许在未加锁构造,所以它也提供了相应的lock
、try_lock
、unlock
等方法。
shared_lock
和std::unique_lock
类似,不过这个是锁定读写锁的读部分。
总结
本文总结了标准库中所有的锁管理的类,合理使用可以使代码更优美。这是标准库线程第三篇博文了,第四篇将会介绍线程里面的条件变量。
ref
[1] https://zh.wikipedia.org/wiki/哲学家就餐问题
博客原文:https://www.cnblogs.com/ink19/p/std_thread-3.html
标签:std,thread,lock,guard,线程,mutex,unique,loop 来源: https://www.cnblogs.com/ink19/p/std_thread-3.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。