1. 程式人生 > >執行緒總結第二篇 多執行緒第一次親密接觸 CreateThread與_beginthreadex本質區別

執行緒總結第二篇 多執行緒第一次親密接觸 CreateThread與_beginthreadex本質區別

本文將帶領你與多執行緒作第一次親密接觸,並深入分析CreateThread_beginthreadex的本質區別,相信閱讀本文後你能輕鬆的使用多執行緒並能流暢準確的回答CreateThread_beginthreadex到底有什麼區別,在實際的程式設計中到底應該使用CreateThread還是_beginthreadex
 

使用多執行緒其實是非常容易的,下面這個程式的主執行緒會建立了一個子執行緒並等待其執行完畢,子執行緒就輸出它的執行緒ID號然後輸出一句經典名言——Hello World。整個程式的程式碼非常簡短,只有區區幾行。


//最簡單的建立多執行緒例項

#include <stdio.h>

#include <windows.h>

//子執行緒函式 此處必須加WINAPI否則會報錯誤
DWORD WINAPI ThreadFun(LPVOID pM)
{

	printf("子執行緒的執行緒ID號為:%d\n子執行緒輸出Hello World\n", GetCurrentThreadId());

	return 0;

}

//主函式,所謂主函式其實就是主執行緒執行的函式。
int main()
{

	printf("     最簡單的建立多執行緒例項\n");

 
        //HANDLE = void*
	HANDLE handle = CreateThread(NULL, 0, ThreadFun, NULL, 0, NULL);
        
        //等待執行緒結束執行
        //否則程序直接結束 執行緒沒有了資源 執行緒也就死掉了
	WaitForSingleObject(handle, INFINITE);

	return 0;

}

執行結果如下所示:

哈哈

下面來細講下程式碼中的一些函式

CreateThread()

  • 函式功能:建立執行緒
  • 函式原型:
     
HANDLE WINAPI CreateThread(  
LPSECURITY_ATTRIBUTES lpThreadAttributes,  
SIZE_T dwStackSize, 
 LPTHREAD_START_ROUTINE lpStartAddress,  
LPVOID lpParameter, 
 DWORD dwCreationFlags,  
LPDWORD lpThreadId
);
  •  函式說明:

第一個引數表示執行緒核心物件的安全屬性,一般傳入NULL表示使用預設設定。

第二個引數表示執行緒棧空間大小。傳入0表示使用預設大小(1MB)。

第三個引數表示新執行緒所執行的執行緒函式地址,多個執行緒可以使用同一個函式地址。

第四個引數是傳給執行緒函式的引數。

第五個引數指定額外的標誌來控制執行緒的建立,為0表示執行緒建立之後立即就可以進行排程,如果為CREATE_SUSPENDED則表示執行緒建立後暫停執行,這樣它就無法排程,直到呼叫ResumeThread()。

第六個引數將返回執行緒的ID號,傳入NULL表示不需要返回該執行緒ID號。

  • 函式返回值:成功返回新執行緒的控制代碼,失敗返回NULL。  

WaitForSingleObject()

  • 函式功能:等待函式 – 使執行緒進入等待狀態,直到指定的核心物件被觸發。
  • 函式原形:
  • DWORD WINAPI WaitForSingleObject(  HANDLE hHandle,  DWORD dwMilliseconds);
  • 函式說明:

第一個引數為要等待的核心物件。

第二個引數為最長等待的時間,以毫秒為單位,如傳入5000就表示5秒,傳入0就立即返回,傳入INFINITE表示無限等待。

因為執行緒的控制代碼線上程執行時是未觸發的,執行緒結束執行,控制代碼處於觸發狀態。所以可以用WaitForSingleObject()來等待一個執行緒結束執行。

  • 函式返回值:

在指定的時間內物件被觸發,函式返回WAIT_OBJECT_0。超過最長等待時間物件仍未被觸發返回WAIT_TIMEOUT。傳入引數有錯誤將返回WAIT_FAILED

   CreateThread()函式是Windows提供的API介面,在C/C++語言另有一個建立執行緒的函式_beginthreadex(),在很多書上(包括《Windows核心程式設計》)提到過儘量使用_beginthreadex()來代替使用CreateThread(),這是為什麼了?下面就來探索與發現它們的區別吧。    

     首先要從標準C執行庫與多執行緒的矛盾說起,標準C執行庫在1970年被實現了,由於當時沒任何一個作業系統提供對多執行緒的支援。因此編寫標準C執行庫的程式設計師根本沒考慮多執行緒程式使用標準C執行庫的情況。比如標準C執行庫的全域性變數errno。很多執行庫中的函式在出錯時會將錯誤代號賦值給這個全域性變數,這樣可以方便除錯。

但如果有這樣的一個程式碼片段:

if (system("notepad.exe readme.txt") == -1)
{
	switch(errno)
	{

		...//錯誤處理程式碼

	}
}

