以Linux下的測試程序說明遞歸型互斥量和普通互斥量的區別
先貼代碼和測試結果
// Mutex.h: 對pthread的互斥量的RAII包裝 #ifndef _MUTEX_H_ #define _MUTEX_H_ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> // 使用錯誤碼errnum和字符串msg來打印錯誤信息, 並且退出程序 static inline void errExitEN(int errnum, const char* msg) { fprintf(stderr, "%s Error: %s\n", msg, strerror(errnum)); exit(1); } class Mutex { public: explicit Mutex() { int s; pthread_mutexattr_t attr; s = pthread_mutexattr_init(&attr); if (s != 0) errExitEN(s, "pthread_mutexattr_init"); s = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); #ifdef ERRORCHECK s= pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); #elif RECURSIVE s = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); #endif if (s != 0) errExitEN(s, "pthread_mutexattr_settype"); pthread_mutex_init(&__mtx, &attr); if(s != 0) errExitEN(s, "pthread_mutex_init"); s = pthread_mutexattr_destroy(&attr); if (s != 0) errExitEN(s, "pthread_mutexattr_destroy"); } virtual ~Mutex() { int s = pthread_mutex_destroy(&__mtx); if (s != 0) errExitEN(s, "pthread_mutex_destroy"); } void lock() { int s = pthread_mutex_lock(&__mtx); if (s != 0) errExitEN(s, "pthread_mutex_lock"); } void unlock() { int s = pthread_mutex_unlock(&__mtx); if (s != 0) errExitEN(s, "pthread_mutex_unlock"); } private: pthread_mutex_t __mtx; }; #endif
// MutexTest.cpp: Mutex類對於重復獲取同一把鎖的測試 #include "Mutex.h" #include <stdio.h> #include <pthread.h> #include <array> Mutex mtx; std::array<int, 10> g_array; // 將g_array[index]左邊的元素自增(使用互斥量來保護) void incrLeftWithMutex(int index) { mtx.lock(); for (int i = 0; i < index; i++) g_array[i]++; mtx.unlock(); } // 將g_array[index]右邊的元素自增(使用互斥量來保護) void incrRightWithMutex(int index) { mtx.lock(); for (int i = index + 1; i < (int) g_array.size(); i++) g_array[i]++; mtx.unlock(); } // 將g_array[index]以外的元素自增 void incrOtherItem(int index) { mtx.lock(); incrLeftWithMutex(index); incrRightWithMutex(index); mtx.unlock(); } int main() { g_array.fill(0); incrOtherItem(5); for (int i : g_array) printf("%d ", i); printf("\n"); return 0; }
$ g++ MutexTest.cpp -std=c++11 -pthread $ time ./a.out ^C real 0m3.973s user 0m0.004s sys 0m0.000s $ g++ MutexTest.cpp -std=c++11 -pthread -DERRORCHECK $ ./a.out pthread_mutex_lock Error: Resource deadlock avoided pthread_mutex_destroy Error: Device or resource busy $ g++ MutexTest.cpp -std=c++11 -pthread -DRECURSIVE $ ./a.out 1 1 1 1 1 0 1 1 1 1
不額外定義宏則使用默認的互斥量(鎖),定義宏ERRORCHECK則鎖自帶錯誤檢查,定義宏RECURSIVE則代表遞歸鎖。
主線程中調用了incrOtherItem函數,該函數先獲取(acquire)鎖mtx,然後調用另外2個函數後釋放(release)鎖mtx。
實驗結果顯示默認鎖陷入了死鎖,錯誤檢查的結果是resource deadlock avoided(即陷入了死鎖),而遞歸鎖則成功執行了下去。
因為向一把已經被獲取的鎖申請上鎖時,線程會阻塞一直到已獲取鎖的一方將鎖釋放。所以若線程已經獲取了鎖A而未釋放,當它再次獲取鎖A時會陷入死鎖,因為此線程會阻塞直到鎖A被釋放,然後只有擁有鎖的線程(也就是它自己)才能釋放鎖,而線程自己處於阻塞中,所以永遠處於阻塞狀態。
遞歸鎖就是為了解決這種狀況,從incrOtherItem的函數定義看起來代碼沒任何問題,但是incrLeftWithMutex和incrRightWithMutex函數試圖獲取了同一把鎖,這樣相當於未釋放鎖就再次獲取同一把鎖。
遞歸鎖會在內部維護一個計數器,當線程第1次獲取互斥量時,計數器置為1,之後該線程可以在此獲取同一把鎖,每次獲取鎖計數器加1,每次釋放鎖計數器減1。由於此時其他線程無法獲取鎖,所以只要保證該線程的執行過程是可重入的,代碼就沒問題。
由於這種情況往往是函數遞歸調用時才出現的,比如
函數1:上鎖,調用函數2,解鎖。
函數2:上鎖……解鎖
函數1的過程就變成了:上鎖,函數1的內容(第一部分),上鎖,函數2的內容,解鎖,函數1的內容(第二部分),解鎖。
如果函數1的內容是不可重入的,而函數2修改了函數1的操作對象,那麽這裏就會出問題。
比如。函數1是想獲取全局int數組(設為int a[4] = { 1,2,3,4 } )的總和,第一部分是求前半部分的和,第二部分是求後半部分的和。
而函數2若導致int數組發生了變化,比如讓a[2] = 0,這樣最後求得的和就是1+2+0+4=7而不是1+2+3+4=10。
以Linux下的測試程序說明遞歸型互斥量和普通互斥量的區別