win32API多執行緒編程

  • 2019 年 10 月 29 日
  • 筆記

win32執行緒API

在Windows平台下可以通過Windows的執行緒庫來實現多執行緒編程。

對於多執行緒程式可以使用Visual Studio調試工具進行調試,也可以使用多核晶片廠家的執行緒分析調試工具進行調試。

Win32 API(了解Windows,程式碼小,效率高)

  • Windows作業系統為內核以及應用程式之間提供的介面
  • 將內核提供的功能進行函數封裝
  • 應用程式通過調用相關的函數獲得相應的系統功能

_beginthread

  • _beginthread(函數名,棧大小,參數指針)
  • Win32 函數庫中提供了操作多執行緒的函數, 包括創建執行緒、管理執行緒、終止執行緒、執行緒同步等介面。
    執行緒函數(執行緒開始執行的函數)
    DWORD WINAPI ThreadFunc (LPVOID
    lpvThreadParm );
    執行緒創建
    HANDLE CreateThread (
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    SIZE_T dwStackSize,
    LPTHREAD_START_ROUTINE lpStartAddress,
    LPVOID lpParameter,
    DWORD dwCreationFlags,
    LPDWORD lpThreadId );
  • 第一個參數lpThreadAtt,是一個指向SECURITY- ATTRIBUTES結構的指針,該結構制定了執行緒的安全屬性,預設為 NULL。
    第二個參數dwStackSize,是棧的大小,一般設置為0。
    第三個參數lpFun是新執行緒開始執行時,執行緒函數的入口地址。它必須是將要被新執行緒執行的函數地址,不能為NULL。
    第四個參數lpParameter,是執行緒函數定義的參數。可以通過這個參數傳送值,包括指針或者NULL 。
    第五個參數dwCreationFlags,控制執行緒創建的附加標誌,可以設置兩種值。0表示執行緒在被創建後就會立即開始執行;如果該參數為CREATE_SUSPENDED,則系統產生執行緒後,該執行緒處於掛起狀態,並不馬上執行,直至函數ResumeThread被調用;
    第六個參數lpThreadID,為指向32位變數的指針,該參數接受所創建執行緒的ID號。如果創建成功則返回執行緒的ID,否則返回NULL。
  • CreateThread不會執行C運行時數據塊, 因此在C運行時庫的應用程式中,不能使用CreateThread創建執行緒,
    微軟提供了另外的創建執行緒的方法:創建執行緒用process.h頭文件中聲明的C執行時期鏈接庫函數_beginthread。
    語法:
    hThread = _beginthread (
    void( __cdecl start_address )( void ),
    unsigned stack_size, void *arglist) ;
    執行緒函數的語法:
    void __cdecl ThreadProc (void * pParam) ;
#include "stdio.h"  #include <windows.h>  #include <process.h>  #include <iostream>  #include <fstream>  using namespace std;    //使用_beginthread函數創建執行緒的例子。    void ThreadFunc1(PVOID param)  {      while (1)      {          Sleep(1000);          cout << "  This is ThreadFunc1  " << endl;      }  }  void ThreadFunc2(PVOID param)  {      while (1)      {          Sleep(1000);          cout << "  This is ThreadFunc2  " << endl;      }  }    //可以多次執行本程式,查看兩個執行緒函數執行的順序。    int main()  {      int i = 0;      _beginthread(ThreadFunc1, 0, NULL);      _beginthread(ThreadFunc2, 0, NULL);      Sleep(5000);      cout << "end" << endl;      return 0;  }

