C++多線程同步之Mutex(互斥量)


原文鏈接: http://blog.csdn.net/olansefengye1/article/details/53086141

一、互斥量Mutex同步多線程

1、Win32平台

相關函數和頭文件

#include <windows.h> HANDLE CreateMutex( LPSECURITY_ATTRIBUTESlpMutexAttributes, // 指向安全屬性的指針 BOOLbInitialOwner, // 初始化互斥對象的所有者 LPCTSTRlpName // 指向互斥對象名的指針 ); DWORD WINAPI WaitForSingleObject( __in HANDLE hHandle,//互斥量對象句柄 __in DWORD dwMilliseconds//等待時間 ); BOOL WINAPI ReleaseMutex(HANDLE hMutex); 返回值:BOOL,TRUE表示成功,FALSE表示失敗。 參數表:hMutex:HANDLE,制定一個互斥體的句柄。 BOOL CloseHandle(HANDLE hObject); 參數: hObject 代表一個已打開對象handle。 返回值: TRUE:執行成功; FALSE:執行失敗,可以調用GetLastError()獲知失敗原因。

源碼: 
從本篇開始,我對代碼會進行一些封裝,使之更貼近實際使用的情況。

/***MyMutex.h頭文件***/ #ifndef __MY_MUTEX_H #define __MY_MUTEX_H #include <windows.h> class CMyMutex { public: CMyMutex(); virtual ~CMyMutex(); void Lock(); void UnLock(); private: HANDLE m_hMutex; }; class CAutoLock { public: CAutoLock(CMyMutex* pMutex); virtual ~CAutoLock(); private: CMyMutex* m_pMutex; }; #endif;
/***MyMutex.cpp文件***/ #include <iostream> #include <windows.h> #include "MyMutex.h" using namespace std; CMyMutex::CMyMutex() { m_hMutex = CreateMutex(NULL /*默認安全屬性*/ , false /*創建線程不擁有該信號量*/ , NULL /*鎖名稱*/ ); } CMyMutex::~CMyMutex() { if(NULL != m_hMutex) { CloseHandle(m_hMutex); cout<<"m_hMutex被關閉"<<endl; } } void CMyMutex::Lock() { if(NULL == m_hMutex) { cout<<"m_hMutex為空"<<endl; return; } DWORD dRes = -1; dRes = WaitForSingleObject(m_hMutex, INFINITE); if(WAIT_OBJECT_0 == dRes) { // cout<<"上鎖成功!"<<endl; } else if(WAIT_ABANDONED == dRes) { cout<<"發生鎖死現象"<<endl; } else if(WAIT_TIMEOUT == dRes) { cout<<"等待超時"<<endl; } else if(WAIT_FAILED == dRes) { cout<<"發生錯誤"<<endl; } else { cout<<"上鎖失敗!"<<endl; } } void CMyMutex::UnLock() { ReleaseMutex(m_hMutex); } //****************************CAutoLock***************************************** CAutoLock::CAutoLock(CMyMutex* pMutex) { m_pMutex = pMutex; m_pMutex->Lock(); } CAutoLock::~CAutoLock() { m_pMutex->UnLock(); }
/***main.cpp文件***/ #include <iostream> #include <windows.h> #include "MySemaphore.h" #include "MyMutex.h" using namespace std; CMyMutex MyMutex;/*聲明一個全局的互斥量對象(自己封裝的)*/ DWORD WINAPI Fun(LPVOID lpParamter) { string strPrint((const char*)lpParamter); int iRunTime = 0; //執行100次跳出 while(++iRunTime<100) { /*利用CMyMutex的構造函數和析構函數分別取創建和關閉互斥量 利用CAutoLock的構造和析構函數去WaitForSingleObject和ReleaseMutex互斥量 */ CAutoLock cLock(&MyMutex); cout <<"["<< iRunTime <<"]:"<< strPrint.c_str()<<endl; //線程函數阻塞,交出CPU使用權限 Sleep(10); } return 0; } int main() { //創建子線程 string str1 = "A"; string str2 = "B"; string str3 = "C"; string str4 = "D"; string str5 = "E"; HANDLE hThread1 = CreateThread(NULL, 0, Fun, (void*)str1.c_str(), 0, NULL); HANDLE hThread2 = CreateThread(NULL, 0, Fun, (void*)str2.c_str(), 0, NULL); HANDLE hThread3 = CreateThread(NULL, 0, Fun, (void*)str3.c_str(), 0, NULL); HANDLE hThread4 = CreateThread(NULL, 0, Fun, (void*)str4.c_str(), 0, NULL); HANDLE hThread5 = CreateThread(NULL, 0, Fun, (void*)str5.c_str(), 0, NULL); //關閉線程 CloseHandle(hThread1); CloseHandle(hThread2); CloseHandle(hThread3); CloseHandle(hThread4); CloseHandle(hThread5); getchar(); // system("pause"); return 0; }

