c++ 递归锁的使用

非递归锁

同一个线程里,在锁未释放的情况下反复加锁,会导致死锁。

  • 示例
c++ 复制代码
#include <iostream>
#include <mutex>
#include <thread>
#include <unistd.h>
using namespace std;

std::mutex m_mutex;

void Func()
{
	m_mutex.lock();
	cout << "Func" << endl;
	m_mutex.unlock();
}

int main()
{
	// create thread
	std::thread th1 = std::thread([&]() {
		while(1)
		{
			m_mutex.lock();
			cout << "thread1 working" << endl;
			Func();
			m_mutex.unlock();
			sleep(1);
		}
	});
	th1.join();	

	while(1)
	{
		sleep(2);
	}

	return 0;
}
  • 运行
bash 复制代码
[root@localhost ~]# ./testDeadLock
thread1 working

发现程序卡住不动,无Func函数中的打印。

  • 调试
bash 复制代码
[root@localhost deadLock]# ps -aux | grep testDeadLock
root      88473  0.0  0.1  27200  1156 pts/1    Sl+  14:13   0:00 ./testDeadLock
root      88541  0.0  0.1 112832   996 pts/2    R+   14:13   0:00 grep --color=auto testDeadLock
[root@localhost deadLock]# gdb attach 88473
//... 
(gdb) info threads
  Id   Target Id         Frame 
  2    Thread 0x7f13e4603700 (LWP 88474) "testDeadLock" 0x00007f13e530454d in __lll_lock_wait () from /lib64/libpthread.so.0
* 1    Thread 0x7f13e5a1d740 (LWP 88473) "testDeadLock" 0x00007f13e52ff017 in pthread_join () from /lib64/libpthread.so.0
(gdb) t 2
[Switching to thread 2 (Thread 0x7f13e4603700 (LWP 88474))]
#0  0x00007f13e530454d in __lll_lock_wait () from /lib64/libpthread.so.0
(gdb) bt
#0  0x00007f13e530454d in __lll_lock_wait () from /lib64/libpthread.so.0
#1  0x00007f13e52ffe9b in _L_lock_883 () from /lib64/libpthread.so.0
#2  0x00007f13e52ffd68 in pthread_mutex_lock () from /lib64/libpthread.so.0
#3  0x0000000000402534 in __gthread_mutex_lock (__mutex=0x604120 <m_mutex>) at /opt/rh/devtoolset-8/root/usr/include/c++/8/x86_64-redhat-linux/bits/gthr-default.h:748
#4  0x0000000000402584 in std::mutex::lock (this=0x604120 <m_mutex>) at /opt/rh/devtoolset-8/root/usr/include/c++/8/bits/std_mutex.h:103
#5  0x0000000000401f88 in Func () at source/test.cpp:12
#6  0x000000000040200a in <lambda()>::operator()(void) const (__closure=0x2439018) at source/test.cpp:28
#7  0x0000000000402242 in std::__invoke_impl<void, main()::<lambda()> >(std::__invoke_other, <unknown type in /home/testDeadLock, CU 0x0, DIE 0xada9>) (
    __f=<unknown type in /home/testDeadLock, CU 0x0, DIE 0xada9>) at /opt/rh/devtoolset-8/root/usr/include/c++/8/bits/invoke.h:60
#8  0x000000000040209e in std::__invoke<main()::<lambda()> >(<unknown type in /mnt/hgfs/test/deadLock/debug.x64-linux-g8/testDeadLock, CU 0x0, DIE 0xb073>) (
    __fn=<unknown type in /home/testDeadLock, CU 0x0, DIE 0xb073>) at /opt/rh/devtoolset-8/root/usr/include/c++/8/bits/invoke.h:95
#9  0x0000000000402484 in std::thread::_Invoker<std::tuple<main()::<lambda()> > >::_M_invoke<0>(std::_Index_tuple<0>) (this=0x2439018) at /opt/rh/devtoolset-8/root/usr/include/c++/8/thread:244
#10 0x000000000040245a in std::thread::_Invoker<std::tuple<main()::<lambda()> > >::operator()(void) (this=0x2439018) at /opt/rh/devtoolset-8/root/usr/include/c++/8/thread:253
#11 0x000000000040243e in std::thread::_State_impl<std::thread::_Invoker<std::tuple<main()::<lambda()> > > >::_M_run(void) (this=0x2439010) at /opt/rh/devtoolset-8/root/usr/include/c++/8/thread:196
#12 0x00000000004028ff in execute_native_thread_routine ()
#13 0x00007f13e52fdea5 in start_thread () from /lib64/libpthread.so.0
#14 0x00007f13e4702b0d in clone () from /lib64/libc.so.6
(gdb) f 5
#5  0x0000000000401f88 in Func () at source/test.cpp:12
12		m_mutex.lock();
(gdb) p *(pthread_mutex_t*)$rdi
$1 = {__data = {__lock = 2, __count = 0, __owner = 88474, __nusers = 1, __kind = 0, __spins = 0, __elision = 0, __list = {__prev = 0x0, __next = 0x0}}, 
  __size = "\002\000\000\000\000\000\000\000\232Y\001\000\001", '\000' <repeats 26 times>, __align = 2}
