1. 程式人生 > >Delphi執行緒同步(臨界區、互斥、訊號量)

Delphi執行緒同步(臨界區、互斥、訊號量)

  當有多個執行緒的時候,經常需要去同步這些執行緒以訪問同一個資料或資源。

  例如,假設有一個程式,其中一個執行緒用於把檔案讀到記憶體,而另一個執行緒用於統計檔案的字元數。當然,在整個檔案調入記憶體之前,統計它的計數是沒有意義的。但是,由於每個操作都有自己的執行緒,作業系統會把兩個執行緒當做是互不相干的任務分別執行,這樣就可能在沒有把整個檔案裝入記憶體時統計字數。為解決此問題,你必須使兩個執行緒同步工作

  存在一些執行緒同步地址的問題,Win 32 提供了許多執行緒同步的方式。這裡將會講到:臨界區、互斥、訊號量和事件

  為了檢驗這些技術,首先來看一個需要執行緒同步解決的問題。假設有一個整數陣列,需要按照升序賦初值。現在要在第一遍把這個陣列賦初值為1~128,第二遍將此陣列賦初值為128~255,然後結果顯示在列表中。要用兩個執行緒來分別進行初始化。下面的程式碼給出了沒有做執行緒同步的程式碼

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 unit Main; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
type TMainForm =class(TForm) Button1: TButton; ListBox1: TListBox; procedure Button1Click(Sender: TObject); private procedure ThreadsDone(Sender: TObject); end; TFooThread = class(TThread) protected procedure Execute; override; end; var MainForm: TMainForm; implementation {$R *.DFM} const MaxSize =
128; var NextNumber: Integer = 0; DoneFlags: Integer = 0; GlobalArray: array[1..MaxSize] of Integer; function GetNextNumber: Integer; begin Result:= NextNumber;    //return global var Inc(NextNumber);    //inc global var end; procedure TFooThread.Execute; var i: Integer; begin OnTerminate:= MainForm.ThreadsDone; for i:=1 to MazSize do begin GlobalArray[i]:= GetNextNumber; Sleep(5); end; end; procedure TMainForm.ThreadsDone(Sender: TObject); var i: Integer; begin Inc(DoneFlags); if DoneFlags = 2 then for i:=1 to MaxSize do ListBox1.Items.Add(IntToStr(GlobalArray[i]));    //注意ListBox的使用,並看下面編譯執行的效果圖 end; procedure TMainForm.Button1Click(Sender: TObject); begin TFooThread.Create(False);    //建立一個新的執行緒 TFooThread.Create(Flase);    //再建立一個新的執行緒 end; end.

  因為兩個執行緒同時執行,同一個陣列在兩個執行緒中被初始化會出現什麼呢?你可以看下面的截圖

  這個問題的解決方案是:當兩個執行緒訪問這個全域性陣列時,為防止它們同時執行,需要使用執行緒的同步。這樣,你就會得到一組合理的數值

1.臨界區

  臨界區是一種最直接的執行緒同步方法。所謂臨界區,就是一次只能有一個執行緒來執行的一段程式碼。如果把初始化陣列的程式碼放在臨界區內,那麼另一個執行緒在第一個執行緒處理完之前是不會被執行的

  在使用臨界區之前,必須使用 InitializeCriticalSection()過程初始化它,其宣告如下

?
1 procedure InitializeCriticalSection(var lpCriticalSection: TRLCriticalSection); stdcall;

  lpCriticalSection引數是一個TRTLCriticalSection型別的記錄,並且是變參。至於TRTLCriticalSection是如何定義的,這並不重要,因為很少需要檢視這個記錄中的具體內容。只需要在lpCriticalSection中傳遞為初始化的記錄,InitializeCriticalSection()過程就會填充這個記錄

  注意:Microsoft 故意隱瞞了TRTLCriticalSection 的細節。因為,其內容在不同的硬體平臺上是不同的。在基於Intel 的平臺上,TRTLCriticalSection 包含一個計數器、一個指示當前執行緒控制代碼的域和一個系統事件的控制代碼。在Alpha 平臺上,計數器被替換為一種Alpha-CPU 資料結構,稱為spinlock。

  在記錄被填充之後,我們就可以開始建立臨界區了。這是我們需要使用EnterCriticalSection() 和LeaveCriticalSection() 來封裝程式碼塊。這兩個過程的宣告如下

