第8章 用戶模式下的線程同步(3)_Slim讀寫鎖(SRWLock)

8.5 Slim讀/寫鎖(SRWLock)——輕量級的讀寫鎖windows

(1)SRWLock鎖的目的數組

  ①容許讀者線程同一時刻訪問共享資源(由於不存在破壞數據的風險)函數

  ②寫者線程應獨佔資源的訪問權,任何其餘線程(含寫入的線程)要等這個寫者線程訪問完才能得到資源。性能

(2)SRWlock鎖的使用方法測試

  

  ①初始化SRWLOCK結構體 InitializeSRWLock(PSRWLOCK pSRWLock);ui

  ②寫者線程調用AcquireSRWLockExclusive(pSRWLock);以排它方式訪問spa

     讀者線程調用AcquireSRWLockShared以共享方式訪問線程

  ③訪問完畢後,寫者線程調用ReleaseSRWLockExclusive解鎖。讀者線程要調用ReleaseSRWLockShared解鎖指針

  ④注意SRWLock不須要刪除和銷燬,因此不用Delete之類的,系統會自動清理。code

(3)SRWLock鎖的共享規則

  ①若當前鎖的狀態是「寫」(即某個線程已經得到排它鎖),這時其餘線程,無論是申請讀或寫鎖的線程,都會被阻塞在AcquireSRWLock*函數中讀鎖或寫鎖等待計數加1。

  ②若當前鎖的狀態是「讀」(即某個(些)線程已經得到了共享鎖)。

  A、若是新的線程申請寫鎖,則此時它將被掛起,鎖的寫等待計數加1。直至當前正在讀鎖的線程所有結束,而後系統會喚醒正在等待寫的線程,即申請排他鎖要在沒有任何其餘鎖的時候才能返回。

  B、若是新的線程申請讀鎖,若此時沒有寫線程正在等待,則容許讀鎖進入而不會被阻塞。若是有寫鎖正在等待,則寫鎖優先獲得鎖新線程進入等待,讀鎖計數加1(這樣作的目的是讓寫鎖有機會進入)。

(4)SRWLock與臨界區的不一樣

  ①不存在TryEnter(Shared/Exclusive)SRWLock之類的函數;若是鎖己經被佔用,那麼調用AcquireSRWLock(Shared/Exclusive)會阻塞調用線程。(如排它寫入時,鎖會被獨佔)(課本這說法已不適用了,在Win7之後系統提供了TryAcquireSRWLock(Exclusive/Shared)等函數,能夠實現這需求了)

  ②不能遞歸得到SRWLock,即一個線程不能爲了屢次寫入資源而屢次鎖定資源,再屢次調用ReleaseSRWLock*來釋放鎖。

【SRWLock程序】

 

#include <windows.h>
#include <tchar.h>
#include <locale.h>
#include <time.h>

//////////////////////////////////////////////////////////////////////////
const int g_iThreadCnt = 20;
int g_iGlobalValue = 0;

//////////////////////////////////////////////////////////////////////////
SRWLOCK g_sl = { 0 };

//////////////////////////////////////////////////////////////////////////
DWORD WINAPI ReadThread(PVOID pParam);
DWORD WINAPI WriteThread(PVOID pParam);

//////////////////////////////////////////////////////////////////////////
int _tmain()
{
    _tsetlocale(LC_ALL, _T("chs"));

    srand((unsigned int)time(NULL));
    //讀寫鎖只需初始化,不須要手動釋放,系統會自行處理
    InitializeSRWLock(&g_sl);

    HANDLE aThread[g_iThreadCnt];
    DWORD  dwThreadId = 0;
    SYSTEM_INFO si = { 0 };
    GetSystemInfo(&si);

    for (int i = 0; i < g_iThreadCnt;i++){
        if (0 == rand()%2)
            aThread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)WriteThread, NULL,
                                      CREATE_SUSPENDED,&dwThreadId);
        else 
            aThread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ReadThread, NULL,
                                     CREATE_SUSPENDED, &dwThreadId);
        SetThreadAffinityMask(aThread[i],1<<(i % si.dwNumberOfProcessors));
        ResumeThread(aThread[i]);
    }

    //等待全部線程結束
    WaitForMultipleObjects(g_iThreadCnt,aThread,TRUE,INFINITE);

    for (int i = 0; i < g_iThreadCnt;i++){
        CloseHandle(aThread[i]);
    }

    _tsystem(_T("PAUSE"));
    return 0;
}
////////////////////////////////////////////////////////////////////////////
////不加保護的讀寫
//DWORD WINAPI ReadThread(PVOID pParam)
//{
//    _tprintf(_T("Timestamp[%u]:Thread[ID:0x%x]讀取全局變量值爲%d\n"), 
//             GetTickCount(),GetCurrentThreadId(),g_iGlobalValue);
//    return 0;
//}
//
////////////////////////////////////////////////////////////////////////////
//DWORD WINAPI WriteThread(PVOID pParam)
//{
//
//    for (int i = 0; i <= 4321; i++){
//        g_iGlobalValue = i;
//        //模擬一個時間較長的處理過程
//        for (int j = 0; j < 1000; j++);
//    }
//
//    //咱們的要求是寫入的最後數值應該爲4321,全局變量未被保護
//    //其中線程可能讀取0-4321的中間值,這不是咱們想要的結果!
//    _tprintf(_T("Timestamp[%u]:Thread[ID:0x%x]寫入數據值爲%d\n"),
//             GetTickCount(), GetCurrentThreadId(), g_iGlobalValue);
//    return 0;
//}

