功能:避免长时间死锁,可以记录锁获取情况,多次超时,可以记录日志,获取错误情况
在04中可以由于try_lock()不会阻塞该线程而是一直占着CPU资源,因此加入sleep_for(100ms)延时一会阻塞下该线程给其他线程一点机会,然而这的延时是调用的this_thread下的函数:
if (!mux.try_lock())
{
cout << "." << flush;
this_thread::sleep_for(100ms);
continue;
}
也可以将延时做为锁的构造函数的参数,使用超时锁timed_mutex:
timed_mutex tmux;
void ThreadMainTime(int i)
{
for (;;)
{
if (!tmux.try_lock_for(chrono::milliseconds(500)))
{
//如果未在规定时间内拿到锁,那么这段代码可能会出现问题,这里可以进行日志的写入,便于调试
cout << i << "[try_lock_for timeout]" << endl;
continue;
}
cout << i << "[in]" << endl;
this_thread::sleep_for(2000ms);
tmux.unlock();
this_thread::sleep_for(1ms);
}
}
同样的为了确保unlock能释放资源,最后延时一下:
创建了三个线程,每个线程尝试解锁之后先阻塞500ms
疑问:三个线程彼此detach为什么打印的不会乱呢?
int main(int argc, char* argv[])
{
for (int i = 0; i < 3; i++)
{
thread th(ThreadMainTime, i + 1);
th.detach();
}
getchar();
}
[in]表示线程进去了
[try_lock_for timeout]表示进入失败
同一个线程中的同一把锁可以锁多次。避免了一些不必要的死锁
使用场景:一个函数被递归调用,而且函数访问共享数据上锁,在解锁之前递归调用自己,会用到递归锁。另一个场景,一个类,两个成员函数f1,f2都会访问共享数据,会上锁。在f1中上锁后调用f2,也需要递归锁。言外之意,只要解锁之前又调用了同一把锁的上锁操作,为了防止死锁,就需要使用递归锁。
参考:https://www.zhihu.com/question/448190927/answer/1768286353
recursive_mutex rmux;
void Task1()
{
rmux.lock();
cout << "task1 [in]" << endl;
rmux.unlock();
}
void Task2()
{
rmux.lock();
cout << "task2 [in]" << endl;
rmux.unlock();
}
void ThreadMainRec(int i)
{
for (;;)
{
rmux.lock();
Task1();
cout << i << "[in]" << endl;
this_thread::sleep_for(2000ms);
Task2();
rmux.unlock();
this_thread::sleep_for(1ms);
}
}
就算是递归锁也是加锁几次就要解锁几次
int main(int argc, char* argv[])
{
for (int i = 0; i < 3; i++)
{
thread th(ThreadMainRec, i + 1);
th.detach();
}
getchar();
}
可以看到123号ThreadMainRec交替切换,每个ThreadMainRec执行一次task1与task2
经常会遇到这样的场景:
很多(100)个线程同时读一个资源,不会有问题,当有线程对这个资源修改时,这100个资源都要等待,此时再有其他资源想对这个资源修改也同样需要等待
总结下:
读资源的线程在读时其他线程可以读,但是不能写
写资源的线程在写时其他线程既不能读也不能写
因此不同需要的线程使用不同的锁,读的线程使用读的锁,写的线程使用写的锁
某个线程只读的话就用读的锁
某个线程准备写的话先用读的锁,再用写的锁,然后再对资源进行修改
现在祭出这两把锁:
c++14 共享超时互斥锁 shared_timed_mutex
c++17 共享互斥 shared_mutex
以C++14标准为例:
shared_timed_mutex stmux;
使用写的锁:stmux.lock();
使用读的锁:stmux.lock_shared();
shared_timed_mutex stmux;
void ThreadRead(int i)
{
for (;;)
{
stmux.lock_shared();
cout << i << " Read" << endl;
//this_thread::sleep_for(500ms);
stmux.unlock_shared();
this_thread::sleep_for(1ms);
}
}
void ThreadWrite(int i)
{
for (;;)
{
stmux.lock(); //互斥锁 写入
cout << i << " Write" << endl;
this_thread::sleep_for(300ms);
stmux.unlock();
this_thread::sleep_for(1ms);
}
}
int main(int argc, char* argv[])
{
for (int i = 0; i < 3; i++)
{
thread th(ThreadWrite, i + 1);
th.detach();
}
for (int i = 0; i < 3; i++)
{
thread th(ThreadRead, i + 1);
th.detach();
}
getchar();
return 0;
}
想拿到写的锁,得先确保读的锁释放与写的锁释放
想拿到读的锁,只用确保写的锁的释放
可以看到读线程经常出现在同一行,三个读线程通过for循环创建之后,如果资源没有被写的锁锁住,三个线程都能拿的读的锁,一起进入cout的线程任务中,当一个线程的cout << i << " Read" << endl;没有运行完时(汇编之后不止一行)另一个线程的cout << i << " Read" << endl;送到CPU上运行,因此会出现同一行的现象,而写线程就不会这样,他要确保当前没有读线程也没有线程
shared_lock C++14 实现可移动的共享互斥体所有权封装器:
int main(int argc, char* argv[])
{
{
//共享锁
static shared_timed_mutex tmux;
//读取锁 共享锁
{
//调用共享锁
shared_lock<shared_timed_mutex> lock(tmux);
cout << "read data" << endl;
//退出栈区 释放共享锁
}
//写入锁 互斥锁
{
unique_lock<shared_timed_mutex> lock(tmux);
cout << "write data" << endl;
}
}
return 0;
}
shared_lock
explicit shared_lock(mutex_type& _Mtx)
: _Pmtx(_STD addressof(_Mtx)), _Owns(true) { // construct with mutex and lock shared
_Mtx.lock_shared();
}
unique_lock
explicit unique_lock(_Mutex& _Mtx) : _Pmtx(_STD addressof(_Mtx)), _Owns(false) { // construct and lock
_Pmtx->lock();
_Owns = true;
}