1. 程式人生 > >互斥鎖、臨界區和事件

互斥鎖、臨界區和事件

一.互斥鎖的介紹

互斥鎖用於控制多個執行緒對他們之間共享資源互斥訪問的一個訊號量。也就是說是為了避免多個執行緒在某一時刻同時操作一個共享資源。 例如執行緒池中的有多個空閒執行緒和一個任務佇列。 任何是一個執行緒都要使用互斥鎖互斥訪問任務佇列,以避免多個執行緒同時訪問任務佇列以發生錯亂。 在某一時刻,只有一個執行緒可以獲取互斥鎖,在釋放互斥鎖之前其他執行緒都不能獲取該互斥鎖。 如果其他執行緒想要獲取這個互斥鎖,那麼這個執行緒只能以阻塞方式進行等待。

這就是像廁所只有一個坑,必須一個一個上的原理。

win32實現一個互斥鎖:

#include "stdafx.h"
#include<Windows.h>
#include<iostream>
using namespace std;
//互斥鎖
HANDLE hMutex1;
int flag;

DWORD WINAPI MyThread2(LPVOID lpParamter)
{
    while (1)
    {
		//沒上鎖的話就自己鎖上,否則等著
        WaitForSingleObject(hMutex1,INFINITE);
        flag=!flag;
        cout << "MyThread1 Runing :"<<"執行緒2"<<" "<<flag<< endl;
        Sleep(1000);
		//解鎖
        ReleaseMutex(hMutex1);
    }
}

DWORD WINAPI MyThread1(LPVOID lpParamter)
{
    while (1)
    {	
		//沒上鎖的話就自己鎖上,否則等著
        WaitForSingleObject(hMutex1,INFINITE);
        flag=!flag;
        cout << "MyThread2 Runing"<<"執行緒1" <<" "<<flag<< endl;
        Sleep(10);
		//解鎖
        ReleaseMutex(hMutex1);
	}
}

int _tmain()
{
    //建立一個鎖
    hMutex1  =CreateMutex(NULL,FALSE,NULL);
    HANDLE hThread1 = CreateThread(NULL, 0, MyThread1, NULL, 0, NULL);
    CloseHandle(hThread1);


	HANDLE hThread2 = CreateThread(NULL, 0, MyThread2, NULL, 0, NULL);
	CloseHandle(hThread2);
	while(1);
	return 0;

}

二.臨界區介紹

指的是一個訪問共用資源的程式片段,而這些共用資源又無法同時被多個執行緒訪問的特性。 當有執行緒進入臨界區段時,其他執行緒或是程序必須等待,有一些同步的機制必須在臨界區段的進入點與離開點實現, 以確保這些共用資源是被互斥獲得使用。其實跟互斥鎖差不多.

重點介紹臨界區

InitializeCriticalSection 初始化臨界區
DeleteCriticalSection	  刪除臨界區
EnterCriticalSection      進入臨界區
LeaveCriticalSection	  離開臨界區

win32例子:

#include "stdafx.h"
#include<Windows.h>
#include<iostream>

using namespace std;



int flag;
typedef CRITICAL_SECTION UPF_thread_mutex_t;

class CGfxMutex
{
public:
	CGfxMutex() 
	{
		::InitializeCriticalSection(&lock_);
	}
	~CGfxMutex() 
	{
		::DeleteCriticalSection(&lock_);
	}
	void Lock()
	{
		::EnterCriticalSection(&lock_);
	}
	void Unlock()
	{
		::LeaveCriticalSection(&lock_);
	}
	// 臨界區結構物件
	UPF_thread_mutex_t lock_;
};

CGfxMutex *mutex;
DWORD WINAPI MyThread2(LPVOID lpParamter)
{
	while (1)
	{
		mutex->Lock();
		flag=!flag;
		cout << "MyThread1 Runing :"<<"執行緒2"<<" "<<flag<< endl;
		Sleep(900);
		mutex->Unlock();
	}
};

DWORD WINAPI MyThread1(LPVOID lpParamter)
{
	while (1)
	{	
		mutex->Lock();
		flag=!flag;
		cout << "MyThread2 Runing"<<"執行緒1" <<" "<<flag<< endl;
		Sleep(1000);
		mutex->Unlock();
	}
};
void main()
{	
	flag = 1;
	//建立一個鎖
	mutex = new CGfxMutex;
	HANDLE hThread1 = CreateThread(NULL, 0, MyThread1, NULL, 0, NULL);
	CloseHandle(hThread1);


    HANDLE hThread2 = CreateThread(NULL, 0, MyThread2, NULL, 0, NULL);
    CloseHandle(hThread2);
    while(1);
    return;


}

臨界區和互斥鎖的區別: 1、臨界區只能用於物件在同一程序裡執行緒間的互斥訪問;互斥體可以用於物件程序間或執行緒間的互斥訪問。 2、臨界區是非核心物件,只在使用者態進行鎖操作,速度快;互斥體是核心物件,在核心態進行鎖操作,速度慢。 3、臨界區和互斥體在Windows平臺都下可用;Linux下只有互斥體可用

windows平臺:

