天天看點

一種有效避免死鎖的互斥鎖設計

下面是摘自網絡的一段話,我覺得很好;對認識鎖很有幫助。 “為什麼要加鎖?加鎖是為了防止不同的線程通路同一共享資源造成混亂。

打個比方:人是不同的線程,衛生間是共享資源。

你在上洗手間的時候肯定要把門鎖上吧,這就是加鎖,隻要你在裡面,這個衛生間就被鎖了,隻有你出來之後别人才能用。想象一下如果衛生間的門沒有鎖會是什麼樣?

什麼是加鎖粒度呢?所謂加鎖粒度就是你要鎖住的範圍是多大。

比如你在家上衛生間,你隻要鎖住衛生間就可以了吧,不需要将整個家都鎖起來不讓家人進門吧,衛生間就是你的加鎖粒度。

怎樣才算合理的加鎖粒度呢?

其實衛生間并不隻是用來上廁所的,還可以洗澡,洗手。這裡就涉及到優化加鎖粒度的問題。

你在衛生間裡洗澡,其實别人也可以同時去裡面洗手,隻要做到隔離起來就可以,如果馬桶,浴缸,洗漱台都是隔開相對獨立的,實際上衛生間可以同時給三個人使用,當然三個人做的事兒不能一樣。這樣就細化了加鎖粒度,你在洗澡的時候隻要關上浴室的門,别人還是可以進去洗手的。如果當初設計衛生間的時候沒有将不同的功能區域劃分隔離開,就不能實作衛生間資源的最大化使用。這就是設計架構的重要性。” 從上述知道,有一種情況就是,當你進了衛生間,鎖上了門,這時你從窗戶逃走了,進而造成衛生間永遠被鎖住了。這就是其中一種死鎖。

是以可以設想的就是,當我們從衛生間出來的時候(無論正常出來,還是飛出來,...),都能把鎖打開,其它人就能進來。下面的代碼就能實作這個功能。

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等等,毫無疑問會出現死鎖。