Windows線程的同步與互斥


    系統中的所有線程都必須擁有對各種系統資源的訪問權,這些資源包括內存堆棧,串口,文件,窗口和許多其他資源。如果一個線程需要獨占對資源的訪問權,那么其他線程就無法完成它們的工作。反過來說,也不能讓任何一個線程在任何時間都能訪問所有的資源。如果在一個線程從內存塊中讀取數據時,另一個線程卻想要將數據寫入同一個內存塊,那么這就像你在讀一本書時另一個人卻在修改書中的內容一樣。這樣,書中的內容就會被搞得亂七八糟,結果什么也看不清楚。

線程需要在下面兩種情況下互相進行通信:

1.當有多個線程訪問共享資源而不使資源被破壞時。
2.當一個線程需要將某個任務已經完成的情況通知另外一個或多個線程時。

Windows下線程同步互斥常用的幾種方法:

1)CriticalSection: 臨界區

適用范圍: 單一進程的各線程之間用來排它性占有
特性: 不是內核對象,快速而有效。無法監測是否被線程放棄。如果在Critical Sections中間突然程序crash或是exit而沒有調用LeaveCriticalSection,則結果是該線程所對應的內核不能被釋放,該線程成為死線程。
函數: EnterCriticalSection LeaveCriticalSection

很好的封裝:

class CritSect
{
public:
    friend class Lock;
    CritSect() { InitializeCriticalSection(&_critSection); }
    ~CritSect() { DeleteCriticalSection(&_critSection); }
private:
    void Acquire(){ EnterCriticalSection(&_critSection); }
    void Release(){ LeaveCriticalSection(&_critSection); }

    CRITICAL_SECTION _critSection;
};
class Lock
{
public:
     Lock(CritSect& critSect):_critSect(critSect) { _critSect.Acquire(); }
     ~Lock(){ _critSect.Release(); }
private:
    CritSect& _critSect;
};

調用:CritSect sect; Lock lock(sect);

2)Mutex: 互斥內核對象

適用范圍: 不同線程之間用來排它性占有
特性: 核心對象,哪個線程擁有mutex,那么該mutex的ID和此線程的ID一樣。
函數: CreateMutex ReleaseMutex

3)Event: 事件內核對象

適用范圍: 用來控制對象信號的接收,常與信號系統結合起來
特性: 核心對象,有兩種不同類型的事件對象。一種是人工重置的事件,另一種是自動重置的事件。當人工重置的事件得到通知時(signaled),等待該事件的所有線程均變為可調度線程。當一個自動重置的事件得到通知時,等待該事件的線程中只有一個線程變為可調度線程。
Microsoft為自動重置的事件定義了應該成功等待的副作用規則,即當線程成功地調用wait函數等待到該對象時,自動重置的事件就會自動重置到未通知狀態(nonsignaled)。通常沒有必要為自動重置的事件調用ResetEvent()函數,因為系統會自動對事件進行重置。但是,Microsoft沒有為人工重置的事件定義成功等待的副作用,所以需要調用ResetEvent()函數將Event設置為未通知狀態(nonsignaled)。當調用SetEvent觸發Auto-reset的Event條件時,如果沒有被條件阻塞的線程,那么此函數仍然起作用,條件變量會處在觸發狀態(和Linux的pthread_cond_signal()不同)。
函數: CreateEvent OpenEvent PulseEvent SetEvent ResetEvent 

4)Semaphore: 信號內核對象

適用范圍: 用來限制資源占用
特性: 核心對象,沒有擁有者,任何線程都可釋放。信號量(Semaphore)內核對象對線程的同步方式與前面幾種方法不同,它允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數目。在用 CreateSemaphore()創建信號量時即要同時指出允許的最大資源計數和當前可用資源計數。一般是將當前可用資源計數設置為最大資源計數,每增加一個線程對共享資源的訪問,當前可用資源計數就會減1,只要當前可用資源計數是大於0的,就可以發出信號量信號。但是當前可用計數減小到0時則說明當前占用資源的線程數已經達到了所允許的最大數目,不能在允許其他線程的進入,此時的信號量信號將無法發出。線程在處理完共享資源后,應在離開的同時通過 ReleaseSemaphore()函數將當前可用資源計數加1。在任何時候當前可用資源計數決不可能大於最大資源計數。
函數: CreateSemaphore OpenSemaphore ReleaseSemaphore

