windwos線程同步的幾種方式

大家都知道開多線程的的目的是爲了提高程序的處理效率,但是多線程會存在訪問同一個資源會出現數據混亂,當然多個線程只讀這一個共享資源肯定是沒有問題的,但是修改的話肯定會有問題,所以需要同步的概念windows提供了多個同步對象

同步模式有兩種:

     1三環(用戶模式):原子操作, 關鍵段
     2內核同步對象:事件, 信號, 互斥體

     三環的同步手段,效率明顯高於內核同步對象

1原子操作

  • 除了當前線程之外,沒有其他任何線程會同時訪問該資源
    • 單值操作:只操作單個變量
int g_nVal = 0;

DWORD WINAPI ThreadFunc(LPVOID lpParam)
{
  for (int i = 0; i < 0x10000; ++i)
  {
    //++g_nVal;
    InterlockedExchangeAdd((LPLONG)&g_nVal, 1);//原子操作加法
  }

  printf(" \r\n %08x \r\n", g_nVal);
  return 0;
}

2關鍵段/臨界區

通過給線程中的部分區域加鎖解鎖,保證該線程對此資源的獨有訪問權。

語法

  • InitializeCriticalSection(); //初始化關鍵段(臨界區)
  • EnterCriticalSection() 進入臨界區(加鎖)
    • 如果其他線程正佔用CRITICAL_SECTION ,則等待,線程阻塞在這裏直到獲取所有權限纔會繼續執行,如果沒有佔用,則返回, 並上鎖(獲取到所有權)
    • 在同一個線程重複調用,會增加引用計數,需要對應次數的LeaveCriticalSection來解鎖,否則CRITICAL_SECTION 一直是佔用狀態(一直是上鎖狀態),其他線程就會獲取不到所有權
  • TryEnterCriticalSection嘗試加鎖
    • 返回值若爲TRUE,進入關鍵段,並上鎖
    • 返回值若爲FALSE,其他線程在使用,無論它是否獲得臨界區的所有權,線程不會阻塞在這裏
  • LeaveCriticalSection()解鎖

#include <stdio.h>
#include <windows.h>
CRITICAL_SECTION cs;
int g_nVal = 0;
DWORD WINAPI ThreadFunc(LPVOID lpParam)
{

  for (int i = 0; i < 0x10000; ++i)
  {
    //檢查cs是否是佔用狀態,如果佔用(鎖着),則等待,如果沒有鎖着,則返回, 並上鎖
    //EnterCriticalSection(&cs);
    //TRUE, 獲得所有權, FALSE,其他線程在使用中,EnterCriticalSection和TryEnterCriticalSection區別在於 TryEnterCriticalSection立即返回,無論它是否獲得臨界區的所有權,線程不會阻塞在這裏
    if (TryEnterCriticalSection(&cs))
    {
      ++g_nVal;
      //解鎖,讓出所有權
      LeaveCriticalSection(&cs);
    }
    else
    {
      //因爲線程不會阻塞,他會繼續執行,所以會++i,要等線程切換回來的時候,他的執行循環次數是原來切換的時候,所以要--
      --i;
    }
  }

  printf("%x\n", g_nVal);
  return 0;
}
int main()
{

  DWORD dwThreadId, dwThrdParam = 1;
  HANDLE hThread;
  char szMsg[80];

  //初始化臨界區
  InitializeCriticalSection(&cs);

  // 在同一個線程調用,會增加引用計數,幾次enter,需要對應次數的leave,否則鎖一直是上鎖狀態,其他線程就會獲取不到所有權
  EnterCriticalSection(&cs);
  EnterCriticalSection(&cs);
  EnterCriticalSection(&cs);

  hThread = CreateThread(
    NULL,
    0,
    ThreadFunc,
    &dwThrdParam,//線程參數
    0,
    &dwThreadId);

  hThread = CreateThread(
    NULL,
    0,
    ThreadFunc,
    &dwThrdParam,//線程參數
    0,
    &dwThreadId);

  Sleep(10000);
  LeaveCriticalSection(&cs);
  LeaveCriticalSection(&cs);
  LeaveCriticalSection(&cs);
  system("pause");
  DeleteCriticalSection(&cs);
}

 

3內核同步對象

概念

內核同步對象有兩種狀態,通過他的兩種狀態來達到線程的同步

  • state(狀態)
    • signaled :已觸發
    • no-signal :未觸發
  • process/thread
    • signaled :結束運行
    • no-signal :正在運行

檢測狀態方式:
          WaitForSingleObject -- 等待一個對象,如果對象的狀態是signaled就結束等待,函數繼續向下執行
          WaitForMultipleObjects  -- 等待多個對象  

  • WaitForSingleObject等待一個對象
if (!CreateProcess(NULL, // No module name (use command line). 
    szParam, // Command line. 
    NULL,             // Process handle not inheritable. 
    NULL,             // Thread handle not inheritable. 
    FALSE,            // Set handle inheritance to FALSE. 
    CREATE_NEW_CONSOLE,                // No creation flags. 
    NULL,             // Use parent's environment block. 
    NULL,             // Use parent's starting directory. 
    &si,              // Pointer to STARTUPINFO structure.
    &pi)             // Pointer to PROCESS_INFORMATION structure.
)
{
printf("CreateProcess failed.");
return 0;
}

printf("開始等待 \r\n");

