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;
}

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

相关推荐
jyan_敬言27 分钟前
【Linux】Linux命令与操作详解(一)文件管理(文件命令)、用户与用户组管理(创建、删除用户/组)
linux·运维·服务器·c语言·开发语言·汇编·c++
笑非不退41 分钟前
C++ 异步编程 并发编程技术
开发语言·c++
T0uken1 小时前
【QT Qucik】C++交互:接收QML信号
c++·qt·交互
爱写代码的刚子1 小时前
C++知识总结
java·开发语言·c++
s_little_monster2 小时前
【QT】QT入门
数据库·c++·经验分享·笔记·qt·学习·mfc
Yingye Zhu(HPXXZYY)2 小时前
洛谷 P11045 [蓝桥杯 2024 省 Java B] 最优分组
c++·蓝桥杯
三玖诶2 小时前
第一弹:C++ 的基本知识概述
开发语言·c++
木向3 小时前
leetcode42:接雨水
开发语言·c++·算法·leetcode
sukalot3 小时前
windows C++-创建基于代理的应用程序(下)
c++