運行結果:這里寫圖片描述 
五個線程分別打印字符串A到E,各執行99次,沒有出現打印混亂(對屏幕資源進行爭奪)的情況。

另外有興趣的讀者可以把代碼敲一遍,每個線程打印9次,然后把CAutoLock的析構函數內的 m_pMutex->UnLock();注釋起來會出現什么情況?可以思考一下。 
運行結果:這里寫圖片描述 
出現的現象是:每個線程打印了9次就出現了“發生死鎖現象”,而且打印A的線程居然可以不停的對m_pMutex->Lock();這是為什么呢? 
WAIT_ABANDONED 0x00000080:當hHandle為mutex時,如果擁有mutex的線程在結束時沒有釋放核心對象會引發此返回值。這就是為什么會打印“發生死鎖現象”,可能這里的提示寫的不是很恰當。 
另外可以重復執行m_pMutex->Lock();是因為打印A線程從最開始已經WaitForSingleObject到該互斥量,並且處於有信號狀態,因此該線程可以一直打印,打印9次之后,線程已經關閉(實際上線程在打印完9次之前已經被CloseHandle()了),因此才會出現返回WAIT_ABANDONED 。 
在這里為什么打印D線程又能WaitForSingleObject,使互斥量變為有信號狀態,那可能就需要知道系統會對未釋放核心對象互斥量進行什么處理。從執行結果看,系統又把它變為有信號狀態,讓其他線程可用了。

2、Linux平台

相關頭文件和API

#include<pthread.h> #include<errno.h> //初始化信號量接口,如果使用默認的屬性初始化互斥量, 只需把attr設為NULL. int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restric attr); //銷毀信號量對象接口 int pthread_mutex_destroy(pthread_mutex_t *mutex); //互斥量加鎖接口--阻塞式 //說明:對共享資源的訪問, 要對互斥量進行加鎖, 如果互斥量已經上了鎖, 調用線程會阻塞, 直到互斥量被解鎖。在完成了對共享資源的訪問后, 要對互斥量進行解鎖。 int pthread_mutex_lock(pthread_mutex_t *mutex); //互斥量加鎖接口--非阻塞式 //說明: 這個函數是非阻塞調用模式, 也就是說, 如果互斥量沒被鎖住, trylock函數將把互斥量加鎖, 並獲得對共享資源的訪問權限; 如果互斥量被鎖住了, trylock函數將不會阻塞等待而直接返回EBUSY,表示共享資源處於忙狀態。 int pthread_mutex_trylock(pthread_mutex_t *mutex); //互斥量解鎖接口 int pthread_mutex_unlock(pthread_mutex_t *mutex); //上述所有返回值: 成功則返回0, 出錯則返回錯誤編號。

