[多線程] Windows多線程編程API及比較


  AfxBeginThread、BeginThread和BeginThreadex實際上是編譯器對CreateThread的封裝。

一、CreateThread:

  Windows的API函數(SDK函數的標准形式,直截了當的創建方式,任何場合都可以使用),提供操作系統級別的創建線程的操作,且僅限於工作者線程。

//@線程創建成功返回新線程的句柄,失敗返回NULL
 HANDLE WINAPI CreateThread( //表示線程內核對象的安全屬性,一般傳入NULL表示使用默認設置
      _In_opt_  LPSECURITY_ATTRIBUTES  lpThreadAttributes,   
//表示線程棧空間大小。傳入0表示使用默認大小(1MB)
      _In_      SIZE_T                 dwStackSize,
//表示新線程所執行的線程函數地址,多個線程可以使用同一個函數地址。
      _In_      LPTHREAD_START_ROUTINE lpStartAddress,
//是傳給線程函數的參數
      _In_opt_  LPVOID                 lpParameter,
//指定額外的標志來控制線程的創建,為0表示線程創建之后立即就可以進行調度,如果為CREATE_SUSPENDED則表示線程創建后暫停運行,這樣它就無法調度,直到調用ResumeThread()。
      _In_      DWORD                  dwCreationFlags,
//將返回線程的ID號,傳入NULL表示不需要返回該線程ID號。
      _Out_opt_ LPDWORD                lpThreadId
    );
/* 創建第一個線程。主進程結束,則撤銷線程。 */
#include<Windows.h>
#include<stdio.h>
DWORD WINAPI ThreadFunc(LPVOID);

void main()
{
    HANDLE hThread;
    DWORD  threadId;
   hThread = CreateThread(NULL, 0, ThreadFunc, 0, 0, &threadId); // 創建線程
    printf("我是主線程, pid = %d\n", GetCurrentThreadId());  //輸出主線程pid
    Sleep(2000);
}

DWORD WINAPI ThreadFunc(LPVOID p)
{   
    printf("我是子線程, pid = %d\n", GetCurrentThreadId());   //輸出子線程pid
    return 0;
}

  在實際使用中盡量使用_beginthreadex()來創建線程,在博客中使用 CreateThread()l來創建線程其實是一種不太好的方法,不過這里只做原理分析,不用在實際項目中。

 二、WaitForSingleObject / WaitForMultipleObjects

  需要等待某一線程完成了特定的操作后再繼續做其他事情,要實現這個目的,可以使用Windows API函數WaitForSingleObject,或者WaitForMultipleObjects。這兩個函數都會等待Object被標為有信號(signaled)時才返回。只要是Windows創建的Object都會被賦予一個狀態量。如果Object被激活了,或者正在使用,那么該Object就是無信號,也就是不可用;另一方面,如果Object可用了,那么它就恢復有信號了。

/*返回值:
WAIT_ABANDONED 0x00000080:當hHandle為mutex時,如果擁有mutex的線程在結束時沒有釋放核心對象會引發此返回值。
WAIT_OBJECT_0 0x00000000 :指定的對象出有有信號狀態
WAIT_TIMEOUT 0x00000102:等待超時
WAIT_FAILED 0xFFFFFFFF :出現錯誤,可通過GetLastError得到錯誤代碼
*/ DWORD WINAPI WaitForSingleObject( /*參數1:
對象的句柄,可以是以下幾種:
Change notification
Console input
Event
Memory resource notification
Mutex
Process
Semaphore
Thread
Waitable timer
*/
    _In_ HANDLE hHandle,
/*參數2:
等待時間,以毫秒為單位。參數dwMilliseconds有兩個具有特殊意義的值:0和INFINITE。若為0,則該函數立即返回;若為INFINITE,則線程一直被掛起,直到hHandle所指向的對象變為有信號狀態時為止。
*/
    _In_ DWORD dwMilliseconds
    );
