c++多線程筆記3 線程同步機制

線程同步機制


由於線程共享同一進程的內存空間,多個線程可能需要同時訪問同一個數據。如果沒有正確的保護措施,對共享數據的訪問會造成數據的不一致和錯誤。
常用的幾種同步機制:

全局變量、臨界區(critical section)、信號量(simphore)、互斥量(mutex)、管程(monitor)

1 全局變量

全局變量:進程中的所有線程均可以訪問所有的全局變量,因而全局變量成爲Win32多線程通信的最簡單方式。

全局變量使用舉例:

#include<stdio.h>
#include <windows.h>
bool threaddone=false;
DWORD WINAPI helloFunc(LPVOIDarg)
printr("Hello Thread\n");o
return 0;
main()
{
HANDLE hThread= CreateThread(NULL,0, helloFunc, NULL,0,
NULL);
while(threaddone);
}
//舉例二,在這個例子裏面主線程一直運行直到附線程執行一次改變全局變量。
#include "stdafx.h"
#include <windows. h>
#include <iostream>
using namespace std;
int globalvar=false:I/同步全局變量
DWORD WINAPI ThreadFunc(LPVOID pParam)
{
 cout<<"ThreadFunc"<<endl;
 Sleep(200);
 Beep(5000,2000)
 globalvar= true;
 return 0;
 }
int main()
{
HANDLE hthread=CreateThread(NULL,0, ThreadFunc,NUL,0,NULL);//創建新線/
if(hthread)
  {
    cout<<"Thread Create Error!"<<endl;
    CloseHandle(hthread);
  }
while(lglobalvar)
    cout<<"Thread while"<<endl;//主線程運行
cout<<"Thread exit"<<endl;
return 0;
}

2 臨界區(critical section)

臨界區同步:臨界區是指包含有共享資源的一段代碼塊,而且這些共享資源和多個線程之間都存在相關關係。臨界區也成爲同步塊,每個臨界區都有一個入口點和一個出口點。在進行臨界區同步時,多個線程對臨界區資源的訪問只能有一個,線程停留在臨界區,此時其他的線程將被掛起。

使用臨界區的原則:
每次只允許一個線程處於它的臨界區(CS)中
若有多個線程同時想進入CS,應在有限時間內讓其中一個線程進入CS,以免阻塞
線程在CS內只能逗留有限時間;
不應使要進入CS的線程無限期地等待在CS之外;
在CS之外的線程不可以阻止其他進程進入CS;
不要預期和假定線程進展的相對速度以及可用的處理器數目,因爲這是不可預期的;

■CRITICAL_SECTION cs(變量可自定義);定義臨界區
■InitializeCriticalSection(&cs);初始化臨界區
■EnterCriticalSection(&cs);進入臨界區
■LeaveCriticalSection(&cs);離開臨界區
■ DeleteCriticalSection(&cs);刪除臨界區

臨界區程序設計的方法如下:

DWORD WINAPI ThreadProc (PVOID pParam)

{

EnterCriticalSection(&cs);//進入臨界區,鎖定共享資源

//data writing;//共享資源的操作

LeaveCriticalSection(&cs);//退出臨界區,釋放鎖定

}

臨界區同步舉例:

#include <stdio.h>
#include<windows.h>
HANDLE evDone; int nun;
CRITICAL_SECTION csDone;
DWORD WINAPI helloFunc(LPVOIDarg) //多線程處理函數
{
EnterCriticalSection(&csDone);
printf("NUM=%d", num++);
LeaveCriticalSection(&csDone);
SetEvent(evDone);
return 0;
}
main()
{
 num=0;
 evDone=CreateEvent(NULL, FALSE, FALSE,NULL);//這裏創建了個事件來控制同步
 InitializeCriticalSection(&csDone);
 HANDLE hThread=_beginThread(helloFunc,0,NULL);
 WaitForSingleObject(evDone, INFINITE);//等待事件結束,目的是防止主線程先退出
 DeleteCriticalSection(&csDone);
 cout<<"DONE"<<endl;
}
//這裏不使用事件寫WaitForSingleObject(hThread, INFINITE);效果一樣

