线程之间的锁有:互斥锁、条件锁、递归锁、读写锁、自旋锁。
busy-waiting锁: 自旋锁是busy-waiting锁。如果线程去申请自旋锁时,发现此时锁被占用了。此时、线程会一直不断地循环检查自旋锁是否可用,直到获取到这个自旋锁为止。
sleep-waiting锁: 除自旋锁外,其他的都是sleep-waiting锁。如果线程去申请锁时,发现此时锁被占用了,线程会处于阻塞状态,被放入到等待队列中去,处理器会去处理其他任务而不必一直等待。
mutex又称互斥量,提供了独占所有权的特性,即不支持递归地对 std::mutex 对象上锁。mutex常见的成员函数如下:
成员函数 | 功能 |
---|---|
构造函数 | std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的 |
lock() | 调用线程将锁住该互斥量 |
unlock() | 解锁,释放对互斥量的所有权 |
try_lock() | 尝试锁住互斥量,如果互斥量被其他线程占有,则当前调用线程返回 false,而并不会被阻塞掉 |
下面是一个使用mutex的示例
#include <mutex>
#include <iostream>
#include <thread>
#define THREAD_MAX_COUNT 10
std::mutex mtx;
int g_count = 0;
void ThreadEntry()
{
for (int i = 0; i < 100; i ++)
{
mtx.lock();
++g_count;
mtx.unlock();
}
}
int main()
{
std::thread threads[THREAD_MAX_COUNT];
for (int i = 0; i < THREAD_MAX_COUNT; i++)
{
threads[i] = std::thread(ThreadEntry);
}
for (auto& th : threads)
{
th.join();
}
std::cout << " successful increases of the counter " << g_count << std::endl;
return 0;
}
已经获取到互斥锁的线程再次尝试获取互斥锁就会产生死锁,例如:
std::mutex mtx;
int g_count = 0;
void func2()
{
mtx.lock();
g_count++;
mtx.unlock();
}
void func1()
{
mtx.lock();
func2(); // func2函数里面有获取互斥锁的操作,会产生死锁
mtx.unlock();
}
int main()
{
std::thread td = std::thread(func1);
td.join();
return 0;
}
检测任务数据结构、定时器、无锁队列,等组件实现一个死锁检测机制
struct Node
{
std::thread td; // 线程ID
unsigned int interval; // 检测周期
int strategy; // 检测到死锁时使用的策略
};
递归锁允许同一个线程多次获取该互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁的问题。
#include <mutex>
#include <iostream>
#include <thread>
std::recursive_mutex mutex;
int g_count = 0;
void func2()
{
std::lock_guard<std::recursive_mutex> lock(mutex);
g_count++;
}
void func1()
{
std::lock_guard<std::recursive_mutex> lock(mutex);
func2();
}
int main()
{
std::thread td = std::thread(func1);
td.join();
std::cout << "count = " << g_count << std::endl;
return 0;
}
自旋锁(spin lock)属于busy-waiting类型锁。自旋锁最多只能被一个可执行线程持有,如果一个可执行线程试图获得一个被其它线程持有的自旋锁,那么线程就会一直进行忙等待,自旋(空转),等待自旋锁重新可用。如果自旋锁未被争用,请求锁的执行线程便立刻得到自旋锁,继续执行。
下面是互斥锁与自旋锁之间的区别,如下:
互斥锁 | 自旋锁 | |
---|---|---|
锁被占用 | 线程变成阻塞状态被放入到等待队列中去,处理器会去处理其他任务而不会一直等待,也就是说处理器不会因为线程阻塞而空闲着 | 线程的状态不会发生变换,会一直不断地循环检查自旋锁是否可用,直到获取到这个自旋锁为止。 |
应用场景 | 执行的任务比较长 | 执行的任务比较短 |
C++ 提供了unique_lock与lock_guard模版,通过RAII的方式对锁进行管理
std::unique_lock 提供了更多的灵活性。它允许在构造时选择是否锁定互斥量,以及在后续的使用中手动锁定和解锁。这使得它更适用于一些需要条件变量、超时等更复杂场景的情况。
std::unique_lock 可以与 std::condition_variable 一起使用,以实现等待某个条件成立的操作。它支持 std::unique_lock::wait、std::unique_lock::notify_one 和 std::unique_lock::notify_all 等操作。
std::lock_guard 提供了一种简单、直观的方式来管理互斥锁。在构造时锁定互斥量,在析构时自动解锁。因为它的设计目标是简洁性,所以没有提供手动解锁的功能。如果你的代码只需进行简单的锁定和解锁操作,std::lock_guard 是一个不错的选择。示例:
#include <mutex>
#include <iostream>
#include <thread>
std::recursive_mutex mutex;
int g_count = 0;
void func1()
{
std::lock_guard<std::recursive_mutex> lock(mutex);
g_count++;
}
int main()
{
std::thread td = std::thread(func1);
td.join();
std::cout << "count = " << g_count << std::endl;
return 0;
}