線程同步之原子操做與旋轉鎖

鎖操做:爲了確保操做區域的安全。通常有旋轉鎖、讀寫鎖、原子操做。數組

原子操做:原子操做是指不會被線程調度機制打斷的操做;這種操做一旦開始,就一直運行到結束,中間不會有任何 context switch (切換到另外一個線程)。
原子操做是不可分割的,在執行完畢以前不會被任何其它任務或事件中斷。安全

例子:++i是否屬於原子操做
答:不是原子操做
解析:
i++分爲三個階段:
  1.內存到寄存器
  2.寄存器自增
  3.寫回內存
這三個階段中間均可以被中斷分離開,因此不屬於原子操做。數據結構

當咱們利用線程須要對一個全局變量進行累加、減操做時,怕被中斷,可用原子操做。函數

int g_nCount = 0; spa

InterlockedExchangeAdd((long*)&g_nCount, 3);
對全局變量&g_nCount每次進行+3操做。線程


==InterlockedExchangeSubtract((long*)&g_nCount, 2)
InterlockedExchangeAdd((long*)&g_nCount, -2);
此兩函數等價。可是InterlockedExchangeSubtract()最終也會調用InterlockedExchangeAdd()此函數。所以能夠直接+(-2)。指針


InterlockedIncrement((long*)&g_nCount);
//對變量每次進行+1操做
InterlockedDecrement((long*)&g_nCount);
//對變量每次進行-1操做
這兩個函數都是固定+(-)1,不能夠自擬。code

 

旋轉鎖(Spin Lock):旋轉鎖是一種非阻塞鎖,由某個線程獨佔。採用旋轉鎖時,等待線程並不靜態地阻塞在同步點,而是必須「旋轉」,
不斷嘗試直到最終得到該鎖。blog

適用:
旋轉鎖多用於多處理器系統中。這是由於,若是在單核處理器中採用旋轉鎖,當一個線程正在「旋轉」時,將沒有執行資源可供另外一釋放鎖的線程使用。
旋轉鎖適合於任何鎖持有時間少於將一個線程阻塞和喚醒所需時間的場合。線程控制的變動,包括線程上下文的切換和線程數據結構的更新。隊列

危害:
在線程調用其餘子系統時,線程不該持有旋轉鎖。對旋轉鎖的不當使用可能會致使線程餓死,所以需謹慎使用這種鎖機制。旋轉鎖致使的餓死問題可以使用排隊技術來解決,
即每一個等待線程按照先進先出的順序或者隊列結構在一個獨立的局部標識上進行旋轉

好比:進屋子,若是門開着的,就能夠直接進入房子同時將反鎖,不容許其餘人進行。當本身離開房子時,再將門打開,等待下一我的。

函數解析:
InterlockedExchange((long *)&g_bIsUsing, true)
原型:
LONG InterlockedExchange(
_Inout_ LONG volatile *Target,
_In_ LONG Value
);
InterlockedExchange(a,b)能以原子操做的方式交換倆個參數a, b,並返回a之前的值;由於InterlockedExchange 是原子函數,不會要求停止中斷,
因此交換指針的方式是安全的。


問題:傳遞線程參數時,爲何保存在數組中,而不是直接傳遞i?
解析:
在循環中,咱們建立線程並傳遞的參數是i=1後,主程序有可能在執到下一次循環時,第一次的ThreadProc函數仍未執行,而此時的i已經=2了,若是ThreadProc再來調用
int nThreadNo = *(int*)lParam;語句時,顯然不是咱們想要的結果。
解決方法:
可使用靜態數組來保存所要傳遞的參數。此時,全部參數均保存在threadId數組中。

源代碼:
#include "stdafx.h"
#include <Windows.h>
#include <process.h>

#define THREADNUM 10

int g_nCount = 0;

bool g_bIsUsing = false;

unsigned _stdcall ThreadProc(void *lParam)
{
    int nThreadNo = *(int*)lParam;
    
    //等待訪問共享資源
    while (InterlockedExchange((long *)&g_bIsUsing, true) == true)
        Sleep(0);
    
    //訪問共享資源
    printf("線程%d開始執行\n", nThreadNo);
    printf("線程%d結束執行\n", nThreadNo);
    
    //結束訪問
    InterlockedExchange((long *)&g_bIsUsing, false);
    
    //原子操做
    //InterlockedExchangeAdd((long*)&g_nCount, 3);
    //InterlockedExchangeAdd((long*)&g_nCount, -2);

    //InterlockedDecrement((long*)&g_nCount);
    //InterlockedIncrement((long*)&g_nCount);
    return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{

    HANDLE pThreads[THREADNUM];

    //保存當前執行線程的ID
    int threadId[THREADNUM];

    //啓動線程
    for (int i = 0; i < THREADNUM; ++i)
    {
        threadId[i] = i;
        pThreads[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadProc, threadId+i, 0, 0);
        if (pThreads[i] == 0)
        {
            continue;
            i--;
        }
        
        //休眠,可保證上一線程執行完成。即按序號執行
        //Sleep(100);

    }

    
    WaitForMultipleObjects(THREADNUM, pThreads, TRUE, INFINITE);
    
    //經過原子操做,最後累加結果
    //printf("g_nCount:%d", g_nCount);

    //釋放資源
    for (int i = 0; i < THREADNUM; ++i)
    {
        CloseHandle(pThreads[i]);
    }

    getchar();
    return 0;
}
相關文章
相關標籤/搜索