線程同步總結--臨界區 事件 互斥量 信號量

在WIN32中,同步機制主要有以下幾種:

  • 臨界區(Critical section)

  • 事件(Event);

  • 互斥量(mutex);

  • 信號量(semaphore);

 

臨界區(Critical section)

臨界區(Critical Section)指的是一個訪問共用資源(例如:共用設備或是共用存儲器)的程序片段,而這些共用資源有無法同時被多個線程訪問的特性。

在有一個線程進入臨界區後,其他所有試圖訪問此臨界區的線程將被掛起,並一直持續到進入臨界區的線程離開。臨界區在被釋放後,其他線程可以繼續搶佔,並以此達到用原子方式操作共享資源的目的。

臨界區可以用於線程間的互斥,但不可以用於同步。

除臨界區外,事件、互斥量和信號量都是內核對象。

一旦線程進入一個臨界區,則它就可以一再的重複進入該臨界區,當然每個進入操作都必須對應離開操作。

初始化

銷燬

進入臨界區

離開臨界區

InitializeCriticalSection

DeleteCriticalSection

EnterCriticalSection

LeaveCriticalSection

也就是EnterCriticalSection( ),可以嵌套。

但是千萬不要在臨界區中調用 sleep(),或任何 Wait..() 函數。

臨界區的缺點是:沒有辦法知道進入臨界區中的那個線程是生是死。如果那個線程在進入臨界區後當掉了,而且沒有退出來,那麼系統就沒有辦法消除掉此臨界區。

 

事件(Event)

 用來通知線程有一些事件已發生,從而啓動後繼任務的開始。

       在創建事件對象時可以設置爲“自動重置事件”或“手動重置事件”。當一個手動重置事件被觸發的時候,正在等待該事件的所有線程都將變成可調度狀態;而當一個自動重置事件對象被觸發的時候,只有一個正在等待該事件的線程會變成可調度狀態,隨後該事件對象自動變爲未觸發態,但是如果被觸發時沒有等待該事件對象的線程,那麼該事件對象會保持觸發狀態,直至遇到第一個等待該事件對象的線程。

創建事件

銷燬事件

獲得事件句柄

觸發事件

使事件未觸發

CreateEvent

CloseHandle

OpenEvent

SetEvent

ResetEvent

舉例:

//使用事件機制同步線程的例子
//設置三個線程,一個主線程,一個讀線程和一個寫線程,
//讀線程必須在寫線程寫之後才能讀,主線程必須在讀線程讀之後才能結束
/*
實現:定義兩個事件, evRead, evFinish;讀線程等待evRead, 主線程等待evFinish.
*/
#include <iostream>
#include <Windows.h>
#include <process.h>
using namespace std;
HANDLE evRead, evFinish;
void ReadThread(void* param){
    //等待讀事件
    WaitForSingleObject(evRead, INFINITE);
    //讀操作
    cout << "reading" << endl;
    //激發事件evFinish
    SetEvent(evFinish);
}
void WriteThread(void* param){
    //寫操作
    cout << "writing" << endl;
    //喚醒讀事件
    SetEvent(evRead);
}
int main()
{
    //創建兩個事件,並初始化爲未激發狀態
    evRead = CreateEvent(NULL, false, false, NULL);
    evFinish = CreateEvent(NULL, false, false, NULL);
    
    //創建讀線程和寫線程
    _beginthread(ReadThread, 0, NULL);
    _beginthread(WriteThread, 0, NULL);
    //等待事件evFinish
    WaitForSingleObject(evFinish, INFINITE);
    cout << "the program is end." << endl;
    return 0;
}

 

互斥量(mutex)

互斥量(mutex)是指用來防止兩條線程同時對同一公共資源(比如全局變量)進行讀寫的機制。