InitializeCriticalSection 初始化臨界區 DeleteCriticalSection 刪除臨界區 EnterCriticalSection 進入臨界區 LeaveCriticalSection 離開臨界區

CreateMutex 建立鎖 WaitForSingleObject 沒上鎖的話就自己鎖上,否則等著 ReleaseMutex 釋放鎖 CloseHandle 銷燬鎖

linux平臺(linux平臺使用的是互斥鎖,沒有臨界區) 建立 pthread_mutex_init 銷燬 pthread_mutex_destroy 加鎖 pthread_mutex_lock 解鎖 pthread_mutex_unlock

三.關於條件的使用

API:

HANDLE CreateEvent( ​ LPSECURITY_ATTRIBUTESl pEventAttributes,// 安全屬性 ​ BOOL bManualReset,// 復位方式 ​ BOOL bInitialState,// 初始狀態 ​ LPCTSTRl pName // 物件名稱 );

pEventAttributes: ​ 一個指向SECURITY_ATTRIBUTES結構的指標,確定返回的控制代碼是否可被子程序繼承。如果lpEventAttributes是NULL,此控制代碼不能被繼承。 Windows NT/2000:lpEventAttributes的結構中的成員為新的事件指定了一個安全符。如果lpEventAttributes是NULL,事件將獲得一個預設的安全符。 bManualReset: ​ 指定將事件物件建立成手動復原還是自動復原。如果是TRUE,那麼必須用ResetEvent函式來手工將事件的狀態復原到無訊號狀態。 如果設定為FALSE,當一個等待執行緒被釋放以後,系統將會自動將事件狀態復原為無訊號狀態。

bInitialState[輸入] ​ 指定事件物件的初始狀態。如果為TRUE,初始狀態為有訊號狀態;否則為無訊號狀態。

pName ​ 指定事件的物件的名稱,是一個以0結束的字串指標。名稱的字元格式限定在MAX_PATH之內。名字是對大小寫敏感的。 如果lpName指定的名字,與一個存在的命名的事件物件的名稱相同,函式將請求EVENT_ALL_ACCESS來訪問存在的物件。 這時候,由於bManualReset和bInitialState引數已經在建立事件的程序中設定,這兩個引數將被忽略。 如果lpEventAttributes是引數不是NULL,它將確定此控制代碼是否可以被繼承,但是其安全描述符成員將被忽略。 如果lpName為NULL,將建立一個無名的事件物件。 如果lpName的和一個存在的訊號、互斥、等待計時器、作業或者是檔案對映物件名稱相同,函式將會失敗, 在GetLastError函式中將返回ERROR_INVALID_HANDLE。造成這種現象的原因是這些物件共享同一個名稱空間。 終端服務(Terminal Services):名稱中可以加入"Global"或是"Local"的字首,這樣可以明確的將物件建立在全域性的或事務的名稱空間。 名稱的其它部分除了反斜槓(\),可以使用任意字元。

BOOL SetEvent(HANDLE hEvent); 設定事件的狀態為有標記,釋放任意等待執行緒。如果事件是手工的,此事件將保持有標記直到呼叫ResetEvent,這種情況下將釋放多個執行緒; 如果事件是自動的,此事件將保持有標記,直到一個執行緒被釋放,系統將設定事件的狀態為無標記; 如果沒有執行緒在等待,則此事件將保持有標記,直到一個執行緒被釋放。 ​

BOOL ResetEvent(HANDLE hEvent); 這個函式把指定的事件物件設定為無訊號狀態。

WaitForSingleObject 等待事件

具體細節,現用現查吧,這些基本就夠用了。 windows平臺 建立 CreateEvent 銷燬 CloseHandle 觸發 SetEvent 廣播 SetEvent / ResetEvent 等待 WaitForSingleObject

linux平臺 建立 pthread_cond_init 銷燬 pthread_cond_destroy 觸發 pthread_cond_signal 廣播 pthread_cond_broadcast 等待 pthread_cond_wait / pthread_cond_timedwait

demo:

#include "stdafx.h"
#include<Windows.h>
#include<iostream>

using namespace std;

HANDLE hEvent; //使用手動重置為無訊號狀態,初始化時有訊號狀態

DWORD WINAPI MyThread2(LPVOID lpParamter)
{
	while (1)
	{
		WaitForSingleObject(hEvent,INFINITE); //有訊號時才能得到
		cout << "MyThread1 Runing :"<<"執行緒2"<<" "<<""<< endl;
		Sleep(900);
		//ResetEvent(hEvent);//重置為無訊號狀態
	}
};

DWORD WINAPI MyThread1(LPVOID lpParamter)
{
	while (1)
	{	

```
	cout << "MyThread2 Runing"<<"執行緒1" <<" "<<""<< endl;
	Sleep(1000);
	SetEvent(hEvent);

}
```

};

void main()
{	

    hEvent = CreateEvent(NULL,TRUE,FALSE, NULL);
    HANDLE hThread1 = CreateThread(NULL, 0, MyThread1, NULL, 0, NULL);
    CloseHandle(hThread1);

    HANDLE hThread2 = CreateThread(NULL, 0, MyThread2, NULL, 0, NULL);
    CloseHandle(hThread2);
    while(1);
    return;


}