假設某個執行緒A在執行上面的程式碼,該執行緒在呼叫system()之後且尚未呼叫switch()語句時另外一個執行緒B啟動了,這個執行緒B也呼叫了標準C執行庫的函式,不幸的是這個函式執行出錯了並將錯誤代號寫入全域性變數errno中。這樣執行緒A一旦開始執行switch()語句時,它將訪問一個被B執行緒改動了的errno。這種情況必須要加以避免!因為不單單是這一個變數會出問題,其它像strerror()、strtok()、tmpnam()、gmtime()、asctime()等函式也會遇到這種由多個執行緒訪問修改導致的資料覆蓋問題。
 

為了解決這個問題,Windows作業系統提供了這樣的一種解決方案——每個執行緒都將擁有自己專用的一塊記憶體區域來供標準C執行庫中所有有需要的函式使用。而且這塊記憶體區域的建立就是由C/C++執行庫函式_beginthreadex()來負責的。下面列出_beginthreadex()函式的原始碼(我在這份程式碼中增加了一些註釋)以便讀者更好的理解_beginthreadex()函式與CreateThread()函式的區別。


//_beginthreadex原始碼整理By MoreWindows( http://blog.csdn.net/MoreWindows )

_MCRTIMP uintptr_t __cdecl _beginthreadex(

	void *security,

	unsigned stacksize,

	unsigned (__CLR_OR_STD_CALL * initialcode) (void *),

	void * argument,

	unsigned createflag,

	unsigned *thrdaddr

)

{

	_ptiddata ptd;          //pointer to per-thread data 見注1

	uintptr_t thdl;         //thread handle 執行緒控制代碼

	unsigned long err = 0L; //Return from GetLastError()

	unsigned dummyid;    //dummy returned thread ID 執行緒ID號

	

	// validation section 檢查initialcode是否為NULL

	_VALIDATE_RETURN(initialcode != NULL, EINVAL, 0);

 

	//Initialize FlsGetValue function pointer

	__set_flsgetvalue();

	

	//Allocate and initialize a per-thread data structure for the to-be-created thread.

	//相當於new一個_tiddata結構,並賦給_ptiddata指標。

	if ( (ptd = (_ptiddata)_calloc_crt(1, sizeof(struct _tiddata))) == NULL )

		goto error_return;

 

	// Initialize the per-thread data

	//初始化執行緒的_tiddata塊即CRT資料區域 見注2

	_initptd(ptd, _getptd()->ptlocinfo);

	

	//設定_tiddata結構中的其它資料,這樣這塊_tiddata塊就與執行緒聯絡在一起了。

	ptd->_initaddr = (void *) initialcode; //執行緒函式地址

	ptd->_initarg = argument;              //傳入的執行緒引數

	ptd->_thandle = (uintptr_t)(-1);

	

#if defined (_M_CEE) || defined (MRTDLL)

	if(!_getdomain(&(ptd->__initDomain))) //見注3

	{

		goto error_return;

	}

#endif  // defined (_M_CEE) || defined (MRTDLL)

	

	// Make sure non-NULL thrdaddr is passed to CreateThread

	if ( thrdaddr == NULL )//判斷是否需要返回執行緒ID號

		thrdaddr = &dummyid;

 

	// Create the new thread using the parameters supplied by the caller.

	//_beginthreadex()最終還是會呼叫CreateThread()來向系統申請建立執行緒

	if ( (thdl = (uintptr_t)CreateThread(

					(LPSECURITY_ATTRIBUTES)security,

					stacksize,

					_threadstartex,

					(LPVOID)ptd,

					createflag,

					(LPDWORD)thrdaddr))

		== (uintptr_t)0 )

	{

		err = GetLastError();

		goto error_return;

	}

 

	//Good return

	return(thdl); //執行緒建立成功,返回新執行緒的控制代碼.

	

	//Error return

error_return:

	//Either ptd is NULL, or it points to the no-longer-necessary block

	//calloc-ed for the _tiddata struct which should now be freed up.

	//回收由_calloc_crt()申請的_tiddata塊

	_free_crt(ptd);

	// Map the error, if necessary.

	// Note: this routine returns 0 for failure, just like the Win32

	// API CreateThread, but _beginthread() returns -1 for failure.

	//校正錯誤代號(可以呼叫GetLastError()得到錯誤代號)

	if ( err != 0L )

		_dosmaperr(err);

	return( (uintptr_t)0 ); //返回值為NULL的效控制代碼

}

講解下部分程式碼:

注1._ptiddataptd;中的_ptiddata是個結構體指標。

在mtdll.h檔案被定義:      typedefstruct_tiddata * _ptiddata

微軟對它的註釋為Structure for each thread's data。

注2._initptd(ptd, _getptd()->ptlocinfo);