(gdb) 
  • 排查步骤
bash 复制代码
1) ps查看进程id;

2) "gdb attach 进程id"附加至该进程;

3) "info threads"查看所有线程信息;

4) 查找有执行锁等待__lll_lock_wait ()的线程, 一般为死锁线程, 切换至该线程, 如切换至线程2,则执行"t 2";

5) "bt" 查看当前线程堆栈;

6) "f 帧数"切换至自己所写代码处;

7) "p *(pthread_mutex_t*)$rdi"打印寄存器信息, rdi为显示寄存器;

8) 对7)的打印主要关注两个地方,"__lock = 2"一般说明有死锁, "__owner = 88474"代码发生死锁的线程, 与"info threads"的打印信息中 "(LWP 88474)"对应, 接下来直接分析88474指向的线程即可;

此排查方式适用于多个线程出现死锁的情况。

递归锁

递归锁允许同一个线程在未释放其拥有的锁时反复对该锁进行加锁操作。

  • 示例

在c++11中,std::recursive_mutex 来支持递归锁。

c++ 复制代码
#include <iostream>
#include <mutex>
#include <thread>
#include <unistd.h>
using namespace std;

std::recursive_mutex m_recursive_mutex;

void Func()
{
	std::lock_guard<std::recursive_mutex> mtx(m_recursive_mutex);
	cout << "Func" << endl;
}

int main()
{
	// create thread1
	std::thread th1 = std::thread([&]() {
		while(1)
		{
			std::lock_guard<std::recursive_mutex> mtx(m_recursive_mutex);
			cout << "thread1 working" << endl;

			Func();

			sleep(1);
		}
	});
	th1.join();	

	while(1)
	{
		sleep(2);
	}

	return 0;
}
  • 运行
bash 复制代码
[root@localhost ~]# ./testDeadLock
thread1 working
Func
thread1 working
Func
thread1 working
Func
thread1 working
Func

程序可正常打印。

windows下递归锁

  • 特点
    • windows下的互斥量和临界区(关键段)默认支持递归锁;
    • EnterCriticalSection可以被多次调用;
    • EnterCriticalSection与LeaveCriticalSection调用次数必须对应;
    • 临界区函数如下:
c++ 复制代码
// 初始化一个临界区对象
void InitializeCriticalSection(  LPCRITICAL_SECTION lpCriticalSection);

// 删除临界区对象释放由该对象使用的所有系统资源
void DeleteCriticalSection(_Inout_ LPCRITICAL_SECTION lpCriticalSection);

// 进入临界区
void EnterCriticalSection( LPCRITICAL_SECTION lpCriticalSection );

// 删除临界区
void LeaveCriticalSection( LPCRITICAL_SECTION lpCriticalSection );
  • 示例
c++ 复制代码
#include <iostream>
#include <thread>
#include <Windows.h>
using namespace std;

CRITICAL_SECTION g_Critical;

class CWinRecursiveLock
{
public:
	CWinRecursiveLock(CRITICAL_SECTION *pCritcal)	
	{
		m_pCritical = pCritcal;
		EnterCriticalSection(m_pCritical);
	}

	~CWinRecursiveLock()	//析构函数
	{
		LeaveCriticalSection(m_pCritical);
	}

private:
	CRITICAL_SECTION *m_pCritical = nullptr;
};

void Func()
{
	CWinRecursiveLock wlock(&g_Critical);  // 重复加锁
	cout << "Func" << endl;
}

int main()
{
	// 初始化
	InitializeCriticalSection(&g_Critical);
	std::thread th1 = std::thread([&]() {
		while(1)
		{
			CWinRecursiveLock wlock(&g_Critical);
			cout << "thread1 working" << endl;

			Func();

			Sleep(1000);
		}
	});
	th1.join();	

	system("pause");
	return 0;
}

经测试,程序可正常运行。

相关推荐
愚润求学3 小时前
【递归、搜索与回溯】FloodFill算法(一)
c++·算法·leetcode
uyeonashi3 小时前
【QT系统相关】QT文件
开发语言·c++·qt·学习
sunny-ll4 小时前
【C++】详解vector二维数组的全部操作(超细图例解析!!!)
c语言·开发语言·c++·算法·面试
嵌入式@秋刀鱼5 小时前
《第四章-筋骨淬炼》 C++修炼生涯笔记(基础篇)数组与函数
开发语言·数据结构·c++·笔记·算法·链表·visual studio code
嵌入式@秋刀鱼5 小时前
《第五章-心法进阶》 C++修炼生涯笔记(基础篇)指针与结构体⭐⭐⭐⭐⭐
c语言·开发语言·数据结构·c++·笔记·算法·visual studio code
whoarethenext6 小时前
使用 C/C++的OpenCV 裁剪 MP4 视频
c语言·c++·opencv
愚润求学6 小时前
【递归、搜索与回溯】FloodFill算法(二)
c++·算法·leetcode
泽02026 小时前
C++之list的自我实现
开发语言·数据结构·c++·算法·list
斗转星移36 小时前
c++默认类模板参数
开发语言·c++
福理原乡大王6 小时前
Linux信号详解
linux·运维·服务器·c++·ubuntu·信号处理