?
1 2 3 procedure EnterCriticalSection(var lpCriticalSection: TRTLCriticalSection); stdcall; procedure LeaveCriticalSection(var lpCriticalSection: TRTLCriticalSection); stdcall;

  正如你所想的,引數 lpCriticalSection 就是有InitializeCriticalSection() 填充的記錄

  當你不需要TRTLCriticalSection 記錄時,應當呼叫 DeleteCriticalSection() 過程,下面是它的宣告

?
1 procedure DeleteCriticalSection(var lpCriticalSection: TRTLCriticalSection); stdcall;

  下面演示利用臨界區來同步陣列初始化執行緒的技術

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 unit Main; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TMainForm =class(TForm) Button1: TButton; ListBox1: TListBox; procedure Button1Click(Sender: TObject); private procedure ThreadsDone(Sender: TObject); end; TFooThread = class(TThread) protected procedure Execute; override; end; var MainForm: TMainForm; implementation {$R *.DFM} const MaxSize = 128; var NextNumber: Integer = 0; DoneFlags: Integer = 0; GlobalArray: array[1..MaxSize] of Integer; CS: TRTLCriticalSection;        // function GetNextNumber: Integer; begin Result:= NextNumber;    //return global var Inc(NextNumber);    //inc global var end; procedure TFooThread.Execute; var i: Integer; begin OnTerminate:= MainForm.ThreadsDone; EnterCriticalSection(CS);        // for i:=1 to MazSize do begin GlobalArray[i]:= GetNextNumber; Sleep(5); end; LeaveCriticalSection(CS);        // end; procedure TMainForm.ThreadsDone(Sender: TObject); var i: Integer; begin Inc(DoneFlags); if DoneFlags = 2 then for i:=1 to MaxSize do ListBox1.Items.Add(IntToStr(GlobalArray[i])); DeleteCriticalSection(CS);    // end; end; procedure TMainForm.Button1Click(Sender: TObject); begin InitializeCriticalSection(CS);    // TFooThread.Create(False);    //建立一個新的執行緒 TFooThread.Create(Flase);    //再建立一個新的執行緒 end; end.

  在第一個執行緒呼叫EnterCriticalSection()之後,所有別的執行緒就不能再進入程式碼塊。下一個執行緒要等到第一個執行緒呼叫LeaveCriticalSection()之後才能被喚醒,輸出結果顯示如下

2.互斥

  互斥非常類似於臨界區,除了兩個關鍵的區別:

1)首先,互斥可用於跨程序的執行緒同步

2)其次,互斥能被賦予一個字串名字,並且通過引用此名字建立現有互斥物件的附加控制代碼

  提示:臨界區與事件物件(比如互斥物件)的最大的區別在效能上。臨界區在沒有執行緒衝突時,要用10~15個時間片,而事件物件由於涉及到系統核心,所以要用400~600個時間片

  可以呼叫函式CreatMutex() 來建立一個互斥量。下面是函式的宣告