//@返回值類型同上
DWORD WINAPI WaitForMultipleObjects( //為等待的內核對象個數,可以是0到MAXIMUM_WAIT_OBJECTS(64)中的一個值
  _In_       DWORD   nCount,
//為一個存放被等待的內核對象句柄的數組
  _In_ const HANDLE  *lpHandles,
//是否等到所有內核對象為已通知狀態后才返回,如果為TRUE,則只有當等待的所有內核對象為已通知狀態時函數才返回,如果為FALSE,則只要一個內核對象為已通知狀態,則該函數返回。
  _In_       BOOL    bWaitAll,
//為等待時間,和WaitForSingleObject中的dwMilliseconds參數類似
  _In_       DWORD   dwMilliseconds
);

、beginthread / beginthreadex:

  MS對C-Runtime庫的擴展SDK函數,首先針對C Runtime庫做了一些初始化的工作,以保證C Runtime庫工作正常,然后,調用CreateThread真正創建線程。beginthread是_beginthreadex的功能子集,雖然_beginthread內部是調用_beginthreadex但他屏蔽了象安全特性這樣的功能,例如,如果使用_beginthread,就無法創建帶有安全屬性的新線程,無法創建暫停的線程,也無法獲得線程的ID值。_beginthread與CreateThread不是同等級別,_beginthreadex和CreateThread在功能上完全可替代 。

 

注意:盡量不要調用CreateThread。相反,應該使用Visual C++運行期庫函數_beginthreadex,原因如下:  考慮標准C運行時庫的一些變量和函數,如errno,這是一個全局變量。必須存在一種機制,使得每個線程能夠引用它自己的errno變量,又不觸及另一線程的errno變量._beginthreadex就為每個線程分配自己的tiddata內存結構。該結構保存了許多像errno這樣的變量和函數的值、地址,通過線程局部存儲將tiddata與線程聯系起來。具體實現在Threadex.c中有,結束線程使用函數_endthreadex函數,釋放掉線程的tiddata數據塊。對於線程的支持是后來的事! 這也導致了許多CRT的函數在多線程的情況下必須有特殊的支持,不能簡單的使用CreateThread。

  CRT的函數庫在線程出現之前就已經存在,所以原有的CRT不能真正支持線程,這導致我們在編程的時候有了CRT庫的選擇,在MSDN中查閱CRT的函數時都有:   

  Libraries   
  LIBC.LIB   Single   thread   static   library,   retail   version     
  LIBCMT.LIB   Multithread   static   library,   retail   version     
  MSVCRT.LIB   Import   library   for   MSVCRT.DLL,   retail   version     
  大多的CRT函數都可以在CreateThread線程中使用,看資料說只有signal()函數不可以,會導致進程終止!但可以用並不是說沒有問題!   
  有些CRT的函數象malloc(),   fopen(),   _open(),   strtok(),   ctime(),   或localtime()等函數需要專門的線程局部存儲的數據塊,這個數據塊通常需要在創建線程的時候就建立,如果使用CreateThread,這個數據塊就沒有建立,然后會怎樣呢?在這樣的線程中還是可以使用這些函數而且沒有出錯,實際上函數發現這個數據塊的指針為空時,會自己建立一個,然后將其與線程聯系在一起,這意味着如果你用CreateThread來創建線程,然后使用這樣的函數,會有一塊內存在不知不覺中創建,遺憾的是,這些函數並不將其刪除,而CreateThread和ExitThread也無法知道這件事,於是就會有Memory leak,在線程頻繁啟動的軟件中(比如某些服務器軟件),遲早會讓系統的內存資源耗盡!   
  _beginthreadex和_endthreadex就對這個內存塊做了處理,所以沒有問題!(不會有人故意用CreateThread創建然后用_endthreadex終止吧,而且線程的終止最好不要顯式的調用終止函數,自然退出最好!)   如果在除主線程之外的任何線程中進行一下操作,你就應該使用多線程版本的C runtime library,並使用_beginthreadex和_endthreadex: 