互斥量跟臨界區很相似,只有擁有互斥對象的線程才具有訪問資源的權限由於互斥對象只有一個,因此就決定了任何情況下此共享資源都不會同時被多個線程所訪問。當前佔據資源的線程在任務處理完後應將擁有的互斥對象交出,以便其他線程在獲得後得以訪問資源。互斥量比臨界區複雜。因爲使用互斥不僅僅能夠在同一應用程序不同線程中實現資源的安全共享,而且可以在不同應用程序的線程之間實現對資源的安全共享。 

創建互斥量

打開互斥量

觸發互斥量

清理互斥量

CreateMutex

OpenMutex

ReleaseMutex

CloseHandle

Mutexes 用途和 Critical Section 非常類似,線程擁有 mutex 就好象線程進入 critical section 一樣,但是它犧牲速度以增加彈性

一旦沒有任何線程擁有那個 mutex,這個 mutex 便處於激發狀態。

它與臨界區的區別是:

1. Mutexes 操作要比 Critical Section 費時的多。

2. Mutexes 可以跨進程使用,Critical Section 則只能在同一進程中使用。

3. 等待一個 Mutex 時,你可以指定"結束等待"的時間長度,而 Critical Section 則不行。

說明:

1. Mutex 的擁有權:

Mutex 的擁有權並非屬於那個產生它的線程,而是那個最後對些 Mutex 進行 WaitXXX() 操作並且尚未進行 ReleaseMutex() 操作的線程。

2. Mutex 被捨棄:

如果線程在結束前沒有調用 ReleaseMutex(),比如線程調用了 EXitThread() 或者因爲當掉而結束。這時的 mutex 不會被摧毀,而是被視爲"未被擁有"以及"未被激發"的狀態,在下一個 WaitXXX() 中線程會被以WAIT_ABANDONED_0 (WAIT_ABANDONED_0_n + 1 )來通知。

3. 最初擁有者:

CreateMutex(),第二個參數 bInitialOwner,允許你指定現行線程是否立刻擁有產生出來的 mutex。

示例:

第一個程序:創建互斥量並等待用戶輸入後就觸發互斥量。

#include <stdio.h>
#include <conio.h>
#include <windows.h>
const char MUTEX_NAME[] = "Mutex_MoreWindows";
int main()
{
    HANDLE hMutex = CreateMutex(NULL, TRUE, MUTEX_NAME); //創建互斥量
    printf("互斥量已經創建,現在按任意鍵觸發互斥量\n");
    getch();
    //exit(0);
    ReleaseMutex(hMutex);
    printf("互斥量已經觸發\n");
    CloseHandle(hMutex);
    return 0;
}

第二個程序:先打開互斥量,成功後就等待並根據等待結果作相應的輸出。

#include <stdio.h>
#include <windows.h>
const char MUTEX_NAME[] = "Mutex_MoreWindows";
int main()
{
    HANDLE hMutex = OpenMutex(MUTEX_ALL_ACCESS, TRUE, MUTEX_NAME); //打開互斥量
    if (hMutex == NULL)
    {
        printf("打開互斥量失敗\n");
        return 0;
    }
    printf("等待中....\n");
    DWORD dwResult = WaitForSingleObject(hMutex, 20 * 1000); //等待互斥量被觸發
    switch (dwResult)
    {
    case WAIT_ABANDONED:
        printf("擁有互斥量的進程意外終止\n");
        break;
    case WAIT_OBJECT_0:
        printf("已經收到信號\n");
        break;
    case WAIT_TIMEOUT:
        printf("信號未在規定的時間內送到\n");
        break;
    }
    CloseHandle(hMutex);
    return 0;
}

 

信號量Semaphore

信號量Semaphore用於保持在0至指定最大值之間的一個計數值。

