tensort 部署具体的joinable 线程安全管理
- 初始状态:当一个
std::thread
对象创建并启动后,它是joinable
的。 - 被
join
后:一旦join
被调用,线程对象不再joinable
。 - 被
detach
后:一旦detach
被调用,线程对象也不再joinable
。 - 默认构造:默认构造的
std::thread
对象(即未关联任何线程的对象)也是不joinable
的。
通过使用 joinable
,可以确保线程的安全管理,避免不必要的错误和资源浪费。
#include <thread>
#include <iostream>
using namespace std;
void worker(int a, int& b) {
printf("hello thread\n");
b = 9;
}
int main() {
// 直接启动线程
// 注意传入参数的生命周期:
int b = 3;
// 下面是对引用的处理
thread t(worker, 5, std::ref(b));
// 等待线程结束
// 只要线程启动,就必须 join
// 如果没启动就 join,也会报错
if (t.joinable()) {
t.join();
}
// 输出修改后的值,验证线程是否正确地修改了传入的引用参数 b
cout << "Value of b after thread execution: " << b << endl;
return 0;
}
-
避免重复
join
:
如果一个线程已经被join
或者已经被detach
,再次调用join
会导致程序崩溃。使用joinable
可以确保线程在调用join
前处于可加入状态,从而避免异常和崩溃。 -
线程管理:
在一些复杂的线程管理逻辑中,可能需要检查线程的状态,以决定下一步的操作。joinable
提供了一种简单的方法来检查线程是否仍然在运行或者是否已经被处理过。 -
资源管理:
检查线程是否joinable
可以帮助管理资源,确保在适当的时机释放线程相关的资源。
部署防止死锁
在多线程编程中,锁(Lock)用于保护共享资源,确保同一时间只有一个线程访问共享资源,从而避免数据竞争。然而,使用锁时需要小心处理,以避免死锁(Deadlock)。以下是关于加锁、解锁以及死锁的解释和示例。
加锁和解锁
在 C++ 中,常用的锁机制包括 std::mutex
和 std::lock_guard
。std::mutex
提供了基本的锁功能,而 std::lock_guard
是一个 RAII(Resource Acquisition Is Initialization)风格的锁管理器,确保在作用域结束时自动解锁。
示例:使用 std::mutex
和 std::lock_guard
#include <thread>
#include <iostream>
#include <mutex>
std::mutex mtx;
void worker(int a, int& b) {
std::lock_guard<std::mutex> lock(mtx); // 加锁
std::cout << "hello thread" << std::endl;
b = 9; // 访问共享资源
// 自动解锁(当 lock_guard 对象销毁时)
}
int main() {
int b = 3;
std::thread t(worker, 5, std::ref(b));
if (t.joinable()) {
t.join();
}
std::cout << "Value of b after thread execution: " << b << std::endl;
return 0;
}
在这个例子中,std::lock_guard
在构造时自动加锁,在析构时自动解锁,确保了线程安全。
死锁
死锁是指两个或多个线程互相等待对方释放资源,从而导致程序无法继续执行。死锁通常发生在以下情况下:
- 互斥条件:至少有一个资源必须是非共享的,即一次只能被一个线程占用。
- 持有并等待条件:一个线程持有至少一个资源,并且在等待其他线程释放资源。
- 不剥夺条件:线程占有的资源在未使用完之前不能被剥夺,只能由占有它的线程释放。
- 循环等待条件:存在一个线程循环等待的链,其中每个线程都在等待下一个线程所占有的资源。
示例:可能导致死锁的代码
#include <thread>
#include <iostream>
#include <mutex>
std::mutex mtx1, mtx2;
void worker1() {
std::lock_guard<std::mutex> lock1(mtx1);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟一些操作
std::lock_guard<std::mutex> lock2(mtx2);
std::cout << "Worker 1 acquired both locks" << std::endl;
}
void worker2() {
std::lock_guard<std::mutex> lock2(mtx2);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟一些操作
std::lock_guard<std::mutex> lock1(mtx1);
std::cout << "Worker 2 acquired both locks" << std::endl;
}
int main() {
std::thread t1(worker1);
std::thread t2(worker2);
t1.join();
t2.join();
return 0;
}
在这个例子中,worker1
和 worker2
分别先锁定 mtx1
和 mtx2
,然后尝试锁定对方已经持有的锁,从而可能导致死锁。
避免死锁的方法
- 避免嵌套锁定:尽量减少嵌套锁定的情况。
- 使用
std::lock
:std::lock
可以同时锁定多个互斥锁,避免死锁。 - 锁的顺序:确保所有线程以相同的顺序获取锁。
- 尝试锁定:使用
std::try_lock
尝试获取锁,如果不能获取则避免等待。
示例:使用 std::lock
避免死锁
#include <thread>
#include <iostream>
#include <mutex>
std::mutex mtx1, mtx2;
void worker1() {
std::lock(mtx1, mtx2); // 同时锁定多个互斥锁
std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
std::cout << "Worker 1 acquired both locks" << std::endl;
}
void worker2() {
std::lock(mtx1, mtx2); // 同时锁定多个互斥锁
std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
std::cout << "Worker 2 acquired both locks" << std::endl;
}
int main() {
std::thread t1(worker1);
std::thread t2(worker2);
t1.join();
t2.join();
return 0;
}
在这个例子中,std::lock
同时锁定 mtx1
和 mtx2
,确保不会发生死锁。std::adopt_lock
表示 lock_guard
对象采用已经锁定的互斥锁。通过这些方法,可以有效避免死锁问题。