1. 程式人生 > >以Linux下的測試程序說明遞歸型互斥量和普通互斥量的區別

以Linux下的測試程序說明遞歸型互斥量和普通互斥量的區別

lib 檢查 字符串 每次 情況 執行 test cpp 遞歸

先貼代碼和測試結果

// 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下的測試程序說明遞歸型互斥量和普通互斥量的區別