//////////////////////////////////////////////////////////////////////////
DWORD WINAPI ReadThread(PVOID pParam)
{
    //以共享的訪問讀
    __try{
        AcquireSRWLockShared(&g_sl);

        //讀出來的全局變量要麼是0,要麼是4321。不可能有其餘值
        //當讀線程第1個被調度時,會讀到0.但一旦寫線程被調度,之後全部的
        //讀取的值都會是4321
        _tprintf(_T("Timestamp[%u]:Thread[ID:0x%X]讀取全局變量值爲%d\n"),
                 GetTickCount(), GetCurrentThreadId(), g_iGlobalValue);
    }
    __finally{
        ReleaseSRWLockShared(&g_sl);
    }

    return 0;
}


//////////////////////////////////////////////////////////////////////////
DWORD WINAPI WriteThread(PVOID pParam)
{
    //寫時以排它的方式
    __try{
        AcquireSRWLockExclusive(&g_sl);
        for (int i = 0; i <= 4321; i++){
            g_iGlobalValue = i;
            SwitchToThread(); //故意切換到其餘線程,很明顯
                              //在這個循環的期間,因排它方式
                              //因此其它訪問鎖的線程會被掛起
                              //從而沒法讀或寫。
        }

        //咱們的要求是寫入的最後數值應該爲4321,全局變量未被保護
        //其中線程可能讀取0-4321的中間值,這不是咱們想要的結果!
        _tprintf(_T("Timestamp[%u]:Thread[ID:0x%X]寫入數據值爲%d\n"),
                 GetTickCount(), GetCurrentThreadId(), g_iGlobalValue);
    }
    __finally{
        ReleaseSRWLockExclusive(&g_sl);
    }

    return 0;
}

