C++详细讲解互斥量与lock_guard类模板及死锁攻略
什么是互斥量?
互斥量(Mutex)是一种基本的同步原语,用于保护共享资源的访问并防止竞争条件。它允许多个线程共享同一个互斥量变量,并且同一时间只有一个线程能够拥有此变量,其他线程在等待时被阻塞。当一个线程拥有互斥量时,它可以访问被保护的资源,当它释放互斥量时,其他线程可以获取互斥量并访问资源。
互斥量的使用
使用互斥量需要包含"C++lock()
和try_lock()
。
lock()
lock()是最常见的方式,当线程请求该互斥量时,如果该互斥量已经被拥有,那么该线程会被阻塞等待。当该互斥量被释放时,等待该互斥量的线程中,优先级最高的线程将被唤醒并重新获取该互斥量。
#include <mutex>
#include <thread>
std::mutex mtx;
void func()
{
mtx.lock(); // 请求互斥量
// 访问共享资源
mtx.unlock(); // 释放互斥量
}
try_lock()
try_lock()允许线程请求互斥量,但如果该互斥量已经被另一个线程所拥有,则该函数不会阻塞,而是会立即返回false
。这可以使程序同时避免竞争条件,也能够不被阻塞地继续执行。
#include <mutex>
#include <thread>
std::mutex mtx;
void func()
{
if (mtx.try_lock()) // 尝试请求互斥量
{
// 访问共享资源
mtx.unlock(); // 释放互斥量
}
else
{
// 互斥量已经被另一个线程所拥有,处理该情况
}
}
lock_guard类
使用互斥量时,我们需要注意以下几点:
- 确保在程序异常退出的情况下,不会遗漏互斥量的释放操作。
- 确保在锁定互斥量的情况下,一定要释放互斥量。
为了解决这些问题,C++11中提供了一个名为lock_guard的类模板,它可以在构造函数中锁定互斥量,在析构函数中自动释放互斥量,同时还保证了异常安全性。
#include <mutex>
#include <thread>
#include <iostream>
std::mutex mtx;
void func()
{
std::lock_guard<std::mutex> lock(mtx); // 创建一个lock_guard对象,自动获取和释放互斥量
// 访问共享资源
}
int main()
{
std::thread t1(func);
std::thread t2(func);
t1.join();
t2.join();
return 0;
}
死锁的解决方法
当使用互斥量时,必须非常小心,否则可能会发生死锁。当一个线程等待被另外一个线程锁定的资源时,而同时另一个线程又在等待该线程锁定的资源,这种情况就被称为死锁。为了避免死锁,我们可以采用以下几种方法:
- 确定锁定的顺序,先锁定资源1再锁定资源2,线程都采用相同的顺序进行锁定,解锁则相反顺序。
- 使用std::lock()函数一次性锁定多个互斥量,避免出现部分锁定的情况。
- 使用std::unique_lock的defer_lock参数,将它的锁定推迟到以后的某个时间点。
- 使用std::recursive_mutex和std::recursive_timed_mutex来防止死锁。它们允许一个线程多次请求同一个互斥量,而不发生死锁。
示例说明
以下是一个简单的示例,模拟了两个线程A和B,它们在不同的顺序锁定两个互斥量,从而产生了死锁。
#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx1, mtx2;
void func1()
{
std::lock_guard<std::mutex> lock1(mtx1);
std::cout << "Thread 1: Got lock 1" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lock2(mtx2); // 线程A尝试获取锁2,但是锁2已经被线程B锁定了
std::cout << "Thread 1: Got lock 2" << std::endl;
}
void func2()
{
std::lock_guard<std::mutex> lock2(mtx2);
std::cout << "Thread 2: Got lock 2" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lock1(mtx1); // 线程B尝试获取锁1,但是锁1已经被线程A锁定了
std::cout << "Thread 2: Got lock 1" << std::endl;
}
int main()
{
std::thread t1(func1);
std::thread t2(func2);
t1.join();
t2.join();
return 0;
}
为了避免死锁,我们可以将线程A和线程B锁定资源的顺序加以规定,例如都先锁定mtx1再锁定mtx2。
#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx1, mtx2;
void func1()
{
std::lock_guard<std::mutex> lock1(mtx1);
std::cout << "Thread 1: Got lock 1" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lock2(mtx2);
std::cout << "Thread 1: Got lock 2" << std::endl;
}
void func2()
{
std::lock_guard<std::mutex> lock1(mtx1);
std::cout << "Thread 2: Got lock 1" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lock2(mtx2);
std::cout << "Thread 2: Got lock 2" << std::endl;
}
int main()
{
std::thread t1(func1);
std::thread t2(func2);
t1.join();
t2.join();
return 0;
}
这样就可以避免死锁的发生。当然,以上只是一种方法,具体使用的方法还需要根据实际情况进行分析和调整。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C++详细讲解互斥量与lock_guard类模板及死锁 - Python技术站