初始化: 
在Linux下, 線程的互斥量數據類型是pthread_mutex_t. 在使用前, 要對它進行初始化: 
對於靜態分配的互斥量,可以把它設置為PTHREAD_MUTEX_INITIALIZER,或者調用pthread_mutex_init; 
對於動態分配的互斥量, 在申請內存(malloc)之后, 通過pthread_mutex_init進行初始化,並且在釋放內存(free)前需要調用pthread_mutex_destroy;

死鎖: 
死鎖主要發生在有多個依賴鎖存在時, 會在一個線程試圖以與另一個線程相反順序鎖住互斥量時發生。如何避免死鎖是使用互斥量應該格外注意的東西。

總體來講, 有幾個不成文的基本原則:

  • 對共享資源操作前一定要獲得鎖。
  • 完成操作以后一定要釋放鎖。
  • 盡量短時間地占用鎖。
  • 如果有多鎖, 如獲得順序是ABC連環扣, 釋放順序也應該是ABC。
  • 線程錯誤返回時應該釋放它所獲得的鎖。

各種Mutex的區別:

鎖類型 初始化方式 加鎖特征 調度特征
普通鎖 PTHREAD_MUTEX_INITIALIZER 同一線程可重復加鎖,解鎖一次釋放鎖 先等待鎖的進程先獲得鎖
嵌套鎖 PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP 同一線程可重復加鎖,解鎖同樣次數才可釋放鎖 先等待鎖的進程先獲得鎖
糾錯鎖 PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP 同一線程不能重復加鎖,加上的鎖只能由本線程解鎖 先等待鎖的進程先獲得鎖
自適應鎖 PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP 同一線程可重加鎖,解鎖一次生效 所有等待鎖的線程自由競爭

代碼:

/********************************Copyright Qinlong***************************** ** File Name: Mutex.cpp ** Create Date: 2016.11.15 ** Modify Time: 2016.11.16 ** Function: mutex synchornization ** Author: qin long ** Modifier: ** ** Version: 1.0 *******************************************************************************/ #include <iostream> #include <pthread.h> #include <errno.h> using namespace std; //普通鎖 static pthread_mutex_t g_mutex=PTHREAD_MUTEX_INITIALIZER; //循環執行次數 static const int g_iRunTime = 100; void* Fun(void* ptr) { int iRunTime = 0; while(++iRunTime< g_iRunTime) { pthread_mutex_lock(&g_mutex); cout << iRunTime << ": Fun() is running!" << endl; // 若下面一行代碼不注釋,則主函數輸出會出現打印"main trylock failed!", // 原因就在於g_mutex鎖被本線程函數長期占用的結果. // usleep(200); pthread_mutex_unlock(&g_mutex); usleep(100000); } } int main() { pthread_t hHandle; int iRet = pthread_create(&hHandle, NULL, Fun, NULL); //create a thread; if(0 != iRet) { cout << "Create thread failed!" << endl; } sleep(1); int iRunTime = 0; while(++iRunTime<g_iRunTime) { //這里僅僅是為了測試pthread_mutex_trylock的用法 if(EBUSY==pthread_mutex_trylock(&g_mutex)) { cout<< "main trylock failed!"<<endl; --iRunTime; } else { cout <<iRunTime<< ": main is running!" << endl; pthread_mutex_unlock(&g_mutex); usleep(100000); } } pthread_join(hHandle, NULL); return 0; } 

運行結果: 
注釋掉Fun中uSleep(200);的結果如下圖所示, 
這里寫圖片描述

未注釋掉Fun中uSleep(200);的結果如下圖所示, 
這里寫圖片描述
這里運行結果出現了main trylock failed!原因是由於Fun函數在打印輸出完畢后使用uSleep(200)“長時間占用”鎖導致的,從使用pthread_mutex_trylock我們可以看到主函數在經過多次嘗試進行加鎖都失敗了。因此我們的設計原則應該就是盡可能短時間去占用鎖,才能提高多線程之間的運行以及同步效率。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM