1. 程式人生 > >秒殺多執行緒第五篇---經典執行緒同步 關鍵段(臨界區)CS

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

上一篇《秒殺多執行緒第四篇 一個經典的多執行緒同步問題》提出了一個經典的多執行緒同步互斥問題,本篇將用關鍵段CRITICAL_SECTION來嘗試解決這個問題。

本文首先介紹下如何使用關鍵段,然後再深層次的分析下關鍵段的實現機制與原理。

關鍵段CRITICAL_SECTION一共就四個函式,使用很是方便。下面是這四個函式的原型和使用說明。

1、函式功能:初始化

函式原型:

void InitializeCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);

函式說明:定義關鍵段變數後必須先初始化。

2、函式功能:銷燬

函式原型:

void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

函式說明:用完之後記得銷燬。

3、函式功能:進入關鍵區域

函式原型:

void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

函式說明:系統保證各執行緒互斥的進入關鍵區域。

4、函式功能:離開關關鍵區域

函式原型:

void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

然後在經典多執行緒問題中設定二個關鍵區域。一個是主執行緒在遞增子執行緒序號時

,另一個是各子執行緒互斥的訪問輸出全域性資源時。詳見程式碼:

#include <stdio.h>
#include <process.h>
#include <windows.h>
long g_nNum;
unsigned int __stdcall Fun(void *pPM);
const int THREAD_NUM = 10;
//關鍵段變數宣告
CRITICAL_SECTION  g_csThreadParameter, g_csThreadCode;
int main()
{
    printf("----經典執行緒同步 關鍵段----\n");
    printf
(" -- WILL for study --\n\n"); //關鍵段初始化 InitializeCriticalSection(&g_csThreadParameter); InitializeCriticalSection(&g_csThreadCode); HANDLE handle[THREAD_NUM]; g_nNum = 0; int i = 0; while (i < THREAD_NUM) { EnterCriticalSection(&g_csThreadParameter);//進入子執行緒序號關鍵區域 handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL); ++i; } WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE); DeleteCriticalSection(&g_csThreadCode); DeleteCriticalSection(&g_csThreadParameter); return 0; } unsigned int __stdcall Fun(void *pPM) { int nThreadNum = *(int *)pPM; LeaveCriticalSection(&g_csThreadParameter);//離開子執行緒序號關鍵區域 Sleep(50);//some work should to do EnterCriticalSection(&g_csThreadCode);//進入各子執行緒互斥區域 g_nNum++; Sleep(0);//some work should to do printf("執行緒編號為%d 全域性資源值為%d\n", nThreadNum, g_nNum); LeaveCriticalSection(&g_csThreadCode);//離開各子執行緒互斥區域 return 0; }

結果:

----經典執行緒同步 關鍵段----
 -- WILL for study --

執行緒編號為2  全域性資源值為1
執行緒編號為6  全域性資源值為2
執行緒編號為4  全域性資源值為3
執行緒編號為8  全域性資源值為4
執行緒編號為6  全域性資源值為5
執行緒編號為4  全域性資源值為6
執行緒編號為9  全域性資源值為7
執行緒編號為3  全域性資源值為8
執行緒編號為10  全域性資源值為9
執行緒編號為7  全域性資源值為10

Process returned 0 (0x0)   execution time : 0.372 s
Press any key to continue.

可以看出來,各子執行緒已經可以互斥的訪問與輸出全域性資源了,但主執行緒與子執行緒之間的同步還是有點問題。

這是為什麼了?

要解開這個迷,最直接的方法就是先在程式中加上斷點來檢視程式的執行流程。斷點處置示意如下:
這裡寫圖片描述

然後按F5進行除錯,正常來說這兩個斷點應該是依次輪流執行,但實際除錯時卻發現不是如此,主執行緒可以多次通過第一個斷點即

EnterCriticalSection(&g_csThreadParameter);//進入子執行緒序號關鍵區域

這一語句。這說明主執行緒能多次進入這個關鍵區域!找到主執行緒和子執行緒沒能同步的原因後,下面就來分析下原因的原因吧^_^

先找到關鍵段CRITICAL_SECTION的定義吧,它在WinBase.h中被定義成RTL_CRITICAL_SECTION。而RTL_CRITICAL_SECTION在WinNT.h中宣告,它其實是個結構體

typedef struct _RTL_CRITICAL_SECTION {
    PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
    LONG LockCount;
    LONG RecursionCount;
    HANDLE OwningThread; // from the thread's ClientId->UniqueThread
    HANDLE LockSemaphore;
    DWORD SpinCount;
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;

各個引數的解釋如下:

第一個引數:PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
除錯用的。

第二個引數:LONG LockCount;
初始化為-1,n表示有n個執行緒在等待。

第三個引數:LONG RecursionCount;
表示該關鍵段的擁有執行緒對此資源獲得關鍵段次數,初為0。

第四個引數:HANDLE OwningThread;
即擁有該關鍵段的執行緒控制代碼,微軟對其註釋為——from the thread’s ClientId->UniqueThread

第五個引數:HANDLE LockSemaphore;
實際上是一個自復位事件。

第六個引數:DWORD SpinCount;
旋轉鎖的設定,單CPU下忽略

由這個結構可以知道關鍵段會記錄擁有該關鍵段的執行緒控制代碼即關鍵段是有“執行緒所有權”概念的。!!!事實上它會用第四個引數OwningThread來記錄獲准進入關鍵區域的執行緒控制代碼,如果這個執行緒再次進入,EnterCriticalSection()會更新第三個引數RecursionCount以記錄該執行緒進入的次數並立即返回讓該執行緒進入。其它執行緒呼叫EnterCriticalSection()則會被切換到等待狀態,一旦擁有執行緒所有權的執行緒呼叫LeaveCriticalSection()使其進入的次數為0時,系統會自動更新關鍵段並將等待中的執行緒換回可排程狀態。

因此可以將關鍵段比作旅館的房卡,呼叫EnterCriticalSection()即申請房卡,得到房卡後自己當然是可以多次進出房間的,在你呼叫LeaveCriticalSection()交出房卡之前,別人自然是無法進入該房間。!!!!!!

回到這個經典執行緒同步問題上,主執行緒正是由於擁有“執行緒所有權”即房卡,所以它可以重複進入關鍵程式碼區域從而導致子執行緒在接收引數之前主執行緒就已經修改了這個引數。所以關鍵段可以用於執行緒間的互斥,但不可以用於同步。!!!!!!

另外,由於將執行緒切換到等待狀態的開銷較大,因此為了提高關鍵段的效能,Microsoft將旋轉鎖合併到關鍵段中,這樣EnterCriticalSection()會先用一個旋轉鎖不斷迴圈,嘗試一段時間才會將執行緒切換到等待狀態。下面是配合了旋轉鎖的關鍵段初始化函式

1、函式功能:初始化關鍵段並設定旋轉次數
函式原型:

BOOL InitializeCriticalSectionAndSpinCount(

  LPCRITICAL_SECTION lpCriticalSection,

  DWORD dwSpinCount);

函式說明:旋轉次數一般設定為4000。

2、函式功能:修改關鍵段的旋轉次數
函式原型:

DWORD SetCriticalSectionSpinCount(

  LPCRITICAL_SECTION lpCriticalSection,

  DWORD dwSpinCount);

《Windows核心程式設計》第五版的第八章推薦在使用關鍵段的時候同時使用旋轉鎖,這樣有助於提高效能。!!!值得注意的是如果主機只有一個處理器,那麼設定旋轉鎖是無效的。無法進入關鍵區域的執行緒總會被系統將其切換到等待狀態。!!!

最後總結下關鍵段:

1.關鍵段共初始化、銷燬、進入和離開關鍵區域四個函式。
2.關鍵段可以解決執行緒的互斥問題,但因為具有“執行緒所有權”,所以無法解決同步問題。!!!!!!!!!!!!!!!!!!
3.推薦關鍵段與旋轉鎖配合使用。

下一篇《秒殺多執行緒第六篇 經典執行緒同步 事件Event》將介紹使用事件Event來解決這個經典執行緒同步問題。

相關推薦

執行---經典執行同步 關鍵臨界CS

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

線程 經典線程同步 信號量Semaphore

max create 知識 inf 消費 資源 close infinite 圖片 前面介紹了關鍵段CS、事件Event、互斥量Mutex在經典線程同步問題中的使用。本篇介紹用信號量Semaphore來解決這個問題。 首先也來看看如何使用信號量,信號量Semaphore常用

線程 經典線程同步總結 關鍵 事件 互斥量 信號量

事件觸發 內核 clas article ase 理解 handle order 線程並發 本文轉載於:http://blog.csdn.net/morewindows/article/details/7538247 來詳細介紹常用的線程同步互斥機制——關鍵段、事件、互斥量

執行 經典執行同步 關鍵CS

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

執行 經典執行同步 訊號量Semaphore

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

執行 經典執行同步 互斥量Mutex

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

執行 經典執行同步總結 關鍵 事件 互斥量 訊號量

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

LinuxC執行程式設計執行通訊(Condition)

        執行緒同步還有一種情況,執行緒1需要等某個條件成立才能繼續往下執行,如果這個條件不成立,執行緒1就阻塞等待,執行緒2在執行某個條件成立了就喚醒執行緒1。這個和Java中的wait()和notify()其實是一樣的 初始化與銷燬通訊條件 #include &

C++執行--同步機制

Call_once 使用call_once包裝的函式在多執行緒中只會被執行一次。 Void call_once(once_flag&flag, Callable && func,Args &&.....args); 其中once_fl

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

:基本資料型別及用法2

1 info={"k1":"v1","k2":"v2"} 2 v=info.setdefault("k3",123) 3 print(info,v) 4 #結果:{'k1': 'v1', 'k2': 'v2', 'k3': 123} 123   字典dict 1.字典的元素是鍵值

輕輕鬆鬆學習SpringBoot2:第二十: Spring Boot和Mongodb整合完整版

今天主要講的是Spring Boot和Mongodb整合我們先來回顧一下前面章節的相關內容前面我們講了SpringBoot和mysql整合,並且講了操作資料庫的幾種方式自動生成表資料庫操作操作篇回到正題,mongodb的安裝在這就不累述了,win版本的去官網下載,然後一直下一

線程面試題系列5經典線程同步 關鍵CS

得到 bug oar -- 多線程同步 實現 unsigned 初始化 alt 上一篇提出了一個經典的多線程同步互斥問題,本篇將用關鍵段CRITICAL_SECTION來嘗試解決這個問題。本文首先介紹下如何使用關鍵段,然後再深層次的分析下關鍵段的實現機制與原理。關鍵段CRI

執行 一個經典執行同步問題

上一篇《秒殺多執行緒第三篇原子操作 Interlocked系列函式》中介紹了原子操作在多程序中的作用,現在來個複雜點的。這個問題涉及到執行緒的同步和互斥,是一道非常有代表性的多執行緒同步問題,如果能將這個問題搞清楚,那麼對多執行緒同步也就打下了良好的基礎。程式描述:主執行緒啟

執行 原子操作 Interlocked系列函式

上一篇《多執行緒第一次親密接觸 CreateThread與_beginthreadex本質區別》中講到一個多執行緒報數功能。為了描述方便和程式碼簡潔起見,我們可以只輸出最後的報數結果來觀察程式是否執行出錯。這也非常類似於統計一個網站每天有多少使用者登入,每個使用者登入用一個執

執行十一 讀者寫者問題

                與上一篇《秒殺多執行緒第十篇 生產者消費者問題》的生產者消費者問題一樣,讀者寫者也是一個非常著名的同步問題。讀者寫者問題描述非常簡單,有一個寫者很多讀者,多個讀者可以同時讀檔案,但寫者在寫檔案時不允許有讀者在讀檔案,同樣有讀者在讀檔案時寫者也不去能寫檔案。上面是讀者寫者問題示意

執行 生產者消費者問題

    繼經典執行緒同步問題之後,我們來看看生產者消費者問題及讀者寫者問題。生產者消費者問題是一個著名的執行緒同步問題,該問題描述如下:有一個生產者在生產產品,這些產品將提供給若干個消費者去消費,為了使生產者和消費者能併發執行,在兩者之間設定一個具有多個緩衝區的緩衝池,生產者

執行第二 執行第一次親密接觸 CreateThread與_beginthreadex本質區別

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

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

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