3 互斥量(mutex)

互斥量:Win32 的互斥量 Mutex 屬於內和對象,它的用途和臨界區的使用十分類似,同樣是確保在同一個時間內只有一個線程擁有 Mutex,即在同一時間內只有唯一的一個線程能夠訪問共享資源。

互斥量相關函數:
創建互斥量:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTESIpMutexAttributes,//安全屬性結構指針
BOOLblnitialOwner,/是否佔有該互斥量,TRUE:佔有,FALSE:不佔有
LPCTSTR lpName/信號量的名稱
)
釋放互斥量:
BOOL WINAPI ReleaseMutex(HANDLE hMutex);

使用互斥編程的一般方法是:
void UpdateResource()

{

WaitForSingleObject(hMutex,…);
./do something
ReleaseMutex(hMutex);
}
互斥量舉例說明:

//互斥量同步編程示例
/******************使用互斥量同步代碼************************/
#include<windows.h>
#include <stdio.h>
#define WORK_THREAD_COUNT 10
HANDLE hMutex;
DWORD WINAPI ThreadProc(LPVOID)
{
	//等待互斥量信號
	WaitForSingleObject(hMutex, INFINITE);
	//保護區
	printf("Thread %d:獲取互斥量….處理完畢\n", GetCurrentThreadId());
	Sleep(100);
	ReleaseSemaphore(hMutex, 1, NULL); //釋放互斥量
		return 0;
}
int main(int argc, TCHAR*argV)
{
	HANDLE hThread[WORK_THREAD_COUNT];
	DWORD ThreadID;
	hMutex = CreateMutex(NULL,FALSE, NULL);
	for (int i = 0; i < WORK_THREAD_COUNT; i++)
		hThread[i] = CreateThread(NULL, 0, ThreadProc, NULL, 0, &ThreadID);//創建十個線程

	WaitForMultipleObjects(WORK_THREAD_COUNT, hThread, TRUE, INFINITE);
	for (int i = 0; i < WORK_THREAD_COUNT; i++)
		CloseHandle(hThread[i]);

	CloseHandle(hMutex);
	return 0;
}

(1)互斥對象的運行速度比關鍵代碼段要慢;
(2)不同進程中的多個線程能夠訪問單個互斥對象;
(3)線程在等待訪問資源時可以設定一個超時值。Mutex 對象的狀態在它不被任
何線程擁有時纔有信號,而當它被擁有時則無信號。Mutex 對象很適合用來協調多個線
程對共享資源的互斥訪問。

4 信號量(semaphore)

信號量同步:
信號對象是內核對象,它擁有一個計數器,可用來控制對多個線程對共享資源進行
訪問,在創建對象時指定最大可同時訪問的線程數。當一個線程申請訪問成功後,信號
對象中的計數器減1,調用ReleaseSemaphore函數後,信號對象中的計數器加1。其中,
計數器值大於或等於0,但小於或等於創建時指定的最大值。如果一個應用在創建一個
信號對象時,將其計數器的初始值設爲0,就阻塞了其他線程,保護了資源。等初始化
完成後,調用ReleaseSemaphore 函數將其計數器增加至最大值,則可進行正常的存取訪問。
互斥量同步的相關函數:
創建信號對象:
HANDLE CreateSemaphore (
PSECURITY_ATTRIBUTE psa,//安全屬性。
LONG InitialCount,//初值,開始時可供使用的資源數
LONG IMaximumCount,//最大資源數
PCTSTR pszName)//Seaphone名稱(字符串)
當信號量存在時打開一個信號對象:
HANDLE OpenSemaphore(
DWORD fdwAccess,
BOOLblnherithandle,
PCTSTR pszName
);
共享資源訪問完成後,釋放對信號對象的佔用:
BOOL WINAPI ReleaseSemaphore(
HANDLE hSemaphore,
LONGIReleaseCount, //信號量的當前資源數增加ReleaseCount
LPLONG IpPreviousCount
);
信號量的特點和用途可用下列幾句話定義:
(1)如果當前資源的數量大於O,則信號量有效;
(2)如果當前資源數量是0,則信號量無效;
(3)系統決不允許當前資源的數量爲負值;
(4)當前資源數量決不能大於最大資源數量。