?
1 function CreateMutex(lpMutexAttributes: PSecurityAttributes; bInitialOwner: BOOL; lpName: PChar): THandle; stdcall;

  lpMutexAttributes 引數為一個指向TSecurityAttributes記錄的指標。此引數通常設為nil , 表示預設的安全屬性

  bInitalOwner 引數表示建立互斥物件執行緒是否稱為互斥物件的擁有者。當此引數為False時,表示互斥物件沒有擁有者。

  lpName 引數指定互斥物件的名稱。設為nil表示無命名,如果引數不設為nil,函式會搜尋是否有同名的互斥物件存在。如果有,函式就會返回同名互斥物件的控制代碼。否則,就新建立一個互斥物件並返回其控制代碼

  當使用完互斥物件時,應當呼叫CloseHandle()來關閉它

  下面演示使用互斥技術來使兩個程序對一個數組的初始化同步

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 unit Main; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TMainForm =class(TForm) Button1: TButton; ListBox1: TListBox; procedure Button1Click(Sender: TObject); private procedure ThreadsDone(Sender: TObject); end; TFooThread = class(TThread) protected procedure Execute; override; end; var MainForm: TMainForm; implementation {$R *.DFM} const MaxSize = 128; var NextNumber: Integer = 0; DoneFlags: Integer = 0; GlobalArray: array[1..MaxSize] of Integer; hMutex: THandle = 0;        // function GetNextNumber: Integer; begin Result:= NextNumber;    //return global var Inc(NextNumber);    //inc global var end; procedure TFooThread.Execute; var i: Integer; begin FreeOnTerminate:= True; OnTerminate:= MainForm.ThreadsDone; if WaitForSingleObject(hMutex, INFINITE) = WAIT_OBJECT_0 then        // begin for i:=1 to MazSize do begin GlobalArray[i]:= GetNextNumber; Sleep(5); end; end; ReleaseMutex(hMutex);        // end; procedure TMainForm.ThreadsDone(Sender: TObject); var i: Integer; begin Inc(DoneFlags); if DoneFlags = 2 then for i:=1 to MaxSize do ListBox1.Items.Add(IntToStr(GlobalArray[i])); CloseHandle(hMutex);    // end; end; procedure TMainForm.Button1Click(Sender: TObject); begin hMutex:= CreateMutex(nil, False, nil);     // TFooThread.Create(False);    //建立一個新的執行緒 TFooThread.Create(Flase);    //再建立一個新的執行緒 end; end.

  你將注意到,在程式中使用 WaitForSingleObject() 來防止其他程序進入同步區域的程式碼。此函式宣告如下

?
1 function WaitForSingleObject(hHandle: Thandle; dwMilliseconds: DWORD): DWORD; stdcall;

  這個函式可以使當前執行緒在dwMilliseconds 指定的時間內睡眠,直到 hHandle引數指向的物件進入發訊號狀態為止。一個互斥物件不再被執行緒擁有時,它就進入發訊號狀態。當一個程序要終止時,它就進入發訊號狀態,而後立即返回。dwMilliSeconds引數設為 INFINITE,表示如果訊號不出現將一直等下去。這個函式的返回值列在下表

返回值 含義
WAIT_ABANDONED     指定的物件時互斥物件,並且擁有這個互斥物件的執行緒在沒有釋放此物件之前就已經終止。此時就稱互斥物件被拋棄。這種情況下,這個互斥物件歸當前執行緒所有,並把它設為非發訊號狀態
WAIT_OBJECT_0     指定的物件處於發訊號狀態
WAIT_TIMEOUT     等待的事件已過,物件仍然是非發訊號狀態

  再次宣告,當一個互斥物件不再被一個執行緒所擁有,它就處於發訊號狀態,此時首先呼叫WaitForSignalObject() 函式的執行緒就稱為該互斥物件的擁有者,此互斥物件設為不發訊號狀態。當執行緒呼叫ReleaseMutex() 函式並傳遞一個互斥物件的控制代碼作為引數時,這種擁有關係就被解除,互斥物件重新進入發訊號狀態

  注意 除WaitForSingleObject() 函式外,你還可以使用 WaitForMultipleObject() 和MsgWaitForMultipleObject() 函式,它們可以等待幾個物件變為發訊號狀態。這兩個函式的詳細情況請看Win32 API聯機文件

3.訊號量

  另外一種使執行緒同步的技術是使用訊號量物件。它是在互斥的基礎上建立的,但是訊號量增加了資源計數的功能,預定數目的執行緒允許同時進入要同步的程式碼。可以用 CreateSemaphore() 來建立一個訊號量物件,其宣告如下