等待函數

1)WaitForSingleObject()

等待函數可使線程自願進入等待狀態,直到一個特定的內核對象變為已通知狀態為止。

DWORD dw = WaitForSingleObject(hProcess, 5000);
switch(dw)
{
   case WAIT_OBJECT_0:
      // The process terminated.
      break;

   case WAIT_TIMEOUT:
      // The process did not terminate within 5000 milliseconds.
      break;

   case WAIT_FAILED:
      // Bad call to function (invalid handle?)
      break;
}

2)WaitForMultipleObjects()

WaitForMultipleObjects與WaitForSingleObject函數很相似,區別在於它允許調用線程同時查看若干個內核對象的已知狀態。WaitForMultipleObjects函數的返回值告訴調用線程,為什么它會被重新調度。可能的返回值是WAIT_FAILED和WAIT_TIMEOUT,這兩個值的作用是很清楚的。如果為fWaitAll參數傳遞TRUE,同時所有對象均變為已通知狀態,那么返回值是WAIT_OBJECT_0。如果為fWaitAll傳遞FALSE,那么一旦任何一個對象變為已通知狀態,該函數便返回。在這種情況下,你可能想要知道哪個對象變為已通知狀態。返回值是WAIT_OBJECT_0與(WAIT_OBJECT_0+dwCount-1)之間的一個值。換句話說,如果返回值不是WAIT_TIMEOUT,也不是WAIT_FAILED,那么應該從返回值中減去WAIT_OBJECT_0。產生的數字是作為第二個參數傳遞給WaitForMultipleObjects的句柄數組中的索引。該索引說明哪個對象變為已通知狀態。下面是說明這一情況的一些示例代碼:

HANDLE h[3];
h[0] = hProcess1;
h[1] = hProcess2;
h[2] = hProcess3;
DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000);
switch(dw) 
{
   case WAIT_FAILED:
      // Bad call to function (invalid handle?)
      break;

   case WAIT_TIMEOUT:
      // None of the objects became signaled within 5000 milliseconds.
      break;

   case WAIT_OBJECT_0 + 0:
      // The process identified by h[0] (hProcess1) terminated.
      break;

   case WAIT_OBJECT_0 + 1:
      // The process identified by h[1] (hProcess2) terminated.
      break;

   case WAIT_OBJECT_0 + 2:
      // The process identified by h[2] (hProcess3) terminated.
      break;
}

3)SingleObjectAndWait()

DWORD SingleObjectAndWait(HANDLE hObjectToSignal,HANDLE hObjectToWaitOn,DWORD dwMilliseconds,BOOL fAlertable);
函數用於在單個原子方式的操作中發出關於內核對象的通知並等待另一個內核對象:hObjectToSignal參數必須標識一個互斥對象、信號對象或事件對象。hObjectToWaitOn參數用於標識下列任何一個內核對象:互斥對象、信標、事件、定時器、進程、線程、作業、控制台輸入和修改通知。與平常一樣,dwMilliseconds參數指明該函數為了等待該對象變為已通知狀態,應該等待多長時間,而fAlertable標志則指明線程等待時該線程是否應該能夠處理任何已經排隊的異步過程調用。

4)MsgWaitForMultipleObjects(Ex)

MsgWaitForMultipleObjects和MsgWaitForMultipleObjectsEx這些函數與WaitForMultipleObjects函數十分相似。差別在於它們允許線程在內核對象變成已通知狀態或窗口消息需要調度到調用線程創建的窗口中時被調度。
創建窗口和執行與用戶界面相關的任務的線程,應該調用MsgWaitForMultipleObjectsEx函數,而不應該調用WaitForMultipleObjects函數,因為后面這個函數將使線程的用戶界面無法對用戶作出響應。