1 使用malloc()和free(),或是new和delete 
2 使用stdio.h或io.h里面聲明的任何函數 
3 使用浮點變量或浮點運算函數 
4 調用任何一個使用了靜態緩沖區的runtime函數,比如:asctime(),strtok()或rand() 
Handle的問題,_beginthread的對應函數_endthread自動的調用了CloseHandle,而_beginthreadex的對應函數_endthreadex則沒有,所以CloseHandle無論如何都是要調用的不過_endthread可以幫你執行自己不必寫,其他兩種就需要自己寫!(Jeffrey   Richter強烈推薦盡量不用顯式的終止函數,用自然退出的方式,自然退出當然就一定要自己寫CloseHandle)

//頭文件:process.h
//函數原型:
unsigned long _beginthreadex(
//安全屬性,NULL為默認安全屬性 
void *security, 
//指定線程堆棧的大小。如果為0,則線程堆棧大小和創建它的線程的相同。一般用0 
unsigned stack_size, 
//指定線程函數的地址,也就是線程調用執行的函數地址(用函數名稱即可,函數名稱就表示地址,注意的是函數訪問方式一定是__stdcall,函數返回值一定是unsigned,函數參數一定是void*) 
unsigned ( __stdcall *start_address )( void * ), 
//傳遞給線程的參數的指針,可以通過傳入對象的指針,在線程函數中再轉化為對應類的指針 
void *arglist, 
//線程初始狀態,0:立即運行;CREATE_SUSPEND:懸掛(如果出事狀態定義為懸掛,就要調用ResumeThread(HANDLE) 來激活線程的運行) 
unsigned initflag, 
//用於記錄線程ID的地址
unsigned *thrdaddr 
);

四、AfxBeginThread:

  MFC中線程創建的MFC函數,它簡化了操作或讓線程能夠響應消息,即可用於界面線程,也可以用於工作者線程,但要注意盡量不要在一個MFC程序中使用_beginthreadex()或CreateThread()。

//用戶界面線程的AfxBeginThread的原型如下:
CWinThread* AFXAPI AfxBeginThread(
//從CWinThread派生的RUNTIME_CLASS類
  CRuntimeClass* pThreadClass,
//指定線程優先級,如果為0,則與創建該線程的線程相同;
  int nPriority,
//指定線程的堆棧大小,如果為0,則與創建該線程的線程相同;
  UINT nStackSize,
//一個創建標識,如果是CREATE_SUSPENDED,則在懸掛狀態創建線程,在線程創建后線程掛起,否則線程在創建后開始線程的執行。
  DWORD dwCreateFlags,
//線程的安全屬性,NT下有用
  LPSECURITY_ATTRIBUTES lpSecurityAttrs)
//工作者線程的AfxBeginThread的原型如下:
//@功時返回一個指向新線程的線程對象的指針,否則NULL
CWinThread* AfxBeginThread(
// 線程的入口函數,聲明一定要如下: UINT MyThreadFunction(LPVOID pParam),不能設置為NULL
     AFX_THREADPROC pfnThreadProc,
//傳遞入線程的參數,注意它的類型為:LPVOID,所以我們可以傳遞一個結構體入線程.
  LPVOID lParam,
// 線程的優先級,一般設置為 0 .讓它和主線程具有共同的優先級.
  int nPriority = THREAD_PRIORITY_NORMAL,
//指定新創建的線程的棧的大小.如果為 0,新創建的線程具有和主線程一樣的大小的棧
  UINT nStackSize = 0,
// 指定創建線程以后,線程有怎么樣的標志.可以指定兩個值:
CREATE_SUSPENDED : 線程創建以后,會處於掛起狀態,直到調用:ResumeThread 0 : 創建線程后就開始運行.
  DWORD dwCreateFlags = 0,
// 指向一個 SECURITY_ATTRIBUTES 的結構體,用它來標志新創建線程的安全性.如果為 NULL,
那么新創建的線程就具有和主線程一樣的安全性.
如果要在線程內結束線程,可以在線程內調用 AfxEndThread.
  LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
  );//用於創建工作者線程

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM