互斥锁mutex
C++11一共提出四种互斥锁
std::mutex:独占的互斥锁,不能递归使用 std::timed_mutex:带超时的独占互斥锁,不能递归使用 std::recursive_mutex:递归互斥锁,不带超时功能 std::recursive_timed_mutex:带超时的递归互斥锁
1. mutex
mutex有三个成员函数: void lock(); bool try_lock(); void unlock();
1.1 lock() lock()函数用于给临界区加锁,并且只能有一个线程获得锁的所有权
独占互斥锁对象有俩种状态: 锁定和未锁定。
lock 函数用于阻塞地锁定互斥量。 当一个线程调用 lock 时,如果互斥量当前没有被其他线程锁定,那么调用线程将成功锁定互斥量,继续执行后续代码。 如果互斥量已经被其他线程锁定,那么调用线程将阻塞,直到互斥量可用。 std::mutex myMutex;
void myFunction() { myMutex.lock(); // 互斥区域 myMutex.unlock(); } 1.2 try_lock() try_lock 函数是 std::mutex 的成员函数,它尝试非阻塞地锁定互斥量。 当一个线程调用 try_lock 时,如果互斥量当前没有被其他线程锁定,那么调用线程将成功锁定互斥量,继续执行后续代码。 如果互斥量已经被其他线程锁定,那么 try_lock 不会阻塞,而是返回一个失败的状态,让调用线程知道互斥量当前不可用。 std::mutex myMutex;
void myFunction() { if (myMutex.try_lock()) { // 互斥区域 myMutex.unlock(); } else { // 互斥量已被锁定,处理失败的情况 } } 1.3 unlock() 当互斥锁被锁定之后可以通过unlock()进行解锁,但是需要注意的是只有拥有互斥锁所有权的线程也就是对互斥锁上锁的线程才能将其解锁,其它线程是没有权限做这件事情的。
1.4 std::lock_guard(轻锁) lock_guard是C++11新增的一个模板类,使用这个类,可以简化互斥锁lock()和unlock()的写法,同时也更安全。 因为其基于 RAII原则的设计,通过在构造函数中锁定互斥锁,在析构函数中解锁互斥锁,确保在作用域结束时互斥锁一定被正确解锁,
#include <iostream> #include <mutex>
std::mutex myMutex;
void someFunction() { std::lock_guardstd::mutex lock(myMutex); // 在作用域内自动锁定和解锁
// 互斥区域
std::cout << "Critical section protected by lock_guard" << std::endl;
} // 在作用域结束时,lock_guard 的析构函数会自动解锁互斥锁 自动锁定和解锁: 一旦 std::lock_guard 对象被创建,它会自动锁定互斥锁。在作用域结束时,不论是正常结束还是异常结束,std::lock_guard 的析构函数都会被调用,自动解锁互斥锁。 不能手动解锁: std::lock_guard 的设计目的是为了简化互斥锁的使用,因此它没有提供手动解锁的方法。互斥锁的锁定和解锁完全由 std::lock_guard 管理,确保锁定互斥锁的时间段与 std::lock_guard 对象的生命周期一致。 lock_guard缺点:不能手动解锁,只有在大括号{}结束的时候才会调用析构函数解锁,这导致如果{}定义域的范围很大的话,锁的粒度也会很大,很大程度上影响效率
为了解决上述缺点,unique_lock就出现了
1.5 std::unique_lock(重锁) 跟lock_guard对比:
优点:可以利用其unlock()函数主动解锁
缺点:效率偏低,毕竟unique_lock内部会维护一个锁的状态
#include <iostream> #include <mutex>
std::mutex myMutex;
void someFunction() { std::unique_lockstd::mutex ul(m); // unique_lock 方式上锁 std::cout << "another worker thread is running..." << std::endl; // 这里可以写一些需要互斥保护的代码 std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << "another worker thread is done." << std::endl; ul.unlock(); // 手动释放锁 //do something... } // 如果锁未释放,unique_lock 会在此自动释放锁
2. std::recursive_mutex
递归互斥锁,允许同一线程多次对互斥量进行锁定,防止死锁。
下面这个例子中会由于重复加锁导致死锁
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
struct Calculate
{
Calculate() : m_i(6) {}
//重复加锁
void mul(int x)
{
lock_guard<mutex> locker(m_mutex);
m_i *= x;
}
void div(int x)
{
lock_guard<mutex> locker(m_mutex);
m_i /= x;
}
//第一次加锁
void both(int x, int y)
{
lock_guard<mutex> locker(m_mutex);
mul(x);
div(y);
}
int m_i;
mutex m_mutex;
};
int main()
{
Calculate cal;
cal.both(6, 3);
return 0;
}
上面的程序中执行了cal.both(6, 3);调用之后,程序就会发生死锁,在both()中已经对互斥锁加锁了,继续调用mult()函数,已经得到互斥锁所有权的线程再次获取这个互斥锁的所有权就会造成死锁(在C++中程序会异常退出,使用C库函数会导致这个互斥锁永远无法被解锁,最终阻塞所有的线程)。要解决这个死锁的问题,一个简单的办法就是使用递归互斥锁std::recursive_mutex,它允许一个线程多次获得互斥锁的所有权。
虽然递归互斥锁可以解决同一个互斥锁频繁获取互斥锁资源的问题,但是还是建议少用,主要原因如下:
使用递归互斥锁的场景往往都是可以简化的,使用递归互斥锁很容易放纵复杂逻辑的产生,从而导致bug的产生 递归互斥锁比非递归互斥锁效率要低一些。 递归互斥锁虽然允许同一个线程多次获得同一个互斥锁的所有权,但最大次数并未具体说明,一旦超过一定的次数,就会抛出std::system错误。
3. std::timed_mutex
std::timed_mutex是超时独占互斥锁,主要是在获取互斥锁资源时增加了超时等待功能,因为不知道获取锁资源需要等待多长时间,为了保证不一直等待下去,设置了一个超时时长,超时后线程就可以解除阻塞去做其他事情了。
std::timed_mutex比std::_mutex多了两个成员函数:try_lock_for()和try_lock_until():
void lock();
bool try_lock();
void unlock();
// std::timed_mutex比std::_mutex多出的两个成员函数
template <class Rep, class Period>
bool try_lock_for (const chrono::duration<Rep,Period>& rel_time);
template <class Clock, class Duration>
bool try_lock_until (const chrono::time_point<Clock,Duration>& abs_time);
try_lock_for函数是当线程获取不到互斥锁资源的时候,让线程阻塞一定的时间长度
try_lock_until函数是当线程获取不到互斥锁资源的时候,让线程阻塞到某一个指定的时间点
关于两个函数的返回值:当得到互斥锁的所有权之后,函数会马上解除阻塞,返回true,如果阻塞的时长用完或者到达指定的时间点之后,函数也会解除阻塞,返回false
以下示例程序演示std::timed_mutex的使用:
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
timed_mutex g_mutex;
void work()
{
chrono::seconds timeout(1);
while (true)
{
// 通过阻塞一定的时长来争取得到互斥锁所有权
if (g_mutex.try_lock_for(timeout))
{
cout << "The current thread 's ID is: " << this_thread::get_id()
<< ", get the mutex..." << endl;
// 模拟处理任务用了一定的时长
this_thread::sleep_for(chrono::seconds(3));
// 互斥锁解锁
g_mutex.unlock();
break;
}
else
{
cout << "The current thread 's ID is: " << this_thread::get_id()
<< ",not get the mutex..." << endl;
// 模拟处理其他任务用了一定的时长
this_thread::sleep_for(chrono::milliseconds(50));
}
}
}
int main()
{
thread t1(work);
thread t2(work);
t1.join();
t2.join();
return 0;
}
/*
===========output==========
The current thread 's ID is: 2, get the mutex...
The current thread 's ID is: 3,not get the mutex...
The current thread 's ID is: 3,not get the mutex...
The current thread 's ID is: 3, get the mutex...
==== Program exited with exit code: 0 ====
Time elapsed: 000:06.250 (MM:SS.MS)
Press any key to continue...
*/
关于递归超时互斥锁std::recursive_timed_mutex的使用方式和std::timed_mutex是一样的,只不过它可以允许一个线程多次获得互斥锁所有权,而std::timed_mutex只允许线程获取一次互斥锁所有权。另外,递归超时互斥锁std::recursive_timed_mutex也拥有和std::recursive_mutex一样的弊端,不建议频繁使用。
条件变量
条件变量是C++11提供的另外一种用于等待的同步机制,它能阻塞一个或多个线程,直到收到另外一个线程发出的通知或者超时时,才会唤醒当前阻塞的线程。条件变量需要和互斥量配合起来使用,C++11提供了两种条件变量:
标签:std,知识点,lock,阻塞,互斥,线程,mutex,多线程 From: https://blog.51cto.com/u_14882565/9120405