信號量對象對線程的同步方式與前面幾種方法不同,信號量允許多個線程同時使用共享資源 ,這與操作系統中的PV操作相同。它指出了同時訪問共享資源的線程最大數目。它允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數目。在用CreateSemaphore()創建信號量時即要同時指出允許的最大資源計數和當前可用資源計數。一般是將當前可用資源計數設置爲最大資源計數,每增加一個線程對共享資源的訪問,當前可用資源計數就會減1,只要當前可用資源計數是大於0的,就可以發出信號量信號。但是當前可用計數減小到0時則說明當前佔用資源的線程數已經達到了所允許的最大數目, 不能在允許其他線程的進入,此時的信號量信號將無法發出。線程在處理完共享資源後,應在離開的同時通過ReleaseSemaphore()函數將當前可用資源計數加1。在任何時候當前可用資源計數決不可能大於最大資源計數。 

創建信號量

銷燬

打開信號量

遞增計數

遞減計數

CreateSemaphore

CloseHandl

OpenSemaphore

ReleaseSemaphore

WaitForSingleObject

 在經典多線程問題中設置一個信號量和一個臨界區。用信號量處理主線程與子線程的同步,用關鍵段來處理各子線程間的互斥。詳見代碼:

#include <stdio.h>
#include <process.h>
#include <windows.h>
long g_nNum;
unsigned int __stdcall Fun(void *pPM);
const int THREAD_NUM = 10;
//信號量與關鍵段
HANDLE            g_hThreadParameter;
CRITICAL_SECTION  g_csThreadCode;
int main()
{
    printf("     經典線程同步 信號量Semaphore\n");
    printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
    //初始化信號量和關鍵段
    g_hThreadParameter = CreateSemaphore(NULL, 0, 1, NULL);//當前0個資源,最大允許1個同時訪問
    InitializeCriticalSection(&g_csThreadCode);
    HANDLE  handle[THREAD_NUM];    
    g_nNum = 0;
    int i = 0;
    while (i < THREAD_NUM)
    {
        handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
        WaitForSingleObject(g_hThreadParameter, INFINITE);//等待信號量>0
        ++i;
    }
    WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
    
    //銷燬信號量和關鍵段
    DeleteCriticalSection(&g_csThreadCode);
    CloseHandle(g_hThreadParameter);
    for (i = 0; i < THREAD_NUM; i++)
        CloseHandle(handle[i]);
    return 0;
}
unsigned int __stdcall Fun(void *pPM)
{
    int nThreadNum = *(int *)pPM;
    ReleaseSemaphore(g_hThreadParameter, 1, NULL);//信號量++
    Sleep(50);//some work should to do
    EnterCriticalSection(&g_csThreadCode);
    ++g_nNum;
    Sleep(0);//some work should to do
    printf("線程編號爲%d  全局資源值爲%d\n", nThreadNum, g_nNum);
    LeaveCriticalSection(&g_csThreadCode);
    return 0;
}

 

 總結
1. 互斥量與臨界區的作用非常相似,但互斥量是可以命名的,也就是說它可以跨越進程使用。所以創建互斥量需要的資源更多,所以如果只爲了在進程內部是用的話使用臨界區會帶來速度上的優勢並能夠減少資源佔用量 。因爲互斥量是跨進程的互斥量一旦被創建,就可以通過名字打開它。 
2. 互斥量(Mutex),信號量(Semaphore),事件(Event)都可以被跨越進程使用來進行同步數據操作,而其他的對象與數據同步操作無關,但對於進程和線程來講,如果進程和線程在運行狀態則爲無信號狀態,在退出後爲有信號狀態。所以可以使用WaitForSingleObject來等待進程和 線程退出。 
3. 通過互斥量可以指定資源被獨佔的方式使用,但如果有下面一種情況通過互斥量就無法處理,比如現在一位用戶購買了一份三個併發訪問許可的數據庫系統,可以根據用戶購買的訪問許可數量來決定有多少個線程/進程能同時進行數據庫操作,這時候如果利用互斥量就沒有辦法完成這個要求,信號燈對象可以說是一種資源計數器

參考文章:

https://blog.csdn.net/morewindows/article/details/7538247

https://blog.csdn.net/sunshinewave/article/details/50851061

 

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