互斥量:
多线程竞争:
上图就是多线程竞争的案例,两个线程t1t2同时看到了arr,然后两个人都想把arr扩容,结果就引发了double free。所以,两个线程应该是要么t1在push,要么t2在push。
所以这时候就要用std::mutex
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <mutex>
int main() {
std::vector<int> arr;
std::mutex mtx;
std::thread t1([&] {
for (int i = 0; i < 1000; i++) {
mtx.lock();//上锁
arr.push_back(1);
mtx.unlock();//解锁
}
});
std::thread t2([&] {
for (int i = 0; i < 1000; i++) {
mtx.lock();
arr.push_back(2);
mtx.unlock();
}
});
t1.join();
t2.join();
return 0;
}
这就像是我们去厕所一样,在上厕所的时候就把门锁上,之后再把锁打开,其他人才可以进来。
但是这就又回到我们RAII思想了,如果在一次忘记unlock了,那么就会出现线程t2一直等待t1的现象,所以,我们应该用一个类来完成这个操作。
std::lock_guard
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <mutex>
int main() {
std::vector<int> arr;
std::mutex mtx;
std::thread t1([&] {
for(int i = 0 ; i<1000 ; i++){
std::lock_guard grd(mtx); //离开所在花括号自动析构(unlock)
arr.push_back(1);
}
});
std::thread t2([&] {
for(int i = 0 ; i<1000 ; i++){
std::lock_guard grd(mtx);
arr.push_back(2);
}
});
t1.join();
t2.join();
return 0;
}
但是lock_guard 太过于死板,而且还不能提前解锁,所以提出了std::unique_lock 这个也符合RAII思想,但是自由度更高。
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <mutex>
int main() {
std::vector<int> arr;
std::mutex mtx;
std::thread t1([&] {
for (int i = 0; i < 1000; i++) {
std::unique_lock grd(mtx);
arr.push_back(1);
}
});
std::thread t2([&] {
for (int i = 0; i < 1000; i++) {
std::unique_lock grd(mtx, std::defer_lock);
printf("before the lock\n");
grd.lock();
arr.push_back(2);
grd.unlock();//可以提前解锁,这样就将printf放在了lock之外。
printf("outside of lock\n");
}
});
t1.join();
t2.join();
return 0;
}
这个unique_lock也符合RAII思想,并且自由度更高
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <mutex>
int main() {
std::vector<int> arr1;
std::mutex mtx1;
std::vector<int> arr2;
std::mutex mtx2;
std::thread t1([&] {
for (int i = 0; i < 1000; i++) {
{
std::lock_guard grd(mtx1);
arr1.push_back(1);
}
{
std::lock_guard grd(mtx2);
arr2.push_back(1);
}
}
});
std::thread t2([&] {
for (int i = 0; i < 1000; i++) {
{
std::lock_guard grd(mtx1);
arr1.push_back(2);
}
{
std::lock_guard grd(mtx2);
arr2.push_back(2);
}
}
});
t1.join();
t2.join();
return 0;
}
当然了,如果有多个对象需要上锁,也可以使用多个mutex。然后用{}限制作用域。
try_lock()
如果你只是想看看有没有锁,没锁就进去,锁了就走,也不等待,那么可以用try_lock(),这个就是看一眼你有没有锁,返回一个true或者false。然后要是没有锁的话就会进去之后自动上锁。
还有就是等待一段时间try_lock_for() 这里定义要用std::timed_mutex mtx1;
这个会等待一段时间,如果超过了这段时间就会返回false。
#include <cstdio>
#include <mutex>
std::timed_mutex mtx1;
int main() {
if (mtx1.try_lock_for(std::chrono::milliseconds(500)))
printf("succeed\n");
else
printf("failed\n");
if (mtx1.try_lock_for(std::chrono::milliseconds(500)))
printf("succeed\n");
else
printf("failed\n");
mtx1.unlock();
return 0;
}
同理还有try_lock_until()
std::unique_lock:用 std::try_to_lock 做参数
#include <cstdio>
#include <thread>
#include <mutex>
int main() {
std::mutex mtx;
std::thread t1([&] {
std::unique_lock grd(mtx, std::try_to_lock);
if (grd.owns_lock())
printf("t1 success\n");
else
printf("t1 failed\n");
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
});
std::thread t2([&] {
std::unique_lock grd(mtx, std::try_to_lock);
if (grd.owns_lock())
printf("t2 success\n");
else
printf("t2 failed\n");
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
});
t1.join();
t2.join();
return 0;
}
在这里unique_lock不会上锁,而是会去执行try_lock()。之后owns_lock返回自身状态,也可以检查是否上锁。
死锁
比如多个mutex:
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
int main() {
std::mutex mtx1;
std::mutex mtx2;
std::thread t1([&] {
for (int i = 0; i < 1000; i++) {
mtx1.lock();
mtx2.lock();
mtx2.unlock();
mtx1.unlock();
}
});
std::thread t2([&] {
for (int i = 0; i < 1000; i++) {
mtx2.lock();
mtx1.lock();
mtx1.unlock();
mtx2.unlock();
}
});
t1.join();
t2.join();
return 0;
}
在这里可能出现这种情况:
t1 mtx1.lock()
t2 mtx2.lock()
t1 mtx2.lock()//等待
t2 mtx1.lock()//等待
这样就造成了死锁问题。
解决方法1 永远不要同时持有两个锁。在lock执行完后立即unlock。
解决方法2 双方上锁顺序一致。比如
std::thread t1([&] {
for (int i = 0; i < 1000; i++) {
mtx1.lock();
mtx2.lock();
mtx2.unlock();
mtx1.unlock();
}
});
std::thread t2([&] {
for (int i = 0; i < 1000; i++) {
mtx1.lock();
mtx2.lock();
mtx2.unlock();
mtx1.unlock();
}
});
这样就是
t1 mtx1.lock()
t2 mtx1.lock()//等待
t1 mtx2.lock()
t2 mtx1.lock()//等待
t1 mtx1.unlock()
t2 mtx1.lock()//执行
解决方法3 std::lock()同时对多个锁上锁
int main() {
std::mutex mtx1;
std::mutex mtx2;
std::thread t1([&] {
for (int i = 0; i < 1000; i++) {
std::lock(mtx1, mtx2);
mtx1.unlock();
mtx2.unlock();
}
});
std::thread t2([&] {
for (int i = 0; i < 1000; i++) {
std::lock(mtx2, mtx1);
mtx2.unlock();
mtx1.unlock();
}
});
t1.join();
t2.join();
return 0;
}
这样就可以保证无论线程中调用的顺序是否相同,都不会出现死锁的问题了。
同时std::lock的RAII版本:std::scoped_lock()//c++17
他和lock_guard相对应,不同的是他可以对多个mutex上锁
std::thread t1([&] {
for (int i = 0; i < 1000; i++) {
std::scoped_lock grd(mtx1, mtx2);
// do something
}
});
std::thread t2([&] {
for (int i = 0; i < 1000; i++) {
std::scoped_lock grd(mtx2, mtx1);
// do something
}
});
同一个线程重复调用lock也会死锁:
std::mutex mtx1;
void other() {
mtx1.lock();
// do something
mtx1.unlock();
}
void func() {
mtx1.lock();
other();
mtx1.unlock();
}
int main() {
func();
return 0;
}
这里就是,当func调用了lock之后执行了other语句,而other语句又将mtx1.lock调用了,但这个时候由于mtx1已经被锁住,所以只能等mtx1释放后再调用,但是mtx1得在other之后才能调用,所以造成了死锁。
解决方法:std::recursive_mutex 他会判断是不是同一个线程lock了多次,里面有计数器
#include <iostream>
#include <mutex>
std::recursive_mutex mtx1;
void other() {
mtx1.lock();
// do something
mtx1.unlock();
}
void func() {
mtx1.lock();
other();
mtx1.unlock();
}
int main() {
func();
return 0;
}