臨界區線程所有權
① windows中使用臨界區需不需要切換內核態
臨界區是一種輕量級機制,在某一時間內只允許一個線程執行某個給定代碼段。通常在多線程修改全局數據時會使用臨界區。事件、信號量也用於多線程同步,但臨界區與它們不同,並不總是執行向內核模式的切換,這一轉換成本昂貴。要獲得一個未佔用臨界區,事實上只需要對內存做出很少的修改,其速度非常快。只有在嘗試獲得已佔用臨界區時,它才會跳至內核模式。這一輕量級特性的缺點在於臨界區只能用於對同一進程內的線程進行同步。
臨界區由 WINNT.H 中所定義的 RTL_CRITICAL_SECTION 結構表示。 WINBASE.H 後您會發現:
typedef RTL_CRITICAL_SECTION CRITICAL_SECTION;
操作臨界區的API函數有:
(1)初始化臨界區InitializeCriticalSection
(2)進入臨界區EnterCriticalSection
(3)離開臨界區LeaveCriticalSection
(4)刪除臨界區DeleteCriticalSection
在臨界區未被使用的理想情況中,對 EnterCriticalSection 的調用非常快速,因為它只是讀取和修改用戶模式內存中的內存位置。所阻止的線程以內核模式等待,在該臨界區的所有者將其釋放之前,不能對這些線程進行調度。如果有多個線程被阻止於一個臨界區中,當另一線程釋放該臨界區時,只有一個線程獲得該臨界區。
RTL_CRITICAL_SECTION 結構
一個進程的臨界區是保存於一個鏈表中,並且可以對其進行枚舉。實際上,WINDBG 支持 !locks 命令,這一命令可以列出目標進程中的所有臨界區。
RTL_CRITICAL_SECTION 結構如下:
struct RTL_CRITICAL_SECTION
{
PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
LONG LockCount;
LONG RecursionCount;
HANDLE OwningThread;
HANDLE LockSemaphore;
ULONG_PTR SpinCount;
};
以下各段對每個欄位進行說明。
DebugInfo 此欄位包含一個指針,指向系統分配的伴隨結構,該結構的類型為 RTL_CRITICAL_SECTION_DEBUG。這一結構中包含更多極有價值的信息,也定義於 WINNT.H 中。LockCount 這是臨界區中最重要的一個欄位。它被初始化為數值 -1;此數值等於或大於 0 時,表示此臨界區被佔用。當其不等於 -1 時,OwningThread 欄位包含了擁有此臨界區的線程 ID。此欄位與 (RecursionCount-1) 數值之間的差值表示有多少個其他線程在等待獲得該臨界區。
RecursionCount 此欄位包含所有者線程已經獲得該臨界區的次數。如果該數值為零,下一個嘗試獲取該臨界區的線程將會成功。
OwningThread 此欄位包含當前佔用此臨界區的線程的線程標識符。此線程 ID 與 GetCurrentThreadId 之類的 API 所返回的 ID 相同。
LockSemaphore 它是一個內核對象句柄,用於通知操作系統:該臨界區現在空閑。操作系統在一個線程第一次嘗試獲得該臨界區,但被另一個已經擁有該臨界區的線程所阻止時,自動創建這樣一個句柄。應當調用 DeleteCriticalSection(它將發出一個調用該事件的 CloseHandle 調用,並在必要時釋放該調試結構),否則將會發生資源泄漏。
SpinCount 僅用於多處理器系統。MSDN文檔對此欄位進行如下說明:「在多處理器系統中,如果該臨界區不可用,調用線程將在對與該臨界區相關的信號執行等待操作之前,旋轉 dwSpinCount 次。如果該臨界區在旋轉操作期間變為可用,該調用線程就避免了等待操作。」旋轉計數可以在多處理器計算機上提供更佳性能,其原因在於在一個循環中旋轉通常要快於進入內核模式等待狀態。此欄位默認值為零,但可以用 API 將其設置為一個不同值。
RTL_CRITICAL_SECTION_DEBUG結構如下:
struct _RTL_CRITICAL_SECTION_DEBUG
{
WORD Type;
WORD CreatorBackTraceIndex;
RTL_CRITICAL_SECTION *CriticalSection;
LIST_ENTRY ProcessLocksList;
DWORD EntryCount;
DWORD ContentionCount;
DWORD Spare[ 2 ];
}
這一結構由InitializeCriticalSection分配和初始化。它既可以由NTDLL內的預分配數組分配,也可以由進程堆分配。RTL_CRITICAL_SECTION的這一伴隨結構包含一組匹配欄位,具有迥然不同的角色:有兩個難以理解,隨後兩個提供了理解這一臨界區鏈結構的關鍵,兩個是重復設置的,最後兩個未使用。
下面是對 RTL_CRITICAL_SECTION 欄位的說明。
Type 此欄位未使用,被初始化為數值 0。
CreatorBackTraceIndex 此欄位僅用於診斷情形中。在注冊表項 HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\YourProgram 之下是 keyfield、GlobalFlag 和 StackTraceDatabaseSizeInMb 值。注意,只有在運行稍後說明的 Gflags 命令時才會顯示這些值。這些注冊表值的設置正確時,CreatorBackTraceIndex 欄位將由堆棧跟蹤中所用的一個索引值填充。在 MSDN 中搜索 GFlags 文檔中的短語「create user mode stack trace database」和「enlarging the user-mode stack trace database」,可以找到有關這一內容的更多信息。
CriticalSection 指向與此結構相關的 RTL_CRITICAL_SECTION。圖 1 說明該基礎結構以及 RTL_CRITICAL_SECTION、RTL_CRITICAL_SECTION_DEBUG 和事件鏈中其他參與者之間的關系。
圖 1 臨界區處理流程
ProcessLocksList LIST_ENTRY 是用於表示雙向鏈表中節點的標准 Windows 數據結構。RTL_CRITICAL_SECTION_DEBUG 包含了鏈表的一部分,允許向前和向後遍歷該臨界區。本文後面給出的實用工具說明如何使用 Flink(前向鏈接)和 Blink(後向鏈接)欄位在鏈表中的成員之間移動。任何從事過設備驅動程序或者研究過 Windows 內核的人都會非常熟悉這一數據結構。
EntryCount/ContentionCount 這些欄位在相同的時間、出於相同的原因被遞增。這是那些因為不能馬上獲得臨界區而進入等待狀態的線程的數目。與 LockCount 和 RecursionCount 欄位不同,這些欄位永遠都不會遞減。
Spares 這兩個欄位未使用,甚至未被初始化(盡管在刪除臨界區結構時將這些欄位進行了清零)。後面將會說明,可以用這些未被使用的欄位來保存有用的診斷值。
總結:
(1)如果 LockCount 欄位有一個不等於 -1 的數值,此臨界區被佔用,OwningThread 欄位包含擁有該臨界區的線程的線程標識符。
(2)如果 RecursionCount 是一個大於 1 的數值,其告知您所有者線程已經重新獲得該臨界區多少次(也許不必要)。
(3)LockCount 與 RecursionCount 欄位中分別包含其初始值 -1 和 0,這一點非常重要。事實上,對於單線程程序,不能僅通過檢查這些欄位來判斷是否曾獲得過臨界區。但是,多線程程序留下了一些標記,可以用來判斷是否有兩個或多個線程試圖同時擁有同一臨界區。
(4)在該臨界區未被佔用時 LockSemaphore 欄位中仍包含一個非零值。這表示:在某一時間,此臨界區阻止了一個或多個線程。事件句柄用於通知該臨界區已被釋放,等待該臨界區的線程之一現在可以獲得該臨界區並繼續執行。因為 OS 在臨界區阻止另一個線程時自動分配事件句柄,所以如果您在不再需要臨界區時忘記將其刪除,LockSemaphore 欄位可能會導致程序中發生資源泄漏。
(5)在多線程程序中可能遇到的另一狀態是 EntryCount 和 ContentionCount 欄位包含一個大於零的數值。這兩個欄位保存有臨界區對一個線程進行阻止的次數。在每次發生這一事件時,這兩個欄位被遞增,但在臨界區存在期間不會被遞減。這些欄位可用於間接確定程序的執行路徑和特性。例如,EntryCount 非常高時則意味著該臨界區經歷著大量爭用,可能會成為代碼執行過程中的一個潛在瓶頸。
(6)可以通過RTL_CRITICAL_SECTION_DEBUG 中的LIST_ENTRY 遍歷進程中的臨界區,Flink=NULL為表頭,Blink=NULL為表尾。
(7)利用RTL_CRITICAL_SECTION 的 Spare 欄位可以區分我們定義的臨界區和系統定義的臨界區。
② 臨界區的臨界區存在的幾個問題
在使用臨界區時,一般不允許其運行時間過長,只要進入臨界區的線程還沒有離開,其他所有試圖進入此臨界區的線程都會被掛起而進入到等待狀態,並會在一定程度上影響程序的運行性能。尤其需要注意的是不要將等待用戶輸入或是其他一些外界干預的操作包含到臨界區。如果進入了臨界區卻一直沒有釋放,同樣也會引起其他線程的長時間等待。換句話說,在執行了EnterCriticalSection()語句進入臨界區後無論發生什麼,必須確保與之匹配的LeaveCriticalSection()都能夠被執行到。可以通過添加結構化異常處理代碼來確保LeaveCriticalSection()語句的執行。雖然臨界區同步速度很快,但卻只能用來同步本進程內的線程,而不可用來同步多個進程中的線程。
1、 臨界區的退出,不會檢測是否是已經進入的線程,也就是說,我可以在A線程中調用進入臨界區函數,在B線程調用退出臨界區的函數,同樣是成功;
2、 我在測試臨界區的時候,如果我沒有調用進入臨界區的函數,直接退出的話,系統沒有進行判斷,但是計數發現了改變,此時此臨界區就再也用不了了,因為結構中的數據已經亂掉了。
解決方法如下:
ypedef class mutex_lock
{
public:
mutex_lock()
: LockCount(-1)
, hEvent(0)
{
}
~mutex_lock()
{
if(NULL != this->hEvent)
{
CloseHandle(this->hEvent);
}
this->hEvent = NULL;
this->LockCount = -1;
}
long GetLock();
long ReleaseLock();
private:
mutex_lock(mutex_lock&);
long LockCount;
HANDLE hEvent;
} MUTEXLOCK, *LPMUTEXLOCK;
long mutex_lock::GetLock()
{
__asm
{
movebx, [this];//把基址保存在ebx中
lock incdword ptr [ebx];//對LockCount進行加鎖加,保證多CPU時的唯一性,
jeLRET1;//如果LockCount加1為0的話,表示沒有人在使用資源,同時利用上面的互斥加對資源進行佔用。
cmpdword ptr [ebx+4], 0;//此時LockCount加1大於0的情況,表示已有人使用此資源,要對此資源進行加內核鎖
jneL1;//如果平常沒有創建內核鎖,則進行創始一個
push 0;
push 0;
push 0;
push 0;
call dword ptr [CreateEvent];
movedx, eax;//創建內存鎖成功後,在同樣對hEvent變數進行互斥比較後替換,以處理多個線程同時對此值進行替換的情況,保證只有一個能夠成功替換
moveax, 0;
lock cmpxchg dword ptr [ebx+4], edx; //互斥比較替換
jeL1;
push edx;//如果已經被別人替換過了,需要把自己創建的內核鎖釋放
call dword ptr [CloseHandle];
L1:
push INFINITE;
push [ebx+4];
call dword ptr [WaitForSingleObject]; //在內核級進行等待
LRET1:
moveax, 0
}
}
long mutex_lock::ReleaseLock()
{
__asm
{
movebx, [this];//把基址保存在ebx中
moveax, -1;
lock xadd dword ptr [ebx], eax; //進行交換自減交換操作,運行後,eax中是第一操作數先前的值,此值用會返回用來判斷是否多調用了ReleaseLock
jlLRET2;//沒有別的線程佔用資源,直接返回,用戶可以通過返回值,分析是否失敗
cmpdword ptr [ebx+4], 0;//有別的線程在等待,檢查內核鎖,如果沒有則進行創建
jneL2;
push 0;
push 0;
push 0;
push 0;
call dword ptr [CreateEvent];
movedx, eax;
moveax, 0;
lock cmpxchg dword ptr [ebx+4], edx; //互斥替換內核鎖句柄
jeL2;//已經有內核鎖了,把自已申請的關閉
push edx;
call dword ptr [CloseHandle];
L2:
push [ebx+4];//設計信號,喚醒一個線程
call dword ptr [SetEvent];
moveax, 0;
LRET2:
}
}
③ 多線程裡面的臨界區如何使用
一般用於多個線程要跑同一個函數,這個函數又要操作一個資源的時候。。。
比如你有個生產電腦的流水線,多個線程都要調用這個流水線生產電腦,那麼在生產過程中可能就需要用臨界區確保每次只有一台電腦在生產。
④ 多線程的臨界區域,是如何設定的呢
使用四個函數,InitializeCriticalSection()初始化臨界區回,EnterCriticalSection()進入臨界區,LeaveCriticalSection()離開答臨界區,DeleteCriticalSection()刪除臨界區。
⑤ linux多線程 有 臨界區嗎
臨界區是自己創建的
⑥ java線程如何保護臨界區
每個進程中訪問臨界資源的那段程序稱為臨界區(Critical Section)(臨界資源是一次僅允許一個進內程使用的容共享資源)。每次只准許一個進程進入臨界區,進入後不允許其他進程進入。不論是硬體臨界資源,還是軟體臨界資源,多個進程必須互斥地對它進行訪問。 多個進程中涉及到同一個臨界資源的臨界區稱為相關臨界區。 進程進入臨界區的調度原則是: ①如果有若干進程要求進入空閑的臨界區,一次僅允許一個進程進入。②任何時候,處於臨界區內的進程不可多於一個。如已有進程進入自己的臨界區,則其它所有試圖進入臨界區的進程必須等待。③進入臨界區的進程要在有限時間內退出,以便其它進程能及時進入自己的臨界區。④如果進程不能進入自己的臨界區,則應讓出CPU,避免進程出現「忙等」現象。 如果有多個線程試圖同時訪問臨界區,那麼在有一個線程進入後其他所有試圖訪問此臨界區的線程將被掛起,並一直持續到進入臨界區的線程離開。臨界區在被釋放後,其他線程可以繼續搶占,並以此達到用原子方式操作共享資源的目的。
⑦ 簡述什麼是線程同步機制和臨界區管理
臨界抄區的基本概念
臨界區:在同一個進程內的多個線程之間通過原子方式實現共享資源的串列化讀寫。
臨界區相關函數
CRITICAL_SECTION cs;
::InitializeCriticalSection(&cs);
::EnterCriticalSection(&cs);
::LeaveCriticalSection(&cs);
::DeleteCriticalSection(&cs);
臨界區編程實例
實例一:臨界區基本使用方法
輸出結果(注掉13,17,沒有進行臨界區同步):
輸出結果(臨界區同步):
實例二:使用臨界區對象創建自動鎖
CriticalSection:封裝 Windows CRICITAL_SECTION 用戶對象
AutoLock:封裝CriticalSection的操作,讓它進入一個Scope的時候自動加鎖,離開一個Scope的時候自動解鎖
⑧ 什麼時候使用互斥體,什麼時候用臨界區
互斥體實現了「互相排斥」(mutual exclusion)同步的簡單形式(所以名為互斥體(mutex))。互回斥體禁止多個線程同時進入答受保護的代碼「臨界區」(critical section)。
每個進程中訪問臨界資源的那段代碼稱為臨界區(Critical Section)(臨界資源是一次僅允許一個進程使用的共享資源)。每次只准許一個進程進入臨界區,進入後不允許其他進程進入。不論是硬體臨界資源,還是軟體臨界資源,多個進程必須互斥地對它進行訪問。
多個進程中涉及到同一個臨界資源的臨界區稱為相關臨界區。.
在任意時刻,只有一個線程被允許進入這樣的代碼保護區。任何線程在進入臨界區之前,必須獲取(acquire)與此區域相關聯的互斥體的所有權。如果已有另一線程擁有了臨界區的互斥體,其他線程就不能再進入其中。這些線程必須等待,直到當前的屬主線程釋放(release)該互斥體。什麼時候需要使用互斥體呢?互斥體用於保護共享的易變代碼,也就是,全局或靜態數據。這樣的數據必須通過互斥體進行保護,以防止它們在多個線程同時訪問時損壞。
⑨ 臨界區的線程同步問題
有多個線程試圖同時訪問臨界區,那麼在有一個線程進入後其他所有試圖訪問此臨界區的線程將被掛起,並一直持續到進入臨界區的線程離開。臨界區在被釋放後,其他線程可以繼續搶占,並以此達到用原子方式操作共享資源的目的。
臨界區在使用時以CRITICAL_SECTION結構對象保護共享資源,並分別用EnterCriticalSection()和LeaveCriticalSection()函數去標識和釋放一個臨界區。所用到的CRITICAL_SECTION結構對象必須經過InitializeCriticalSection()的初始化後才能使用,而且必須確保所有線程中的任何試圖訪問此共享資源的代碼都處在此臨界區的保護之下。否則臨界區將不會起到應有的作用,共享資源依然有被破壞的可能。
下面通過一段代碼展示了臨界區在保護多線程訪問的共享資源中的作用。通過兩個線程來分別對全局變數g_cArray[10]進行寫入操作,用臨界區結構對象g_cs來保持線程的同步,並在開啟線程前對其進行初始化。為了使實驗效果更加明顯,體現出臨界區的作用,在線程函數對共享資源g_cArray[10]的寫入時,以Sleep()函數延遲1毫秒,使其他線程同其搶佔CPU的可能性增大。如果不使用臨界區對其進行保護,則共享資源數據將被破壞(參見圖1(a)所示計算結果),而使用臨界區對線程保持同步後則可以得到正確的結果(參見圖1(b)所示計算結果)。
代碼實現清單附下: //臨界區結構對象CRITICAL_SECTIONg_cs;//共享資源charg_cArray[10];UINTThreadProc10(LPVOIDpParam){//進入臨界區EnterCriticalSection(&g_cs);//對共享資源進行寫入操作for(inti=0;i<10;i++){g_cArray[i]=a;Sleep(1);}//離開臨界區LeaveCriticalSection(&g_cs);return0;}UINTThreadProc11(LPVOIDpParam){//進入臨界區EnterCriticalSection(&g_cs);//對共享資源進行寫入操作for(inti=0;i<10;i++){g_cArray[10-i-1]=b;Sleep(1);}//離開臨界區LeaveCriticalSection(&g_cs);return0;}……voidCSample08View::OnCriticalSection(){//初始化臨界區InitializeCriticalSection(&g_cs);//啟動線程AfxBeginThread(ThreadProc10,NULL);AfxBeginThread(ThreadProc11,NULL);//等待計算完畢Sleep(300);//報告計算結果CStringsResult=CString(g_cArray);AfxMessageBox(sResult);}
⑩ 何謂臨界區下面給出的兩個進程互斥的演算法是安全的嗎為什麼
臨界區就是只有一個線程能進去的區域,進程互斥可以說是安全的,因為不同的進程不共享內存。
但是也有用到互斥的地方,比如文件鎖,
消息隊列等