多线程同步
1.為什么使用多線程
多個線程同時訪問同一個全局變量,如果都是讀取操作,則不會出現問題。如果一個線程負責改變此變量的值,而其他線程負責同時讀取變量內容,則不能保證讀取到的數據是經過寫線程修改后的。
2.多線程缺陷
創建競爭條件是多線程或并發編程的主要缺陷之一,另一個缺陷就是死鎖。
?
避免競爭條件:4種
a.不要使用可能同時位于臨界區內的兩個進程
b.不要依賴對CPU的速度做出的任何假設
c.不要讓臨界區外部停止的進程阻塞其他進程
d.不要讓進城等待任意長的時間進入其臨界區。
?
死鎖必須的先行條件:4個
a.進程聲明排他性控制他們需求的資源
b.進程在等待其他資源的釋放時占有資源
c.資源無法強行從進程中刪除
d.存在一個循環等待條件
3.多線程同步
同步原因:?
多個線程同時訪問同一個全局變量,如果都是讀取操作,則不會出現問題。如果一個線程負責改變此變量的值,而其他線程負責同時讀取變量內容,則不能保證讀取到的數據是經過寫線程修改后的。
用戶模式與內核模式線程同步機制比較:
| ? | 用戶模式 | 內核模式 |
| 優點 | 線程同步機制速度快 ? | 支持多個進程之間的線程同步 防止死鎖 |
| 缺點 | 容易陷入死鎖狀態 多個進程之間的線程同步會出現問題。(比如競爭資源、死鎖) | 線程同步機制速度慢 線程必須從用戶模式轉為內核模式。這個轉換需要很大的代價:往返一次需要占用x?8?6平臺上的大約1?0?0?0個C?P?U周期 |
?
用戶模式:
臨界區:CRITICAL_SECTION,CCriticalSection類?一段獨占對某些共享資源訪問的代碼,在任意時刻只允許一個線程對共享資源進行訪問。臨界區同步速度很快,但卻只能用來同步本進程內的線程,而不可用來同步多個進程中的線程。
?
內核對象:
由于這種同步機制使用了內核對象,使用時必須將線程從用戶模式切換到內核模式,而這種轉換一般要耗費近千個CPU周期,因此同步速度較慢,但在適用性上卻要遠優于用戶模式的線程同步方式。
事件:
HANDLE,CEvent類,線程通信時可以使用過事件內核對象來進行線程間的通信,除此之外,事件內核對象也可以通過通知操作的方式來保持線程的同步。
使用臨界區只能同步同一進程中的線程,而使用事件內核對象則可以對進程外的線程進行同步,其前提是得到對此事件對象的訪問權,可以通過OpenEvent()函數獲取得到。如果事件對象已創建(在創建事件時需要指定事件名),函數將返回指定事件的句柄。對于那些?在創建事件時沒有指定事件名的事件內核對象,可以通過使用內核對象的繼承性或是調用DuplicateHandle()函數來調用?CreateEvent()以獲得對指定事件對象的訪問權。在獲取到訪問權后所進行的同步操作與在同一個進程中所進行的線程同步操作是一樣的。
信號量:
HANDLE,CSemaphore類?信號量(Semaphore)它允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數目。在用CreateSemaphore()創建信號量?時即要同時指出允許的最大資源計數和當前可用資源計數。一般是將當前可用資源計數設置為最大資源計數,每增加一個線程對共享資源的訪問,當前可用資源計數?就會減1,只要當前可用資源計數是大于0的,就可以發出信號量信號。但是當前可用計數減小到0時則說明當前占用資源的線程數已經達到了所允許的最大數目,?不能在允許其他線程的進入,此時的信號量信號將無法發出。線程在處理完共享資源后,應在離開的同時通過ReleaseSemaphore()函數將當前可?用資源計數加1。
互斥:HANDLE,CMutex類?Mutex是一種用途非常廣泛的內核對象。能夠保證多個線程對同一共享資源的互斥訪問。在編寫程序時,互斥對象多用在對那些為多個線程所訪問的內存塊的保護上,可以確保任何線程在處理此內存塊時都對其擁有可靠的獨占訪問權。
?
MFC提供了多種同步對象,下面我們只介紹最常用的四種:?
·?臨界區(CCriticalSection)?
·?事件(CEvent)?
·?互斥量(CMutex)?
·?信號量(CSemaphore)
///
//?tt.cpp?:?定義控制臺應用程序的入口點。
//
#include?"stdafx.h"
#include?<Windows.h>
#include?<stdio.h>
#include?<conio.h>
#include?<time.h>
?
int?gTest=0;
#define?THREADNUM?4
//臨界區
CRITICAL_SECTION?cs;
//事件句柄
HANDLE?hEvent?=?NULL;
//信號量對象句柄
HANDLE?hSemaphore;
//?互斥對象
HANDLE?hMutex?=?NULL;
?
?
void?fun(int?tNum)
{
for?(int?i=0;i<1000*1000*1;i++)
{
//等待互斥對象通知
WaitForSingleObject(hMutex,?INFINITE);
//試圖進入信號量關口
//WaitForSingleObject(hSemaphore,?INFINITE);
//等待事件置位
//WaitForSingleObject(hEvent,?INFINITE);
//臨界區
//EnterCriticalSection(&cs);//進入臨界區,其它線程則無法進入
?
//gTest++;
printf("%d--1234567890123456789012345678901234567890123456789012345678901234567890\n",GetCurrentThreadId());
//gTest--;
//LeaveCriticalSection(&cs);//離開臨界區,其它線程可以進入
?
//處理完成后即將事件對象置位
//SetEvent(hEvent);
//ResetEvent(hEvent);
//釋放信號量計數
//ReleaseSemaphore(hSemaphore,?1,?NULL);
//釋放互斥對象
ReleaseMutex(hMutex);
}
}
int?_tmain(int?argc,?_TCHAR*?argv[])
{
//創建互斥對象
hMutex?=?CreateMutex(NULL,FALSE,NULL);
?
//創建信號量對象
hSemaphore?=?CreateSemaphore(NULL,2,2,NULL);
?
/*
創建的Event是自動復位還是人工復位.如果true,人工復位,一旦該Event被設置為有信號,則它一直會等到ResetEvent()API被調用時才會恢復為無信號.?
如果為false,Event被設置為有信號,則當有一個wait到它的Thread時,??該Event就會自動復位,變成無信號.
如果想在每次調用WaitForSingleObject后讓WINDOWS為您自動地把事件地狀態恢復為”無信號”狀態,必須把該參數設為FALSE,否則,您必須每次調用ResetEvent函數來清除事件的信號。
這里有兩個API函數用來修改事件對象的信號狀態:SetEvent和ResetEvent。前者把事件對象設為”有信號”狀態,而后者正好相反。
在事件對象生成后,必須調用WaitForSingleObject來讓線程進入等待狀態.
*/
//創建事件對象
hEvent?=?CreateEvent(NULL,?FALSE,?FALSE,?NULL);
//?事件置位
SetEvent(hEvent);
?
//初始化臨界區
//InitializeCriticalSection(&cs);?
?
DWORD?threadId[THREADNUM];
HANDLE?hThread[THREADNUM];
long?c1=clock();
for?(int?i=0;i<THREADNUM;i++)
{
hThread[i]=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)fun,(LPVOID)i,0,&(threadId[i]));
}
DWORD?wm=WaitForMultipleObjects(THREADNUM,hThread,true,INFINITE);
long?c2=clock();
printf("耗費時間%d\n",c2-c1);
?
//刪除臨界區
//DeleteCriticalSection(&cs);?
//刪除事件對象
//CloseHandle(hEvent);
//刪除信號量
CloseHandle(hSemaphore);
?
return?0;
}
///
?
?
知識點:
可以使用WaitForSingleObject函數來等待一個內核對象變為已通知狀態:DWORD?WaitForSingleObject(
HANDLE?hObject,?//指明一個內核對象的句柄
DWORD?dwMilliseconds);?//等待時間
還可以使用WaitForMulitpleObjects函數來等待多個內核對象變為已通知狀態:
DWORD?WaitForMultipleObjects(
DWORD?dwCount,?//等待的內核對象個數
CONST?HANDLE*?phObjects,?//一個存放被等待的內核對象句柄的數組
BOOL?bWaitAll,?//是否等到所有內核對象為已通知狀態后才返回
DWORD?dwMilliseconds);?//等待時間
該函數的第一個參數指明等待的內核對象的個數,可以是0到MAXIMUM_WAIT_OBJECTS(64)中的一個值。
等你需要通知一個互斥內核對象并等待一個事件內核對象的時候,可以這么寫:
ReleaseMutex(hMutex);
WaitForSingleObject(hEvent,?INFINITE);
可是,這樣的代碼不是以原子的方式來操縱這兩個內核對象。因此,可以更改如下:
SignalObjectAndWait(hMutex,?hEvent,?INFINITE,?FALSE);
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
在用戶線程/主線程中推薦MsgWaitForMultipleObjects代替WaitForSingleObject和WaitForMultipleObjects()函數?
在多線程編程中,通常都需要線程間的同步,一個線程要等待另一個線程的事件才繼續執行,一般的做法是采用WaitForSingleObject和WaitForMultipleObjects()函數來實現。但在實際的應用中,經常出現等待線程卡死的狀況,也就是說等待的事件一直無效。為什么事件一直無效呢?很多的情況是等待線程阻塞了另外的線程,使另外的線程無法設置事件有效。為什么會阻塞呢?原因就比較多了,需要具體問題具體分析。?WaitForSingleObject和WaitForMultipleObjects()都是阻塞函數,事件無效就一直不返回,從而阻塞該線?程,使該線程無法處理其他的事務,如果其他的線程發送消息過來,將得不到處理而不返回,從而將其他的線程也阻塞,造成相互等待,這就是臭名昭著的“死?鎖”!!!
??微軟提供了另外一個函數可以解決該問題,它就是MsgWaitForMultipleObjects()函數,該函數不但可以等待事件,還可以等待消息,從而處理消息,使線程不阻塞。
MsgWaitForMultipleObjectsEx
函數功能:阻塞時仍可以響應消息
函數原型?
DWORD?MsgWaitForMultipleObjectsEx(?
DWORD?nCount,?//?句柄數組中句柄數目
LPHANDLE?pHandles,?//?指向句柄數組的指針?
DWORD?dwMilliseconds,?//?以毫秒計的超時值?
DWORD?dwWakeMask,?//?要等待的輸入事件類型?
DWORD?dwFlags?//?等待標志?
);?
?
參數?
nCount,指定pHandles指向的數組中的對象句柄數目。最大對象數目是MAXIMUM_WAIT_OBJECTS-1?
pHandles?,指向一個對象句柄數組。要得到可以使用的對象句柄類型清單,請查看備注部分。數組中可以包含多種對象類型。?Windows?NT:?數組中句柄必須擁有SYNCHRONIZE訪問權。要得到更多相關信息,請查閱MSDN中Standard?Access?Rights。?
dwMilliseconds?,指定以毫秒計的超時值。即使參數dwWakeMask與dwFlags中指定的條件未滿足,超時后函數仍然返回。如果dwMilliseconds值為0,函數測試指定的對象狀態并立即返回。如果dwMilliseconds值為INFINITE,函數超時周期為無窮大。?
dwWakeMask?,指定被加到對象句柄數組中的輸入事件對象句柄的對象類型。這個參數可以是下面列出值的任意組合:?
值含義
QS_ALLEVENTS?
?WM_TIMER,?WM_PAINT,?WM_HOTKEY輸入消息或登記消息(posted?message)在消息隊列中
QS_ALLINPUT?
?任何消息在消息隊列中?
QS_ALLPOSTMESSAGE
?登記消息(在此處列出的除外)在消息隊列中
QS_HOTKEY?
?WM_HOTKEY消息在消息隊列中?
QS_INPUT
?輸入消息在消息隊列中
?
QS_KEY?
?WM_KEYUP,WM_KEYDOWN,WM_SYSKEYUP或WM_SYSKEYDOWN消息在消息隊列中
QS_MOUSE?
?WM_MOUSEMOVE消息或鼠標點擊消息(WM_LBUTTONUP,WM_RBUTTONDOWN等)在消息隊列中
QS_MOUSEBUTTON?
?鼠標點擊消息(WM_LBUTTONUP,WM_RBUTTONDOWN等)在消息隊列中
QS_MOUSEMOVE?
?WM_MOUSEMOVE消息在消息隊列中
QS_PAINT?
?WM_PAINT消息在消息隊列中
QS_POSTMESSAGE
?登記消息(在此處列出的除外)在消息隊列中?
QS_SENDMESSAGE?
?由另一個線程或應用發送的消息在消息隊列中?
QS_TIMER
?WM_TIMER消息在消息隊列中
?
特別是在主線程和界面線程中推薦使用該函數,可以避免很多麻煩!!!
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
線程間通訊
1.使用全局變量通信
解決線程間通信最簡單的一種方法是使用全局變量。對于標準類型的全局變量,我們建議使用volatile?修飾符,它告訴編譯器無需對該變量作任何的優化,即無需將它放到一個寄存器中,并且該值可被外部改變。如果線程間所需傳遞的信息較復雜,我們可以定義一個結構,通過傳遞指向該結構的指針進行傳遞信息。
2.使用自定義消息通信
我們可以在一個線程的執行函數中向另一個線程發送自定義的消息來達到通信的目的。一個線程向另外一個線程發送消息?是通過操作系統實現的。利用Windows操作系統的消息驅動機制,當一個線程發出一條消息時,操作系統首先接收到該消息,然后把該消息轉發給目標線程,?接收消息的線程必須已經建立了消息循環。?
?
總結
- 上一篇: 关于afxbeginthread时Wai
- 下一篇: 标准模板库(STL)学习指南之set集合