VC中建立執行緒分析

2021-06-16 13:00:44 字數 2831 閱讀 7799

1、createthread、_beginthreadex、afxbeginthread的區別和正確使用:

createthread是乙個windows 的api函式,_beginthreadex是乙個微軟vc中c執行時庫中的執行緒建立函式,afxbeginthread則是mfc中的執行緒建立函式。

其依賴關係為:

createthread

createthread

_beginthreadex為每個使用執行緒在heap上建立(用__calloc_crt,相當於calloc)了乙個tiddata結構並且設定到動態tls。這樣c執行時庫中使用靜態變數的幾個函式就可以得到只和執行緒相關的乙份「靜態」和「全域性」變數了,c執行時全域性變數也不會互相干擾。假若使用createthread建立了這同樣的執行緒,因為沒有事先分配這個tiddata結構,那麼後果自然就很嚴重了。

但是使用了_beginthreadex也有乙個不算麻煩的小麻煩。

執行緒最好的退出方式是從return退出,所有的資源都會正確的釋放,如果呼叫exitthread退出執行緒,那麼執行緒堆疊上的內容被自動清除,但是c++物件不能夠通過呼叫析構函式而正確的清除。而和_beginthreadex對應的_endthreadex恰恰就是呼叫了exitthread來終止執行緒。所以,這下_endthreadex也不能呼叫了。那麼在前面分配的tiddata怎麼辦?沒有任何辦法,除非去呼叫_endthreadex才會得到釋放,但是exitthread語句又緊跟在後邊。還好,tiddata結構非常小,不超過100個位元組,但是要是遇上傻大黑粗的野蠻程式設計師,在程序生命期中反覆建立執行緒,也挺恐怖。

至於afxbeginthread,是mfc中定義的乙個函式,如果你使用mfc框架,那麼應該使用這個函式,這樣更符合框架的設計目的。這個函式內部的實現就是通過createthread,因為既然都使用mfc了,還費勁使用c函式庫幹嘛?

2、createthread的不知道算不算bug的bug:

createthread的定義請參見不同版本的msdn。

這個位址是vs2008 msdn中的定義

該api函式的第2個引數 dwstacksize 以及 第5個引數 dwcreationflag 分別指定了執行緒堆疊的大小和建立標誌。

按照文件中的說明,dwcreationflag有乙個設定值為stack_size_param_is_a_reservation。這個標誌的含義是,將指定大小的堆疊在虛擬記憶體中分配,並且初始只對映系統預設頁數的物理記憶體(大概是兩個頁),隨著堆疊使用的增大,系統根據堆疊物理記憶體頁後方的設定為page_guard屬性的頁觸發來把更多的物理記憶體對映到後續的頁。

值得注意的是,stack_size_param_is_a_reservation這個標誌只在windows xp以及windows 2003以及更新的系統上才會得到支援。如果沒有設定這個標誌,並且也讓執行緒一建立就開始執行(大部分時候是這樣),那麼系統會參照dwstacksize中指定的堆疊大小,並參照記憶體分配最小粒度以及頁對齊原則,把一定大小的物理記憶體對映給堆疊。

看上去一切都很不錯,但是如果你嘗試建立乙個會很快耗光堆疊的執行緒threadproc,並且用下面的語句建立:

createthread(null, 1024 * 1024 /* 1mb */, threadproc, null, 0, &threadid);

那麼你會震驚的發現:

程式因為exception_access_violation異常而直接退出了。沒有exception_stack_overflow。

你重新設定異常條件,決定捕捉全部的異常,但是你會發現你完全無法捕捉。why?

接下來,你把堆疊大小設定為:1024 * 1024 + 1 或者1024 * 1024 - 8092,那麼又可以準確的捕捉到溢位異常了。

或者你把dwstacksize設定為0,或者給dwcreationflag 加上stack_size_param_is_a_reservation標誌,也可以成功捕捉異常。

現在應該知道原因了。執行在x86上的windows xp,目前記憶體分配最小粒度為64kb,使用者態下頁面大小是4kb。

當把dwstacksize設定為0時,系統會預設設定堆疊為1mb大小,但是事實上執行緒得不到1mb的堆疊,因為系統會在堆疊最後的部分用加上page_guard屬性的頁來檢測溢位,並且還要保留用於處理溢位異常發生時的頁面,所以可以檢測到異常。

而給dwcreationflag 加上stack_size_param_is_a_reservation標誌時,按照64kb分配粒度的原則,堆疊最後的部分始終有足夠的頁來檢測溢位和處理異常。所以也很安全。

而為執行緒指定1024 * 1024大小堆疊時,堆疊邊界正好和64kb以及1mb邊界對齊的,當完全提交物理記憶體時,堆疊的所有頁的屬性都將是page_readwrite(事實上應該是乙個windows的bug,但是考慮效能原因而故意讓其存在)。所以系統根本沒機會檢測到溢位錯誤,而會在堆疊溢位後直接引發exception_access_violation。而因為沒有預留的異常處理頁面,異常處理程式又會引發乙個exception_access_violation異常。天啦!!整個程序就這樣崩潰了。

而為什麼指定1024 * 1024 + 1 或者1024 * 1024 - 8092就可以捕捉異常了呢?因為堆疊大小在超過1mb後,是按照1mb對齊原則,哪怕多乙個位元組,系統也會多對映1mb的物理記憶體。這樣足夠兩頁的異常檢測和異常處理頁面。1024 * 1024 - 8092大小正是這個原因所以能正確引發異常。

所以,正確的做法是,如果想在執行createthread時指定堆疊大小,並且全部提交物理記憶體的話,那麼大小必須保證在整1mb邊界上留出至少兩個頁面給系統。

VC建立執行緒

方法二 使用mfc全域性函式 cwinthread afxbeginthread afx threadproc pfnthreadproc,lpvoid pparam,int npriority thread priority normal,uint nstacksize 0,dword dwcre...

vc 執行緒的建立,暫停和結束

1.執行緒的建立 1 cwinthread mainthread mainthread afxbeginthread mythread,this,thread priority normal,0,0,null 2 後台工作執行緒函式 uint mythread lpvoid pparam 3 fun...

vc 執行緒的建立 暫停和結束

1.執行緒的建立 1 cwinthread mainthread mainthread afxbeginthread mythread,this,thread priority normal,0,0,null 2 後台工作執行緒函式 uint mythread lpvoid pparam 3 fun...