創建執行掛起終止

  • CreateThread(NULL, 0, FunOne, (void*)&input, CREATE_SUSPENDED, NULL);
  • 安全屬性 棧大小 執行緒函數 參數指針 附加標誌 ID號(32位int指針)
  • 附加標誌為0 創建即可執行
  • 附加標誌為CREATE_SUSPENDED 創建就要掛起
  • ResumeThread(句柄) 掛起計數器-1,為0則進行
  • SuspendThread(句柄) 掛起計數器+1
  • TerminateThread(hand1, 1) 第二個參數為exitcode
  • 調用SetThreadPriority函數設置執行緒的相對優先順序,例如
    Bool SetThreadPriority (HANDLE hPriority , int nPriority)
    參數hPriority 指向待設置的執行緒句柄
  • nPriority 是執行緒的相對優先順序,可以是以下的值:
    空閑:THREAD - PRIORITY- IDLE 15
    最低執行緒:THREAD - PRIORITY- LOWEST 2
    低於正常執行緒:THREAD - PRIORITY- BELOW- NORMAL 1
    正常執行緒:THREAD - PRIORITY- NORMAL 0
    高於正常執行緒:THREAD - PRIORITY- ABOVE – NORMAL -1
    最高執行緒:THREAD - PRIORITY- HIGHEST -2
    關鍵時間:THREAD - PRIORITY- TIME – CRITICAL -15
  • 掛起與恢複函數原型
    DWORD SuspendThread(HANDLE hThread);
    掛起指定的執行緒(慎用,不處理同步對象)
    如果函數執行成功,則執行緒的執行被終止
    每次調用SuspendThread() 函數,執行緒將掛起計數器的值增1

    DWORD ResumeThread(HANDLE hThread);
    結束執行緒的掛起狀態來執行這個執行緒
    每次調用ResumeThread() 函數,執行緒將掛起計數器的值減1
    若掛起計數器的值為0,則不會再減

#include "stdio.h"  #include <windows.h>  #include <iostream>  using namespace std;    //簡單的多執行緒創建、執行、掛起、終止的程式例子。    DWORD WINAPI FunOne(LPVOID param) {      while (true)      {          Sleep(1000);          cout << "hello! ";          //cout<<"hello! "<<*((int*)param);      }      return 0;  }  DWORD WINAPI FunTwo(LPVOID param) {      while (true)      {          Sleep(1000);          cout << "world! ";      }      return 0;  }    //注意創建執行緒函數的第五個參數的運用。  //輸入1和2數字,可以控制執行緒的啟動和終止。  //注意連續輸入1和2數字執行緒的運行情況。  //執行緒的終止函數。    int main(int argc, char* argv[])  {      int input = 0;        //創建執行緒。這裡第四個參數值設為NULL也可以,因為執行緒函數里沒有使用輸入參數。      HANDLE hand1 = CreateThread(NULL, 0, FunOne, (void*)&input, CREATE_SUSPENDED, NULL);      HANDLE hand2 = CreateThread(NULL, 0, FunTwo, (void*)&input, CREATE_SUSPENDED, NULL);      while (true) {          cin >> input;          if (input == 1)          {              //恢復執行緒              ResumeThread(hand1);              ResumeThread(hand2);          }          if (input == 2)          {              //掛起執行緒              SuspendThread(hand1);              SuspendThread(hand2);          }          if (input == 0)          {              //終止執行緒              TerminateThread(hand1, 1);              TerminateThread(hand2, 1);          }          if (input == 9)              return 0;      };        return 0;    }  

利用全局變數實現同步