?
1 function CreateSemaphore(lpSemaphoreAttributes: PSecurityAttributes; lInitialCount, lMaxiMumCount: LongInt; lpName: PChar): THandle; stdcall;

  和CreateMutex() 函式一樣,CreateSemaphore() 的第一個引數也是一個指向 TSecurityAttributes 記錄的指標,此引數的預設值可以設為 nil。

  lInitialCount 引數用來指定一個訊號量的初始計數值,這個值必須在 0 和 lMaximumCount 之間。此引數大於 0,就表示訊號量處於發訊號狀態。當呼叫 WaitForSingleObject() 函式(或其他函式)時,此計數值就減1。當呼叫 ReleaseSemaphore() 時,此計數值加1。

  引數 lMaximumCount 指定計數值的最大值。如果這個訊號量代表某種資源,那麼這個值代表可用資源總數

  引數 lpName 用於給出訊號量物件的名稱,它類似於 CreateMutex() 函式的 lpName 引數。

  下面是使用訊號量技術來同步初始化陣列的程式碼

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 unit Main; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TMainForm =class(TForm) Button1: TButton; ListBox1: TListBox; procedure Button1Click(Sender: TObject); private procedure ThreadsDone(Sender: TObject); end; TFooThread = class(TThread) protected procedure Execute; override; end; var MainForm: TMainForm; implementation {$R *.DFM} const MaxSize = 128; var NextNumber: Integer = 0; DoneFlags: Integer = 0; GlobalArray: array[1..MaxSize] of Integer; hSem: THandle = 0;        // function GetNextNumber: Integer; begin Result:= NextNumber;    //return global var Inc(NextNumber);    //inc global var end; procedure TFooThread.Execute; var i: Integer; WaitReturn: DWORD; begin OnTerminate:= MainForm.ThreadsDone; WaitReturn:= WaitForSingleObject(hSem, INFINITE);       // if WaitReturn = WAIT_OBJECT_0 then        // begin for i:=1 to MazSize do begin GlobalArray[i]:= GetNextNumber; Sleep(5); end; end

相關推薦

Delphi執行同步臨界互斥訊號

  當有多個執行緒的時候,經常需要去同步這些執行緒以訪問同一個資料或資源。   例如,假設有一個程式,其中一個執行緒用於把檔案讀到記憶體,而另一個執行緒用於統計檔案的字元數。當然,在整個檔案調入記憶體之前,統計它的計數是沒有意義的。但是,由於每個操作都有自己的執行緒,作業系統會把兩個執行緒當做是互不相干的任

執行同步臨界互斥

Win32 中關於程序和執行緒的協調工作是由同步機制來完成的,同步機制相當於執行緒間的紅綠燈。 一. 同步和非同步 舉個例子: PostMessage(),是把訊息放到對方的訊息佇列中,然後不管三七二十一,就回到原呼叫點繼續執行,這就是非同步。 SendMessage(),就像呼叫一

執行同步總結--臨界 事件 互斥 訊號