微軟對這一句程式碼中的getptd()的說明為:    

 /* return address of per-thread CRT data */      _ptiddata __cdecl_getptd(void);

對_initptd()說明如下:    

 /* initialize a per-thread CRT data block */      void__cdecl_initptd(_Inout_ _ptiddata _Ptd,_In_opt_ pthreadlocinfo _Locale);

註釋中的CRT (C Runtime Library)即標準C執行庫。

注3.if(!_getdomain(&(ptd->__initDomain)))中的_getdomain()函式程式碼可以在thread.c檔案中找到,其主要功能是初始化COM環境。


由上面的原始碼可知,_beginthreadex()函式在建立新執行緒時會分配並初始化一個_tiddata塊。這個_tiddata塊自然是用來存放一些需要執行緒獨享的資料。事實上新執行緒執行時會首先將_tiddata塊與自己進一步關聯起來。然後新執行緒呼叫標準C執行庫函式如strtok()時就會先取得_tiddata塊的地址再將需要保護的資料存入_tiddata塊中。這樣每個執行緒就只會訪問和修改自己的資料而不會去篡改其它執行緒的資料了。因此,如果在程式碼中有使用標準C執行庫中的函式時,儘量使用_beginthreadex()來代替CreateThread()。相信閱讀到這裡時,你會對這句簡短的話有個非常深刻的印象,如果有面試官問起,你也可以流暢準確的回答了^_^。


接下來,類似於上面的程式用CreateThread()建立輸出“Hello World”的子執行緒,下面使用_beginthreadex()來建立多個子執行緒:


//建立多子個執行緒例項

#include <stdio.h>

#include <process.h>

#include <windows.h>

//子執行緒函式

unsigned int __stdcall ThreadFun(PVOID pM)

{

	printf("執行緒ID號為%4d的子執行緒說:Hello World\n", GetCurrentThreadId());

	return 0;

}

//主函式,所謂主函式其實就是主執行緒執行的函式。

int main()

{

	printf("     建立多個子執行緒例項 \n");

	printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");

	

	const int THREAD_NUM = 5;

	HANDLE handle[THREAD_NUM];

	for (int i = 0; i < THREAD_NUM; i++)

		handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);

	WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);

	return 0;

}

相關推薦

執行總結第二 執行第一親密接觸 CreateThread_beginthreadex本質區別

本文將帶領你與多執行緒作第一次親密接觸,並深入分析CreateThread與_beginthreadex的本質區別,相信閱讀本文後你能輕鬆的使用多執行緒並能流暢準確的回答CreateThread與_beginthreadex到底有什麼區別,在實際的程式設計中到底應該使用Cre

秒殺執行第二 執行第一親密接觸 CreateThread_beginthreadex本質區別

本文將帶領你與多執行緒作第一次親密接觸,並深入分析CreateThread與_beginthreadex的本質區別,相信閱讀本文後你能輕鬆的使用多執行緒並能流暢準確的回答CreateThread與_beginthreadex到底有什麼區別,在實際的程式設計中到底應該使用C

秒殺執行第二 執行第一親密接觸 CreateThread beginthreadex本質區別

                    本文將帶領你與多執行緒作第一次親密接觸,並深入分析CreateThread與_beginthreadex的本質區別,相信閱讀本文後你能輕鬆的使用多執行緒並能流暢準確的回答CreateThread與_beginthreadex到底有什麼區別,在實際的程式設計中到底應該使用

秒殺執行第一 CreateThread_beginthreadex本質區別

   本文將帶領你與多執行緒作第一次親密接觸,並深入分析CreateThread與_beginthreadex的本質區別,相信閱讀本文後你能輕鬆的使用多執行緒並能流暢準確的回答CreateThread與_beginthreadex到底有什麼區別,在實際的程式設計中到底應該使用CreateThread還是_

秒殺執行第九 經典執行同步總結 關鍵段 事件 互斥量 訊號量

                前面《秒殺多執行緒第四篇一個經典的多執行緒同步問題》提出了一個經典的多執行緒同步互斥問題,這個問題包括了主執行緒與子執行緒的同步,子執行緒間的互斥,是一道非常經典的多執行緒同步互斥問題範例,後面分別用了四篇來詳細介紹常用的執行緒同步互斥機制——關鍵段、事件、互斥量、訊號量。下面

java執行第一——執行基礎知識點總結

程序與執行緒一個正在作業系統中執行的 exe 程式理解成一個“程序”,執行緒可以理解成是在程序中獨立執行的子任務。一個QQ.exe 執行時是一個程序, 其中:好友視訊執行緒、下載檔案執行緒、傳輸資料執行緒、傳送表 情執行緒等。  2.main方法是一個執行緒3.執行緒是一個子

Python中的執行程式設計,執行安全鎖(一) 聊聊Python中的GIL 聊聊Python中的GIL python基礎之執行鎖機制 python--threading執行總結 Python3入門之執行threading常用方法