這樣做可能會有一個問題,主執行緒結束時其他執行緒也就跟著結束了

  • 全局變數
    進程中的所有執行緒均可以訪問所有的全局變數,因而全局變數成為Win32多執行緒通訊的最簡單方式。
    int var; //全局變數
    UINT ThreadFunction ( LPVOID pParam {
    while (var)
    {
    …… //執行緒處理
    }
    return 0; }
    var是一個全局變數,任何執行緒均可以訪問和修改。執行緒間可以利用此特性達到執行緒同步的目的。
#include "stdio.h"  #include <windows.h>  #include <iostream>  using namespace std;    //使用全局變數同步執行緒的例子。    //全局變數。  int globalvar = false;    DWORD WINAPI ThreadFunc(LPVOID pParam)  {      cout << "  ThreadFunc  " << endl;      Sleep(100);        //修改全局變數的值。      globalvar = true;        return 0;  }  DWORD WINAPI ThreadFunc1(LPVOID pParam)  {      while (1)          cout << "  ThreadFunc111  " << endl;      return 0;  }  DWORD WINAPI ThreadFunc2(LPVOID pParam)  {      while (1)          cout << "  ThreadFunc222  " << endl;      return 0;  }    //這種方式可能存在一些問題。    int main()  {      HANDLE hthread1 = CreateThread(NULL, 0, ThreadFunc1, NULL, 0, NULL);      HANDLE hthread2 = CreateThread(NULL, 0, ThreadFunc2, NULL, 0, NULL);      HANDLE hthread = CreateThread(NULL, 0, ThreadFunc, NULL, 0, NULL);      if (!hthread)      {          cout << "Thread Create Error ! " << endl;          CloseHandle(hthread);      }      bool b =SetThreadPriority(hthread,15);      cout<<GetThreadPriority(hthread1)<<endl;      cout<<GetThreadPriority(hthread2)<<endl;      cout<<GetThreadPriority(hthread)<<endl;        //循環判斷全局變數的值。      while (!globalvar)          cout << "Thread while" << endl;        cout << "Thread exit" << endl;      return 0;  }  
Thread while  ThreadFunc111    ThreadFunc111    ThreadFunc111    ThreadFunc111    ThreadFunc222    ThreadFunc222    ThreadFunc222    ThreadFunc222  Thread while  Thread while  Thread while  Thread while        ThreadFunc222    ThreadFunc222    ThreadFunc111  Thread while  Thread while  ThreadFunc222        ThreadFunc111  Thread exit    ThreadFunc111    ThreadFunc222      ThreadFunc222    ThreadFunc111    ThreadFunc111    ThreadFunc222    ThreadFunc111

利用事件實現同步

  • 事件是WIN32提供的最靈活的執行緒間同步方式。
    事件存在兩種狀態:
    激髮狀態(signaled or true)
    未激髮狀態(unsignal or false)
    事件可分為兩類:
    手動設置:這種對象只能用程式來手動設置,在需要該事件或者事件發生時,採用SetEvent及ResetEvent來進行設置。
    SetEvent只有一個參數,該參數指定了事件對象的句柄值,若事件成功激發,返回TRUE;
    ResetEvent函數將事件對象恢復到最初的非激髮狀態,只有一個參數,成功後返回真
    自動恢復:一旦事件發生並被處理後,自動恢復到沒有事件狀態,不需要再次設置。

  • CreateEvent(NULL, FALSE, FALSE, NULL);
  • 第一個參數還是安全,默認為NULL,二參代表事件類型,true為手動清除訊號,false為自動清除
  • 三參:事件的初始狀態 四參:事件的名稱
  • setEvent(句柄)
  • WaitForSingleObject(evRead, INFINITE);等待某個事件
    WaitForSingleObject (evFinish, INFINITE)
    參數1:等待對象
    參數2:等待時間
    返回值:WAIT_OBJECT_0(激發)
    WAIT_TIMEOUT(超時)
    WAIT_FAILED(錯誤)
  • WaitForMultipleObjects(2 ,evFin ,TRUE ,INFINITE)
    參數1:等待的句柄數
    參數2:等待的句柄數組
    參數3:確定是否等待所有句柄激發後才返回
    參數4:等待時間
    返回值: WAIT_OBJECT_I(激發事件在句柄數組中的索引)
  • 例子:有三個執行緒
    主執行緒、讀執行緒ReadThread、寫執行緒WriteThread
    讀執行緒ReadThread必須在寫執行緒WriteThread 的寫操作完成之後才能進行讀操作
    主執行緒必須在讀執行緒ReadThread 的讀操作完成後才結束
    定義兩個事件對象evRead,evFinish
    evRead由寫執行緒WriteThread用於通知讀執行緒ReadThread 進行讀操作
    evFinish由讀執行緒ReadThread用於通知主執行緒讀操作已經結束

#include "stdio.h"  #include <windows.h>  #include <iostream>  using namespace std;    //**使用事件機制同步執行緒的例子。    //兩個事件。  HANDLE evRead, evFin;  HANDLE evWrite;    void ReadThread(LPVOID param)  {      //等待讀事件。      WaitForSingleObject(evRead, INFINITE);        cout << "Reading" << endl;      //ResetEvent(evRead);//evRead為手動恢復類型時打開      SetEvent(evWrite);      WaitForSingleObject(evRead, INFINITE);      cout << "Reading11111" << endl;        //激活結束事件。      SetEvent(evFin);  }  void WriteThread(LPVOID param)  {      cout << "Writing" << endl;        //激活讀事件。      SetEvent(evRead);        WaitForSingleObject(evWrite, INFINITE);      cout << "Writing11111" << endl;      SetEvent(evRead);  }  int main(int argc, char* argv[])  {      //創建兩個事件,注意事件參數的含義。      evRead = CreateEvent(NULL, FALSE, FALSE, NULL);      evFin = CreateEvent(NULL, FALSE, FALSE, NULL);        evWrite = CreateEvent(NULL, FALSE, FALSE, NULL);      //evRead = CreateEvent (NULL ,TRUE ,FALSE ,NULL) ;//修改第二個參數為TRUE      //evFin = CreateEvent (NULL ,TRUE ,FALSE ,NULL) ;//修改第二個參數為TRUE      //evWrite = CreateEvent (NULL ,TRUE ,FALSE ,NULL) ;//修改第二個參數為TRUE        _beginthread(ReadThread, 0, NULL);      _beginthread(WriteThread, 0, NULL);        //等待結束事件。      WaitForSingleObject(evFin, INFINITE);        cout << "The Program is End" << endl;      return 0;  }    

臨界區

  • 防止多個執行緒同時執行一個特定程式碼段的機制
    適用於多個執行緒操作之間沒有先後順序,但要求互斥的同步
    多個執行緒訪問同一個臨界區的原則:
    一次最多只能一個執行緒停留在臨界區內
    不能讓一個執行緒無限地停留在臨界區內,否則其它執行緒將不能進入該臨界區
    定義臨界區變數的方法如下:
    CRITICAL_SECTION gCriticalSection;
    通常情況下,CRITICAL_SECTION結構體應該被定義為全局變數,便於進程中的所有執行緒可以方便地按照變數名來引用該結構體。
  • 初始化臨界區
    VOID WINAPI InitializeCriticalSection (
    LPCRITICAL_SECTION lpCriticalSection );
    刪除臨界區
    VOID WINAPI DeleteCriticalSection (
    LPCRITICAL_SECTION lpCriticalSection );
    進入臨界區
    VOID WINAPI EnterCriticalSection (
    LPCRITICAL_SECTION lpCriticalSection );
    執行該語句時,程式會判斷cs對象是否已被鎖定,若沒鎖定則執行緒進入臨界區,並將cs置為鎖定狀態;否則,執行緒執行緒被阻塞以等待cs解鎖。
    離開臨界區
    VOID WINAPI LeaveCriticalSection (
      LPCRITICAL_SECTION lpCriticalSection );
    執行緒執行該語句後,程式自動將cs解鎖,並喚醒等待cs解鎖的執行緒
  • 使用臨界區編程的一般方法是:
    void WriteData()
    {
    EnterCriticalSection(&gCriticalSection);
    //do something
    LeaveCriticalSection(&gCriticalSection);
    }
    例子
    假如一個銀行系統有兩個執行緒執行取款任務,一個使用存摺在櫃檯取款,一個使用銀行卡在ATM取款。若不加控制,很可能賬戶餘額不足兩次取款的總額,但還可以把錢取走。
#include "stdio.h"  #include <windows.h>  #include <iostream>  #include <process.h>  #include <iostream>  #include <fstream>  using namespace std;    //**使用臨界區機制同步執行緒。    int total = 100;  HANDLE evFin[2];  CRITICAL_SECTION cs;//臨界區。    //可以去掉臨界區機制,查看是否出現錯誤。(需要輔助sleep函數)  void WithdrawThread1(LPVOID param)  {      EnterCriticalSection(&cs);//進入臨界區      if (total - 90 >= 0)      {//Sleep(100);          total -= 90;          cout << "You withdraw 90" << endl;      }      else          cout << "You do not have that much money" << endl;      LeaveCriticalSection(&cs);//退出臨界區      SetEvent(evFin[0]);  }  void WithdrawThread2(LPVOID param)  {      EnterCriticalSection(&cs);//進入臨界區      if (total - 20 >= 0)      {          total -= 20;          cout << "You withdraw 20" << endl;      }      else          cout << "You do not have that much money" << endl;      LeaveCriticalSection(&cs);//退出臨界區      //LeaveCriticalSection(&cs) ;      SetEvent(evFin[1]);  }  int main(int argc, char* argv[])  {      evFin[0] = CreateEvent(NULL, FALSE, FALSE, NULL);      evFin[1] = CreateEvent(NULL, FALSE, FALSE, NULL);        InitializeCriticalSection(&cs);//初始化臨界區。        _beginthread(WithdrawThread1, 0, NULL);//創建執行緒順序影響執行的結果。      _beginthread(WithdrawThread2, 0, NULL);      //_beginthread(WithdrawThread1 , 0 , NULL) ;        int id = WaitForMultipleObjects(2, evFin, TRUE, INFINITE);//等待兩個事件都激活。      cout << "句柄數組中的索引:" << id << endl;        DeleteCriticalSection(&cs);//刪除臨界區        cout << total << endl;      return 0;  }  

先1後2

先2後1

互斥量

  • 通常用於協調多個執行緒或進程的活動,通過對資源「鎖定」和「取消鎖定」,來控制對共享資源的訪問。
    當一個互斥量被一個執行緒鎖定了,其他試圖對其加鎖的執行緒就會被阻塞;當對互斥量加鎖的執行緒解除鎖定後,則被阻塞的執行緒中的一個會得到互斥量。
    互斥量的作用是保證每次只能有一個執行緒獲得互斥量
    使用CreateMutex函數創建:
    HANDLE CreateMutex(
    LPSECURITY_ATTRIBUTES lpMutexAttributes,
    BOOL bInitialOwner,
    LPCTSTR lpName
    );
    安全屬性 是否手動清除(true為手動) 名稱
  • 相關的API:
    CreateMutex 創建一個互斥對象,返回對象句柄;
    OpenMutex 打開並返回一個已存在的互斥對象的句柄,使之後續訪問;
    ReleaseMutex 釋放對互斥對象的佔用,使之成為可用;
    使用互斥量的一般方法是:
    void Writedata()
    {
    WaitForSingleObject(hMutex,…);
    ...//do something
    ReleaseMutex(hMutex);
    }
#include "stdio.h"  #include <windows.h>  #include <iostream>  #include <process.h>  #include <iostream>  #include <fstream>  using namespace std;    //**互斥量的使用方法。    #define THREAD_INSTANCE_NUMBER  3    LONG g_fResourceInUse = FALSE;  LONG g_lCounter = 0;    DWORD ThreadProc(void* pData) {        int ThreadNumberTemp = (*(int*)pData);      HANDLE hMutex;        if ((hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, "Mutex.Test")) == NULL) {          cout << "Open Mutex error!" << endl;      }        //cout << "ThreadProc is running hMutexxxxxxx!!"  << ThreadNumberTemp <<endl;      WaitForSingleObject(hMutex, INFINITE);//獲取互斥量。      cout << "ThreadProc is running!!" << ThreadNumberTemp << endl;      cout << "ThreadProc gets the mutex-" << ThreadNumberTemp << endl;        ReleaseMutex(hMutex);//釋放互斥量。      CloseHandle(hMutex);      return 0;  }    int main(int argc, char* argv[])  {        int i;      DWORD ID[THREAD_INSTANCE_NUMBER];      HANDLE h[THREAD_INSTANCE_NUMBER];      HANDLE hMutex;      if ((hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, "Mutex.Test")) == NULL) {          if ((hMutex = CreateMutex(NULL, FALSE, "Mutex.Test")) == NULL) //注意第二個參數,當前執行緒是否擁有創建的鎖          {              cout << "Create Mutex error!" << endl;              return 0;          }      }      //ReleaseMutex(hMutex);  //CreateMutex函數第二參數為TRUE時打開        //獲取訊號量的位置不同,將產生不同的結果。     //WaitForSingleObject(hMutex,INFINITE);      for (i = 0; i < THREAD_INSTANCE_NUMBER; i++)      {          WaitForSingleObject(hMutex, INFINITE);//獲取互斥量。本執行緒可重複獲取,但解鎖時需釋放相同的次數。          h[i] = CreateThread(NULL,              0,              (LPTHREAD_START_ROUTINE)ThreadProc,              (void*)&ID[i],              0,              &(ID[i]));          //WaitForSingleObject(hMutex,INFINITE);//演示重複獲取互斥量          if (h[i] == NULL)              cout << "CreateThread error" << ID[i] << endl;          else              cout << "CreateThread: " << ID[i] << endl;          ReleaseMutex(hMutex);          //Sleep(1000);      }      //ReleaseMutex(hMutex);//ReleaseMutex(hMutex);ReleaseMutex(hMutex);//演示釋放重複獲取的互斥量        WaitForMultipleObjects(THREAD_INSTANCE_NUMBER, h, TRUE, INFINITE);      cout << "Close the Mutex Handle! " << endl;      CloseHandle(hMutex);        return 0;  }    

訊號量

  • 訊號量是一個核心對象,擁有一個計數器,可用來管理大量有限的系統資源
    當計數值大於零時,訊號量為有訊號狀態
    當計數值為零時,訊號量處於無訊號狀態
    創建訊號量
    HANDLE CreateSemaphore (PSECURITY_ATTRIBUTE psa,
    LONG lInitialCount, LONG lMaximumCount, PCTSTR pszName);
    安全屬性 初始數量 最大數量 訊號量名稱
  • 釋放訊號量
    BOOL WINAPI ReleaseSemaphore(HANDLE hSemaphore,
    LONG lReleaseCount, //訊號量的當前資源數增加lReleaseCount
    LPLONG lpPreviousCount);
    打開訊號量
    HANDLE OpenSemaphore (DWORD fdwAccess,
    BOOL bInherithandle, PCTSTR pszName );
// exa7.cpp : Defines the entry point for the console application.  //    #include "stdafx.h"  #include <windows.h>  #include <iostream>  using namespace std;    //**使用訊號量機制同步執行緒。    #define THREAD_INSTANCE_NUMBER  3    DWORD foo(void * pData) {        int ThreadNumberTemp = (*(int*) pData);      HANDLE hSemaphore;        if ((hSemaphore = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, "Semaphore.Test")) == NULL) {          cout << "Open Semaphore error!" << endl;      }        WaitForSingleObject(hSemaphore,INFINITE);//獲取訊號量。        cout << "foo is running!!!"  << ThreadNumberTemp << endl;      cout << "foo gets the semaphore-" << ThreadNumberTemp<< endl;        ReleaseSemaphore(hSemaphore, 1, NULL);//釋放一個單位的訊號量        CloseHandle(hSemaphore);      return 0;  }    int main(int argc, char* argv[])  {        int i;      DWORD ThreadID[THREAD_INSTANCE_NUMBER];      HANDLE hThread[THREAD_INSTANCE_NUMBER];      HANDLE hSemaphore;          if ((hSemaphore = CreateSemaphore(NULL,1,1, "Semaphore.Test")) == NULL ) {          cout << "Create Semaphore error!" << endl;          return 0;      }        //與互斥量一樣,這裡獲取訊號量的位置不同,會產生不同的結果。      for (i=0;i<THREAD_INSTANCE_NUMBER;i++)      {          WaitForSingleObject(hSemaphore,INFINITE);//獲取訊號量。不可重入。          hThread[i] = CreateThread(NULL,                              0,                              (LPTHREAD_START_ROUTINE) foo,                              (void *)&ThreadID[i],                              0,                              &(ThreadID[i]));            if (hThread[i] == NULL)                cout << "CreateThread error" << ThreadID[i] << endl;          else              cout << "CreateThread: " << ThreadID[i] << endl;            ReleaseSemaphore(hSemaphore, 1, NULL);      }        WaitForMultipleObjects(THREAD_INSTANCE_NUMBER,hThread,TRUE,INFINITE);      cout << "Close the Semaphore Handle! " << endl;      CloseHandle(hSemaphore);        return 0;  }  

Tags: