一种有效避免死锁的互斥锁设计

下面是摘自网络的一段话,我觉得很好;对认识锁很有帮助。
“为什么要加锁?加锁是为了防止不同的线程访问同一共享资源造成混乱。
打个比方:人是不同的线程,卫生间是共享资源。
你在上洗手间的时候肯定要把门锁上吧,这就是加锁,只要你在里面,这个卫生间就被锁了,只有你出来之后别人才能用。想象一下如果卫生间的门没有锁会是什么样?

什么是加锁粒度呢?所谓加锁粒度就是你要锁住的范围是多大。
比如你在家上卫生间,你只要锁住卫生间就可以了吧,不需要将整个家都锁起来不让家人进门吧,卫生间就是你的加锁粒度。
怎样才算合理的加锁粒度呢?
其实卫生间并不只是用来上厕所的,还可以洗澡,洗手。这里就涉及到优化加锁粒度的问题。
你在卫生间里洗澡,其实别人也可以同时去里面洗手,只要做到隔离起来就可以,如果马桶,浴缸,洗漱台都是隔开相对独立的,实际上卫生间可以同时给三个人使用,当然三个人做的事儿不能一样。这样就细化了加锁粒度,你在洗澡的时候只要关上浴室的门,别人还是可以进去洗手的。如果当初设计卫生间的时候没有将不同的功能区域划分隔离开,就不能实现卫生间资源的最大化使用。这就是设计架构的重要性。”
从上述知道,有一种情况就是,当你进了卫生间,锁上了门,这时你从窗户逃走了,从而造成卫生间永远被锁住了。这就是其中一种死锁。
因此可以设想的就是,当我们从卫生间出来的时候(无论正常出来,还是飞出来,...),都能把锁打开,其它人就能进来。下面的代码就能实现这个功能。
metux.h
#ifndef MUTEX_LOCK_H
#define MUTEX_LOCK_H

#ifndef WIN32
#include <windows.h>
#endif

#ifdef __unix
#include <pthread.h>
#endif // __unix


class Mutex
{
public:
	Mutex();
	~Mutex();

	void Lock();

	void Unlock();

private:
	Mutex(const Mutex&);
	void operator=(const Mutex&);

#ifdef WIN32
	CRITICAL_SECTION m_mutex;
#endif // WIN32

#ifdef __unix
	pthread_mutex_t m_mutex;
#endif // __unix	
};


class MutexLock
{
public:
	explicit MutexLock(Mutex *mutex) :m_mutex(mutex)
	{
		m_mutex->Lock();
	};
	~MutexLock()
	{ 
		m_mutex->Unlock(); 
	};

private:
	// 不允许复制
	MutexLock(const MutexLock&);
	void operator=(const MutexLock&);

	Mutex *m_mutex;
};


#endif // !MUTEX_LOCK_H
 
   mutex.cpp 
   
#include "mutex.h"

Mutex::Mutex()
{
#ifdef WIN32
	InitializeCriticalSection(&m_mutex);
#endif
#ifdef __unix
	pthread_mutex_init(&m_mutex, NULL);
#endif // __unix
}

Mutex::~Mutex()
{
#ifdef WIN32
	DeleteCriticalSection(&m_mutex);
#endif
	
#ifdef __unix
	pthread_mutex_destroy(&m_mutex);
#endif // __unix
}

void Mutex::Lock()
{
#ifdef WIN32
	EnterCriticalSection(&m_mutex);
#endif
#ifdef __unix
	pthread_mutex_lock(&m_mutex);
#endif // __unix
}

void Mutex::Unlock()
{
#ifdef WIN32
	LeaveCriticalSection(&m_mutex);
#endif
#ifdef __unix
	pthread_mutex_unlock(&m_mutex);
#endif // __unix
}


测试
Mutex mutex;

void MutexTest()
{
	MutexLock l(&mutex);
	static int  i = 0;
	printf("i = %d\n", i);
	++i;
}
原理就是,当MutexLock生命周期结束时,会调用析构函数,从而可以实现每次从卫生间出来都可以解锁。当然你可以在MutexText添加大括号({})来约束MetexLock的生命同期,从而减小锁的粒度。
这个设计无论是原理还是实现,还是蛮简单的。前提是你有这方面的经验,才会想到这种实现方法。

一年之后:
时间:20150611
最近想用C++ 11的里面的std::mutex代替原来需要定义各种系统的mutex,因为这样代码更加简洁。
上述设计是之前看LevelDB源码学来,觉得挺好,于是分享出来。而今天修改代码时候发现其实可以用宏定义。
例如:
#define MUTEX_LOCK()\
	m_mutex.lock(); \
	{

#define MUTEX_UNLOCK()\
	}\
	m_mutex.unlock();
如果,只使用#define MUTEX_LOCK,没有使用MUTEX_UNLOCK,编译的时候肯定会报错;很明显,没有MUTEX_UNLOCK,括号是不匹配的。以前的方法是,如果你忘记了写大括号来控制锁的粒度,那么很可能要到函数结束的时候才会解锁。现在的方法不存在这种问题。
但是如果在MUTEX_LOCK 与 MUTEX_UNLOCK之间有return、goto等等,毫无疑问会出现死锁。

猜你喜欢

转载自blog.csdn.net/a374826954/article/details/34948239
今日推荐