1. 多執行緒程式設計與執行緒安全相關重要概念 在我的上篇博文 聊聊Python中的GIL 中,我們熟悉了幾個特別重要的概念:GIL,執行緒,程序, 執行緒安全,原子操作。 以下是簡單回顧,詳細介紹請直接看聊聊Python中的GIL  GIL:&n

秒殺執行第八 經典執行同步 訊號量Semaphore

前面介紹了關鍵段CS、事件Event、互斥量Mutex在經典執行緒同步問題中的使用。本篇介紹用訊號量Semaphore來解決這個問題。 首先也來看看如何使用訊號量,訊號量Semaphore常用有三個函式,使用很方便。下面是這幾個函式的原型和使用說明。 第一個 Create

秒殺執行第七 經典執行同步 互斥量Mutex

閱讀本篇之前推薦閱讀以下姊妹篇: 《秒殺多執行緒第四篇一個經典的多執行緒同步問題》 《秒殺多執行緒第五篇經典執行緒同步關鍵段CS》 《秒殺多執行緒第六篇經典執行緒同步事件Event》   前面介紹了關鍵段CS、事件Event在經典執行緒同步問題中的使用。本篇介紹用互斥量Mu

秒殺執行第一 執行筆試面試題彙總

    系列前言    本系列是本人蔘加微軟亞洲研究院,騰訊研究院,迅雷面試時整理的,另外也加入一些其它IT公司如百度,阿里巴巴的筆試面試題目,因此具有很強的針對性。系列中不但會詳細講解多執行緒同步互斥的各種“招式”,而且會進一步的講解多執行緒同步互斥的“內功心法”。有了“招

【伺服器效能】第二 分析執行日誌

1,執行緒狀態為“waiting for monitor entry”: 意味著它 在等待進入一個臨界區 ,所以它在”Entry Set“佇列中等待。 此時執行緒狀態一般都是 Blocked: java.lang.Thread.State: BLOCKED (on ob

秒殺執行第五 經典執行同步 關鍵段CS

                上一篇《秒殺多執行緒第四篇 一個經典的多執行緒同步問題》提出了一個經典的多執行緒同步互斥問題,本篇將用關鍵段CRITICAL_SECTION來嘗試解決這個問題。本文首先介紹下如何使用關鍵段,然後再深層次的分析下關鍵段的實現機制與原理。關鍵段CRITICAL_SECTION一共就

Java執行程式設計總結筆記——02執行基礎知識

讀解Thread類API 構造方法摘要 Thread(Runnable target) 分配新的 Thread 物件。 Thread(String name) 分配新的 Thread 物件。 方法摘要 static Thread cur

Java執行入門第二

Java執行緒通訊方法 0、(why)每個執行緒都有自己的棧空間,我們要執行緒之間進行交流,合作共贏。 1、synchronized和volatile關鍵字 a) 看下面的synchronized關鍵字 b) 看下面的volatile關鍵字 2、等待/通知機制:一個執行緒A呼

【面試總結】java執行

    4.1 ThreadLocal為什麼會產生記憶體洩露(建議結合原始碼學習)       https://blog.csdn.net/puppylpg/article/details/8043

C++執行-第四-Thread_group(執行池)

thread_group--執行緒池 Thread_group用於管理一組執行緒,類似一個執行緒池,使用std::list<thread*>來榮納建立的thread物件 Thread_group類摘要 Class thread_group //不可拷貝 { P

C++執行-第三-Thread(執行)

Thread //Boost #include<boost/thread/thread.hpp> #define BOOST_THREAD_VERSION 4 //使用最新版本,含有1,2,3但只是為了相容之前程式。 Thread庫豐富強大的擴充套件功能但不在T

windows C++執行程式設計高階 實現執行同步

    上一篇文章windows程式設計 使用C++實現多執行緒類僅僅是介紹了怎樣用類來實現多執行緒,這篇文章則重點介紹多執行緒中資料同步的問題。好了,廢話不多說,進入主題。     問題場景:這裡我們假設有這樣一個工作流水線(CWorkPipeline),它不斷的生成一

秒殺執行第五---經典執行同步 關鍵段(臨界區)CS

上一篇《秒殺多執行緒第四篇 一個經典的多執行緒同步問題》提出了一個經典的多執行緒同步互斥問題,本篇將用關鍵段CRITICAL_SECTION來嘗試解決這個問題。 本文首先介紹下如何使用關鍵段,然後再深層次的分析下關鍵段的實現機制與原理。 關鍵段CRITICA

SQLite3使用總結備忘(執行/WAL/鎖等)

1、多執行緒下使用    rc = sqlite3_open_v2(DBFILENAME, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX, NULL);2、多執行緒開啟WAL模式:rc = sqlite3_ex