在WIN32中,同步機制主要有以下幾種: 臨界區(Critical section) 事件(Event); 互斥量(mutex); 訊號量(semaphore);   臨界區(Critical section) 臨界區(Cri

C/C++多執行執行同步互斥鎖與訊號

參考連結2.中寫的非常好,簡單易懂,上手快,非常好的博文。使用多執行緒及互斥鎖樣例:#include <iostream> #include <windows.h> using namespace std; HANDLE hMutex = NULL;

C++多執行例項之臨界同步

本篇是對上一篇 進行了重構,增加了windos下的臨界區鎖。 臨界區的特點:非核心物件,只能在window下使用,linux下不能使用;只能在同一程序內的執行緒間使用,速度快。 互斥量特點:互斥量是核心物件,可以用於程序內也可以在程序間互斥,速度相對互斥量慢點,也可以

C++11併發學習之四:執行同步

有時候,在第一個執行緒完成前,可能需要等待另一個執行緒執行完成。C++標準庫提供了一些工具可用於這種同步操作,形式上表現為條件變數(condition variable)和期望(future)。 一.條件變數(condition variable) C++標準庫對條件變數有兩套實現:std::c

基本執行同步使用Lock同步程式碼塊

宣告:本文是《 Java 7 Concurrency Cookbook 》的第二章,作者: Javier Fernández González     譯者:許巧輝 校對:方騰飛 使用Lock同步程式碼塊 Java提供另外的機制用來同步程式碼塊。它比synchronized關鍵字更加強大、靈活

基本執行同步使用讀/寫鎖同步資料訪問

宣告:本文是《 Java 7 Concurrency Cookbook 》的第二章,作者: Javier Fernández González     譯者:許巧輝 使用讀/寫鎖同步資料訪問 鎖所提供的最重要的改進之一就是ReadWriteLock介面和唯一 一個實現它的ReentrantRe

基本執行同步修改Lock的公平性

宣告:本文是《 Java 7 Concurrency Cookbook 》的第二章,作者: Javier Fernández González  譯者:許巧輝 校對:方騰飛 修改Lock的公平性 在ReentrantLock類和 ReentrantReadWriteLock類的構造器中,允許一

基本執行同步在Lock中使用多個條件

宣告:本文是《 Java 7 Concurrency Cookbook 》的第二章,作者: Javier Fernández González     譯者:許巧輝 校對:方騰飛 在Lock中使用多個條件 一個鎖可能伴隨著多個條件。這些條件宣告在Condition介面中。 這些條件的目的是允許

基本執行同步引言

宣告:本文是《 Java 7 Concurrency Cookbook 》的第二章,作者: Javier Fernández González     譯者:許巧輝 校對:方騰飛 引言 在這個章節中,我們將覆蓋: 同步方法 介紹 在併發程式設計中發生的最常見的一種情況是超過一個

基本執行同步同步程式碼中使用條件

宣告:本文是《 Java 7 Concurrency Cookbook 》的第二章,作者: Javier Fernández González     譯者:許巧輝 校對:方騰飛 在同步程式碼中使用條件 在併發程式設計中的一個經典問題是生產者與消費者問題,我們有一個數據緩衝區,一個或多個數據的

基本執行同步同步的類裡安排獨立屬性

宣告:本文是《 Java 7 Concurrency Cookbook 》的第二章,作者: Javier Fernández González     譯者:許巧輝  校對:方騰飛 在同步的類裡安排獨立屬性 當你使用synchronized關鍵字來保護程式碼塊時,你必須通過一個物件的引用作為引

執行同步條件變數

參考:https://blog.csdn.net/qq_37653144/article/details/81988615 https://blog.csdn.net/qq_34328833/article/details/56012780  https://blog.csdn.n

基本執行同步同步方法

宣告:本文是《 Java 7 Concurrency Cookbook 》的第二章,作者: Javier Fernández González     譯者:許巧輝 校對:方騰飛 同步方法 在這個指南中,我們將學習在Java中如何使用一個最基本的同步方法,即使用 synchronized關鍵字

四種執行同步互斥方式小結

一,什麼是執行緒同步和互斥 同步就是協同步調,按預定的先後次序進行執行。如:你說完,我再說。這裡的同步千萬不要理解成那個同時進行,應是指協同、協助、互相配合。執行緒同步是指多執行緒通過特定的設定(如互

java筆記--關於執行同步7種同步方式

關於執行緒同步(7種方式) 為何要使用同步?     java允許多執行緒併發控制,當多個執行緒同時操作一個可共享的資源變數時(如資料的增刪改查),      將會導致資料不準確,相互之間產生衝突,因此加入同步鎖以避免在該執行緒沒有完成操作之前,被其他執行緒的呼叫,      從而保證了該變數的唯一性

執行同步執行安全處理Synchronized與死鎖

一  執行緒同步 java中提供了執行緒同步機制,它能夠解決上述的執行緒安全問題。          執行緒同步的方式有兩種: 方式1:同步程式碼塊   方式2:同步方法 1 同步程式碼塊 同步程式

windows下多執行同步利用事件物件,互斥物件,關鍵程式碼段實現

一:利用事件實現執行緒同步 1.createthread函式的用法 hThread = CreateThread(&security_attributes, dwStackSize, ThreadProc,pParam, dwFlags, &idThre

執行同步:銀行帳戶存取款問題

1.銀行賬戶類 package com.bankAccount.test; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.ut