while (true)
{    
    //參數1等待對象的句柄,參數2等待時間
    DWORD dwRet = WaitForSingleObject(pi.hProcess, 1000);
if (dwRet == WAIT_OBJECT_0)
{
    printf("子進程結束 \r\n");
    break;
}
else if (dwRet == WAIT_TIMEOUT)
{
    printf("等待超時 \r\n");
}
  • WaitForMultipleObjects等待多個對象
DWORD dwThreadId, dwThrdParam = 1;
const int nCountOfThread = 5;
HANDLE hThreads[nCountOfThread];
char szMsg[80];

//創建多線程
for (int i = 0; i < nCountOfThread; ++i)
{
    hThreads[i] = CreateThread(
      NULL,                   
      0,                   
      ThreadFunc,            
      (LPVOID)i,              
      0,                     
      &dwThreadId);            
}

DWORD dwRet = WaitForMultipleObjects(
  nCountOfThread,//句柄數組數量
  hThreads,//句柄數組
  FALSE,          //是否等待所有對象
  INFINITE);//等待時間,INFINITE表示一直等待

1事件(Event)

  • CreateEvent 創建事件對象
  • SetEvent 將事件對象設置爲已觸發(開鎖)狀態
  • ResetEvent 將事件對象設置爲未觸發(關鎖)狀態
  • OpenEvent 通過名字打開一個事件對象
  • CloseHandle 關閉事件
Handle hEvent = CreateEvent(
    NULL, //句柄是否被繼承
    TRUE, //TRUE:手動修改事件的狀態 FALSE:自動修改事件狀態(遇到 WaitForSingleObject若爲已觸發,會修改成未觸發)
    FALSE, //TRUE:默認已觸發 FALSE:默認未觸發
    _T("cr33event"));
    
//已觸發,開鎖(事件創建時必須先設置爲手動修改)
SetEvent(hEvent);

//未觸發,關鎖(事件創建時必須先設置爲手動修改)
ResetEvent(hEvent);

//通過事件名字打開
Handle hNewEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, _T("cr33event"));

//關閉事件
CloseHandle(hEvent);

 2信號(Semaphore)

多用於處理多個線程共享多於一個資源的情況,常見於池技術,多用於生產者消費者模式

語法

  • CreateSemaphore創建信號
  • WaitForSingleObject
    • 等待成功,信號量個數減一
    • 信號量的個數爲0,信號量處於no-signale狀態,此時纔會等待
  • ReleaseSem釋放信號,信號量個數加一
#include <windows.h>
#include <conio.h>
#include <ctime>
#include <stdio.h>
HANDLE g_hSemphore = NULL;

DWORD WINAPI ThreadFunc(LPVOID lpParam)
{
  DWORD dwIdx = (DWORD)lpParam;
  while (TRUE)
  {
    //如果信號數量大於0就返回並把信號減一,等待可用的信號, 如果成功,則可用信號的個數減一
    WaitForSingleObject(g_hSemphore, INFINITE);

    printf("%d 教室開始答疑 \r\n", dwIdx);
    int nCnt = rand() % 0x100;
    for (int i = 0; i < nCnt; ++i)
    {
      Sleep(100);
    }
    printf("%d 教室答疑結束 \r\n", dwIdx);

    //釋放可用信號,指定的信號量增加指定的值,這邊是增加1個信號
    ReleaseSemaphore(g_hSemphore, 1, NULL);
  }

  return dwIdx;
}
int main()
{
  srand(time(NULL));
  DWORD dwThreadId, dwThrdParam = 1;
  const int nCountOfThread = 4;  //四個教室,四個線程
  HANDLE hThreads[nCountOfThread];

  g_hSemphore = CreateSemaphore(
    NULL,
    2,//初始信號是兩個
    4,//最多可以有4個信號
    NULL
    );
  for (int i = 0; i < nCountOfThread; ++i)
  {
    hThreads[i] = CreateThread(
      NULL,                       
      0,                          
      ThreadFunc,                 
      (LPVOID)i,                
      0,                          
      &dwThreadId);               
  }
  system("pause");
  return 0;
}

3互斥體

多個線程使用互斥體,只有一個線程擁有互斥體的權限

語法

  • CreateMutex創建互斥體
  • WaitForSingleObject獲取互斥體的使用權,並修改互斥體的狀態爲no-signal
  • ReleaseMutex釋放互斥體的使用權
#include <windows.h>
#include <stdio.h>
int g_nVal = 0;
HANDLE g_hMutex = NULL;


DWORD WINAPI ThreadFunc(LPVOID lpParam)
{
  for (int i = 0; i < 0x10000; ++i)
  {
    //等待互斥體,如果成功,則獲取互斥體的權限,並將互斥體狀態改爲no-signal
    WaitForSingleObject(g_hMutex, INFINITE);
    ++g_nVal;
    //釋放互斥體,給其它線程使用
    ReleaseMutex(g_hMutex);
  }

  printf("%08x\n", g_nVal);
  return 0;
}


int main()
{
  DWORD dwThreadId, dwThrdParam = 1;
  HANDLE hThread[2];
  g_hMutex = CreateMutex(NULL, // 安全屬性
    TRUE,// 爲True創建互斥體的線程擁有互斥體的權限,無信號狀態
    NULL);// 跨進程使用需要填名字
  
  hThread[0] = CreateThread(
    NULL,                        
    0,                           
    ThreadFunc,                  
    &dwThrdParam,                
    0,                           
    &dwThreadId);                

  hThread[1] = CreateThread(
    NULL,                        
    0,                           
    ThreadFunc,                  
    &dwThrdParam,                
    0,                           
    &dwThreadId);                
  Sleep(5000);//因爲主線程獲取了互斥體,所以等5秒後釋放互斥體,互斥體信號重置爲signal(有信號狀態)
  //釋放互斥體,給其它線程使用
  ReleaseMutex(g_hMutex);
  WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
  system("pause");
  return 0;
}

 

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