信號量舉例:下面的示例代碼創建12個線程,信號量允許同時啓用的線程數爲4個,在
輸出結果時我們可以看到,屏幕將每4個一組輸出後延時,再處理其他的線程。

/********** 使用信號量同步代碼***************/
#include<windows.h>
#include<stdio.h>
#define MAX_SEM_COUNT 4
#define WORK_THREAD_COUNT 12
HANDLE hSemaphore;
DWORD WINAPI ThreadProc(LPVOID)
{
WaitForSingleObject(hSemaphore, INFINITE); // 等待 Semaphore 信號量 > 0
//保護下
printf("Thread %d:獲取信號量….處理完畢\n", GetCurrentThreadId());
Sleep(1000);
ReleaseSemaphore(hSemaphore, 1, NULL); //釋放信號量, 信號對象計數器加1
return 0;
}
void main()
{
HANDLE hThread[WORK_THREAD_COUNT];
DWORD ThreadID;
hSemaphore = CreateSemaphore(NULL,MAX_SEM_COUNT,MAX_SEM_COUNT,NULL);

for (int i = 0; i < WORK_THREAD_COUNT; i++)
	hThread[i] = CreateThread(NULL,0,ThreadProc,NULL,0,&ThreadID);

WaitForMultipleObjects(WORK_THREAD_COUNT, hThread, TRUE, INFINITE);

for (int i = 0; i < WORK_THREAD_COUNT; i++ )
	CloseHandle(hThread[i]); 
CloseHandle(hSemaphore);
}

5 事件同步(event)

事件對象的最大特點是:它完全是在你的控制之下的。不像互斥量和信號量一樣,
互斥量和信號量的狀態都會因爲WaiteForSingleObject()函數的調用而變化,事件對象的可以讓你完全控制它的狀態。
創建事件對象:
HANDLE CreateEvent (LPSECURITY_ATTRIBUTESlpEventAttributes,
BOOLbManualReset,
OOLblnitialState,//初始狀態
LPCTSTRIpName//事件的名稱
);
說明:

IpEventAttributes: SECURITY_ATTRIBUTES結構指;
bManualReset: //手動(TRUE)自動設置(FALSE)
事件對象創建函數中,如果第二個參數是手工重置事件(TRUE),那麼WaitForSingleObject()函數的調用不會影響它的狀態,它將總是保持有信號狀態,直到用 ResetEvent 函數重置成無信號的事件。如果是自動重置事件,那麼它的狀態在
WaitForSingleObject()調用後會自動變爲無信號的。

打開事件對象:
HANDLE OpenEvent(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR IpName);

BOOLSetEvent(HANDLE hEvent)
BOOLResetEvent(HANDLE hEvent);
BOOLPulseEvent(HANDLE hEvent);

事件同步舉例

/****** 使用事件同步代碼****/
#include<windows.h>
#include<stdio.h>
HANDLE hEvent; // 事件對象
DWORD WINAPI Thread1(LPVOID)
{
	WaitForSingleObject(hEvent, INFINITE);//等待事件激活
	printf("ThreadI Workingin");
	return 0;
}
DWORD WINAPI Thread2(LPVOID)
{
	printf("激活Threadl\n");
	Sleep(500);
	SetEvent(hEvent); // 激活事件對象
		return 0;
}
int main(int argc, TCHAR*argVJ)
{
	HANDLE hThread[2];
	hEvent = CreateEvent(NULL,TRUE, FALSE,NULL);
	hThread[0] = CreateThread(NULL,0, Thread1, NULL, 0,NULL);
	hThread[1] = CreateThread(NULL,0, Thread2, NULL, 0,NULL);
	WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
	CloseHandle(hEvent);
	return 0;
}

6 注意事項

死鎖:兩個線程分別擁有鎖,並等待對方的鎖

死鎖避免方法:線程以相同次序進行加鎖、非阻塞的加鎖操作

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章