Windows下的生產者消費者問題:

View Code
#include "StdAfx.h"
#include <windows.h>
#include <stdio.h>

#define BUFFER_SIZE 10
typedef struct Prodcon
{
    int readpos;
    int writepos; //position for reading and writing
    int buffer[BUFFER_SIZE];
}Prodcon;

bool isOver = false;
HANDLE hmutex; 
HANDLE hfullsemaphore; 
HANDLE hemptysemaphore; 

void init(Prodcon * pb)
{
    memset(pb->buffer,0,sizeof(pb->buffer));
    pb->readpos = 0;
    pb->writepos = 0;
}

//store an integer in the buffer
void put(Prodcon* pb,int data)
{
    WaitForSingleObject(hemptysemaphore,INFINITE);
    WaitForSingleObject(hmutex,INFINITE);
    pb->buffer[pb->writepos] = data;
    pb->writepos++;
    pb->writepos %= BUFFER_SIZE;
    ReleaseMutex(hmutex);
    ReleaseSemaphore(hfullsemaphore,1,NULL);
}

//read an integer from the buffer
int get(Prodcon* pb)
{
    int data;
    WaitForSingleObject(hfullsemaphore,INFINITE);
    WaitForSingleObject(hmutex,INFINITE);
    data = pb->buffer[pb->readpos];
    pb->readpos ++;
    pb->readpos %= BUFFER_SIZE;
    ReleaseMutex(hmutex);
    ReleaseSemaphore(hemptysemaphore,1,NULL);                       
    return data;
}

DWORD WINAPI produce(LPVOID lppara)
{
    Prodcon* pb = (Prodcon*)lppara;
    while(1)
    {
        for(int i=1; i<=50; ++i)
        {
            put(pb,i);
            printf("Thread %d put a data: %d\n",GetCurrentThreadId(),i);
            Sleep(10); //producer is fast
        }
        isOver = true;
        break;
    }
    return NULL;
}

DWORD WINAPI consume(LPVOID lppara)
{
    Prodcon* pb = (Prodcon*)lppara;
    while(1)
    {
        int d = get(pb);
        printf("Thread %d get data: %d\n",GetCurrentThreadId(),d);
        if(isOver == true && pb->readpos == pb->writepos)
        {
            printf("OVER!\n");
            break;
        }
        Sleep(100); //consumer is slow
    }
    return NULL;
}

int main()
{
    DWORD writerdata;
    DWORD readerdata;
    DWORD readerdata1;
    Prodcon pb;

    init(&pb);
    hmutex = CreateMutex(NULL,false,NULL);

    //test produce/consume semaphore trigger
    hfullsemaphore = CreateSemaphore(NULL,0,BUFFER_SIZE,NULL);
    hemptysemaphore = CreateSemaphore(NULL,BUFFER_SIZE,BUFFER_SIZE,NULL);
    
    if(CreateThread(NULL,0,produce,&pb,0,&writerdata)==NULL)
        return -1;
    if(CreateThread(NULL,0,consume,&pb,0,&readerdata)==NULL)
        return -1;
    if(CreateThread(NULL,0,consume,&pb,0,&readerdata1)==NULL)
        return -1;


    char ch;
    while(1)
    {
        ch = getchar(); //press "e" to exit
        if(ch == 'e') break;
    }

    printf("Program ends successfully\n");
    CloseHandle(hmutex);
    CloseHandle(hfullsemaphore);
    CloseHandle(hemptysemaphore);

    return 0;
}

 

參考資料:
http://msdn2.microsoft.com/en-us/library/ms686360(VS.85).aspx
http://www.cppblog.com/mzty/archive/2008/07/29/57470.html

 


免責聲明!

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



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