(5)SRWLock鎖與其餘鎖的比較——實驗:對同一個全局變量加不一樣類型鎖,同時讓每一個線程進行1000000次讀寫性能的比較(時間單位爲ms

線程數

Volatile

讀取

Volatile

寫入

InterLocked

遞增

臨界區

SRWLock

共享

SRWLock

排它

互斥量

Mutex

1

39

42

57

84

86

88

1098

2

42

54

80

126

150

120

5774

4

95

119

161

283

236

236

11589

  ①InterLockedIncrement比Volatile讀寫慢是由於CPU必須鎖定內存,但InterLocked函數只能同步一些簡單的整型變量。

  ②臨界區與SRWLock鎖效率差很少,但建議用SRWLock代替臨界區,由於該鎖容許多個線程同時讀取,對那些只須要讀取共享資源線程來講,這提升了吞吐量

  ③內核對象的性能最差,這是由於等待、釋放互斥量須要在用戶模式與內核模式之間切換。

  ④若是考慮性能,首先應該嘗試不要共享數據,而後依次是Volatile讀寫、InterLock函數、SRWLock鎖、臨界區。當這些條件都不知足時,再使用內核對象。

【UserSyncCompare程序】比較不一樣類型鎖的性能

 

/***********************************************************************
Module: UserSyncCompare.cpp
Notices: Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
***********************************************************************/

#include "../../CommonFiles/CmnHdr.h"
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <locale.h>

//////////////////////////////////////////////////////////////////////////
// 晶振計時類,基於主板晶振頻率的時間戳計數器的時間類(見第7章)
class CStopWatch {
public:
    CStopWatch() { QueryPerformanceFrequency(&m_liPerfFreq); Start(); }

    void Start() { QueryPerformanceCounter(&m_liPerfStart); }

    //返回計算自調用Start()函數以來的毫秒數
    __int64 Now() const {
        LARGE_INTEGER liPerfNow;
        QueryPerformanceCounter(&liPerfNow);
        return(((liPerfNow.QuadPart - m_liPerfStart.QuadPart) * 1000)
               / m_liPerfFreq.QuadPart);
    }

private:
    LARGE_INTEGER m_liPerfFreq;   // Counts per second
    LARGE_INTEGER m_liPerfStart;  // Starting count
};

//////////////////////////////////////////////////////////////////////////
DWORD g_nIterations = 1000000;  //疊代次數
typedef void(CALLBACK* OPERATIONFUNC)();

DWORD WINAPI ThreadInterationFunction(PVOID operationFunc){
    OPERATIONFUNC op = (OPERATIONFUNC)operationFunc;

    for (DWORD iteration = 0; iteration < g_nIterations;iteration++){
        op();
    }
    return 0;
}
//////////////////////////////////////////////////////////////////////////
//MeasureConcurrentOperation函數
//功能:測試一組並行線程的性能
//參數:szOperationName——操做的名稱
//      nThreads——測試線程的數量
//      ofnOperationFunc——具體以哪一種方式讀、寫操做的函數
void MeasureConcurrentOperation(TCHAR* szOperationName,DWORD nThreads,
       OPERATIONFUNC opfOperationFunc)
{
    HANDLE* phThreads = new HANDLE[nThreads];
    //將當前線程的優先級提到「高」
    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
    for (DWORD i = 0; i < nThreads;i++){
        phThreads[i] = CreateThread(NULL, 0, 
                                    ThreadInterationFunction, //線程函數
                                    opfOperationFunc,//線程函數的參數(是個函數指針)
                                    0,      //當即運行
                                    NULL);
    }
    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
    CStopWatch watch;

    //等待全部線程結束
    WaitForMultipleObjects(nThreads, phThreads, TRUE, INFINITE);

    __int64 elapsedTime = watch.Now();

    _tprintf(_T("線程數=%u,毫秒=%u,測試類型=%s\n"),
             nThreads,(DWORD)elapsedTime,szOperationName);

    //別忘了關閉全部的線程句柄,並刪除線程數組
    for (DWORD i = 0; i < nThreads; i++){
        CloseHandle(phThreads[i]);
    }
    delete phThreads;
}

//////////////////////////////////////////////////////////////////////////
//測試列表:
//一、不加同步鎖,對整型volatile變量直接讀取
//二、使用InterlockedIncrement對整型變量進行寫操做
//三、使用臨界區對整型volatile變量進行讀取
//四、使用SRWLock鎖對整型volatile變量進行讀寫操做
//五、使用互斥對象Mutex對整型volatile變量進行讀取操做

volatile LONG gv_value = 0;

//一、直接讀寫volatile型的整型變量
//'lValue':是個局部變量,己初始化,但未被引用,編譯器
//會出現警告,可禁用該警告
#pragma warning(disable:4189)
void WINAPI VolatileReadCallBack()
{
    LONG lValue = gv_value;
}
#pragma warning(default:4189)

void WINAPI VolatileWriteCallBack()
{
    gv_value = 0;
}

//二、使用InterlockedIncrement對整型變量進行寫操做
void WINAPI InterlockedIncrementCallBack()
{
    InterlockedIncrement(&gv_value);
}

//三、使用臨界區對整型volatile變量進行讀取
CRITICAL_SECTION g_cs;
void WINAPI CriticalSectionCallBack()
{
    EnterCriticalSection(&g_cs);
    gv_value = 0;
    LeaveCriticalSection(&g_cs);
}

//四、使用SRWLock鎖對整型volatile變量進行讀寫操做
SRWLOCK  g_srwLock;
void WINAPI SRWLockReadCallBack()
{
    AcquireSRWLockShared(&g_srwLock);
    gv_value = 0;
    ReleaseSRWLockShared(&g_srwLock);
}

void WINAPI SRWLockWriteCallBack()
{
    AcquireSRWLockExclusive(&g_srwLock);
    gv_value = 0;
    ReleaseSRWLockExclusive(&g_srwLock);
}

//五、使用互斥對象Mutex對整型volatile變量進行讀取操做
HANDLE g_hMutex;
void WINAPI MutexCallBack()
{
    WaitForSingleObject(g_hMutex, INFINITE);
    gv_value = 0;
    ReleaseMutex(g_hMutex);
}

//////////////////////////////////////////////////////////////////////////
int  _tmain()
{
    _tsetlocale(LC_ALL, _T("chs"));

    //分別測試當線程總數爲一、二、4時各類鎖切換所花費的時間
    for (int nThreads = 1; nThreads <= 4;nThreads *= 2){
        //一、直接讀寫
        MeasureConcurrentOperation(_T("Volatile Read"), nThreads, VolatileReadCallBack);
        MeasureConcurrentOperation(_T("Volatile Write"), nThreads, VolatileWriteCallBack);
        
        //二、InterlockedIncrement
        MeasureConcurrentOperation(_T("Interlocked Increment"), nThreads, InterlockedIncrementCallBack);

        //三、臨界區:
        InitializeCriticalSection(&g_cs);//初始化臨界區
        MeasureConcurrentOperation(_T("Critical Section"), nThreads, CriticalSectionCallBack);
        DeleteCriticalSection(&g_cs);

        //四、SRWLock鎖:
        InitializeSRWLock(&g_srwLock);//初始化SRWLock鎖,注意不須要釋放,系統會自行回收
        MeasureConcurrentOperation(_T("SRWLock Read"), nThreads, SRWLockReadCallBack);
        MeasureConcurrentOperation(_T("SRWLock Write"), nThreads, SRWLockWriteCallBack);
        
        //五、互斥對象:
        g_hMutex = CreateMutex(NULL, false, NULL);//準備互斥對象
        MeasureConcurrentOperation(_T("Mutex"), nThreads, MutexCallBack);
        CloseHandle(g_hMutex);
        _tprintf(_T("\n"));
    }
    return 0;
}
相關文章
相關標籤/搜索