1. 程式人生 > >一種Windows下執行緒同步的實現方法

一種Windows下執行緒同步的實現方法

一種Windows下執行緒同步的實現方法

Windows下的多執行緒與執行緒同步概述

多工是一個作業系統可以同時執行多個程式的能力。基本上,作業系統使用一個硬體時鐘為同時執行的每個程序分配“時間片”。如果時間片足夠小,並且機器也沒有由於太多的程式而超負荷,那麼在使用者看來,所有的這些程式似乎在同試執行著。

多執行緒是在一個程式內部實現多工的能力。程式可以把它自己分隔為單獨的執行“執行緒”,這些執行緒似乎也同時在執行[1][g1]多執行緒的應用非常廣泛,最常見的是在需要進行大量計算的程式中使用輔助執行緒完成計算工作,而使用者介面執行緒響應使用者的操作。

多執行緒中不同執行緒之間的通訊通常是使用共享資料物件來實現的,不管是使用全域性變數還是執行緒過程函式的指標引數進行通訊,都可能引發訪問衝突

[2][g2]而解決這一問題的方法即執行緒同步。

Windows提供了多種方法來實現執行緒之間的協調和同步,有臨界區、事件物件、互斥量等。這些方法都有各自的特點和適用的場合,下面就讓我們看一下經典之作Windows程式設計》一書中所介紹的使用臨界區進行執行緒同步的方法 [3]

首先需要定義一個全域性的臨界區物件,以便在不同的執行緒中能夠訪問。例如:CRITICAL_SECTION cs;

然後在某個執行緒中初始化這個臨界區物件:

InitializeCriticalSection(&cs);

這樣就建立了一個名為cs的臨界區物件。此時,執行緒可以通過下面的呼叫進入臨界區:

EnterCriticalSection(&cs);

在這時,執行緒被認為“擁有”臨界區物件。沒有兩個執行緒可以同時擁有臨界區物件,因此,如果一個執行緒進入了臨界區,那麼下一個使用同一臨界區物件呼叫EnterCriticalSection的執行緒將在函式呼叫中被掛起。只有當第一個執行緒通過下面的呼叫離開臨界區時,函式才會返回:

LeaveCriticalSection(&cs);

這時,在EnterCriticalSection的呼叫中被掛起的執行緒擁有臨界區,其函式呼叫也返回,允許執行緒繼續執行。

當臨界區不再需要時,可以呼叫:

DeleteCriticalSection(&cs);

將其刪除。[g3]

在進入臨界區後,執行緒可以獨佔方式訪問資源而不用擔心其他執行緒的干擾,當不同的執行緒共享不同的資料時,還可以通過使用多個臨界區來實現。

事件物件和互斥量等在使用上與臨界區有所不同,但流程和步驟相似,只不過需要呼叫WaitForSingleObject來替代EnterCriticalSection函式來阻塞執行緒,並等待其他執行緒在執行完畢後釋放資源。

可以看出,執行緒同步是一件比較複雜而又容易出錯的工作,既要保證各執行緒在訪問和更新資料中不會衝突,又要防止死鎖。在MFC中,為了降低執行緒同步的複雜性,減少工作量,提供了CCriticalSectionCEventCMutex等類封裝了Windows API中相關的執行緒同步函式,方便程式設計人員使用[4][g4]但是這些類的出現並沒有改變基本的執行緒同步程式設計的流程和步驟,在具體使用過程中仍需要小心謹慎的使用才能夠達到目的。

一種簡便的執行緒同步的實現方法

能否在Windows提供的執行緒同步的方法上進行改進,提供一種簡單方便的實現方法呢,這種方法應該不需要繁瑣的步驟,既能夠滿足我們的要求,同時又足夠的靈活。

首先讓我們重新審視一下Windows所提供的幾種執行緒同步機制。可以發現,在事件物件、互斥量和訊號量的使用中都可以使用一個字串作為執行緒同步物件的標識,當建立一個帶有名稱標識的執行緒同步物件時,若已存在同名物件則會返回一個已存在物件的控制代碼,並且可以通過呼叫GetLastError獲得ERROR_ALREADY_EXISTS值來檢驗是否返回了一個已存在物件的控制代碼值。

以互斥量舉例來說:

//建立第一個互斥量

HANDLE mutex1;

mutex1 = CreateMutex(NULL, TRUE, "mutex");

if(ERROR_ALREADY_EXISTS == GetLastError())//存在同名互斥量

{

printf("Mutex exist!/n");

}

else //未發現同名互斥量

{

printf("Create mutex!/n");

}

//建立第二個互斥量

HANDLE mutex2;

mutex2 = CreateMutex(NULL, TRUE, "mutex");

if(ERROR_ALREADY_EXISTS == GetLastError())//存在同名互斥量

{

printf("Mutex exist!/n");

}

else //未發現同名互斥量

{

printf("Create mutex!/n");

}

我們在VC++6.0中編譯並執行上述程式碼,得到的結果輸出為:

Create mutex!

Mutex exist!

從而可以看出,當我們使用“mutex”作為名稱多次建立互斥量時,通過檢查GetLastError的返回值可判斷出是否是第一次建立此名稱的互斥量。由此便可進一步發展出下面兩個函式:

HANDLE Lock(char* name)

{

HANDLE mutex;

// Try to open an exist mutex firstly.

mutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, name);

if(NULL == mutex)// If the mutex does not exist, create it with the certain name.

{

mutex = CreateMutex(NULL, TRUE, name);

}

else // If the mutex already exist, wait for other thread release it.

{

WaitForSingleObject(mutex, INFINITE);

}

return mutex;

}

bool Unlock(HANDLE mutex)

{

if(0 == ReleaseMutex(mutex))// Failed to release mutex

{

return false;

}

else// Successed in release mutex

{

CloseHandle(mutex);

mutex = NULL;

return true;

}

}

使用時僅需進行如下呼叫:

HANDLE mutex = Lock("MutexLockName");

……

Unlock(mutex);

在呼叫Lock時傳入互斥量的名稱。當不存在同名的互斥量時呼叫CreateMutex建立一個以name變數值為名稱的互斥量;若同名的互斥量已經存在時OpenMutex函式將返回已存在的互斥量的一個控制代碼,此時通過呼叫WaitForSingleObject阻塞當前執行緒,等待同名互斥量被釋放後繼續執行。

在呼叫Unlock時傳入互斥量的控制代碼,通過ReleaseMutex釋放執行緒對互斥量的擁有權,並關閉控制代碼,防止資源洩露。

有了上述兩個函式,我們便可通過對同一資源使用相同的名稱來進行鎖定,實現執行緒同步了。例如在不同的執行緒中存在著一個物件,定義如下:

CObject object;

當我們在訪問和更改這一物件時只需進行如下呼叫:

HANDLE mutex = Lock("object");

object.DoSomeThing();

Unlock(mutex);

就可以十分方便的完成針對這一物件的執行緒同步了。

通過LockUnlock函式,我們隱藏了使用互斥量時的一些具體細節,降低了複雜性,但是對於這種方法仍存在著許多不足,下面就一步一步循序漸進的來改進這些問題。

在使用過程中,必須為Lock函式傳入一個名稱作為引數,並且此名稱在鎖定同一資料物件時必須是一致的才能保證Lock函式的正常運作,這就為使用者帶來了不必要的麻煩。當存在如下宣告時:

CObject* pObject1 = new CObject;

CObject* pObject2 = pObject1;

CObject* pObject3 = pObject2;

可以看出,pObject1pObject2pObject3實際上指向同一物件,這時候如果要進行執行緒同步必須在鎖定這三個指標時使用相同的名稱標識。於是就出現了別名問題。

C++中,在不同的作用域中變數名稱是可以相同的,因此同樣一個pObject在不同的作用域中可能是指向不同的物件的,也就是同名問題。

針對別名和同名,如何能夠簡單的識別出同一物件並且為其統一命名呢?在C++ 中每個類都存在一個this指標用以指向自身儲存的地址。在一個程式中,每個不同的物件在記憶體中都有著不同的儲存地址。這個地址就好像身份證號碼一樣標識出了不同的物件,也正是我們所希望得到的針對不同物件的唯一的名稱。於是可以將Lock函式改造為:

HANDLE Lock(void* pointer)

{

char name[128];

itoa((DWORD)pointer, name, 16);

// 以下同前述

HANDLE mutex;

// Try to open an exist mutex firstly.

mutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, name);

……

}

這樣在呼叫時就不需要再人為的為每個物件命名了,只需要將物件的地址傳入即可:

HANDLE mutex = Lock(pObject);

……

Unlock(mutex);

從而也就免去了命名之苦。

最主要的問題已經通過努力一步步地解決了,但是在程式看起來卻依然很醜陋,LockUnlock必須作為全域性函式,有違面向物件的設計原則,而且每次需要成對的呼叫,難免會有粗心的程式設計師呼叫了Lock[g5]卻忘記Unlock,於是程式就有可能進入死鎖[MS6] 

為了能夠更方便的使用,首先建立一個類CMutexLock,將LockUnlock作為成員函式,再重新審視前面的程式,不難發現Unlock的引數HANDLE mutex也可以移入CMutexLock成為其成員變數:

class CMutexLock 

{

public:

CMutexLock();

virtual ~CMutexLock();

bool Lock(void* pointer);

bool Unlock();

private:

HANDLE m_Mutex;

};

這時,在使用過程中只需先宣告一個CMutexLock的物件,然後呼叫LockUnlock函式。

LockUnlock成為一個類的成員函式時,隨之而來的一個疑問就是:

“有必要暴露這兩個函式成為公共方法嗎?”既然LockUnlock必須成對的出現,那麼不是剛好對應於類的建構函式和解構函式嗎?為什麼不把它們一個放入建構函式,一個放入解構函式呢?當提出了這些疑問的同時也就找到了解決之道:

class CMutexLock 

{

public:

CMutexLock(void* pointer);

virtual ~CMutexLock();

private:

bool Lock(void* pointer);

bool Unlock();

HANDLE m_Mutex;

};

CMutexLock::CMutexLock(void* pointer)

:m_Mutex(NULL)

{

this->Lock(pointer);

}

CMutexLock::~CMutexLock()

{

this->Unlock();

}

……

CMutexLock的呼叫也簡化為了一句定義語句:

{

CMutexLock lock(pObject);

……

}

當宣告lock時,針對傳入建構函式的pObject呼叫Lock函式,當lock離開生存空間銷燬時在解構函式裡面呼叫Unlock釋放互斥量。在這裡通過使用{}來控制CMutexLock解構函式的呼叫,即釋放對pObject的鎖定。在C++{}可以用來控制變數的作用域。在{}內宣告的lock在遇到}時即會被銷燬,在銷燬前解構函式也將會被自動呼叫。

至此,我們就完成了一個使用起來簡單方便的執行緒同步輔助類。

小結

在本文中,以Windows多執行緒機制為基礎,利用C++的一些最基本的特性一步步的創建出了CMutexLock類,而執行緒同步也由最初繁瑣複雜的過程簡化至只需一對{}和一句定義語句就完成了。CMutexLock使用起來簡單方便,可以針對不同的資料物件分別進行鎖定,並且減少了死鎖的出現。在實際程式中運作良好,表明了這種方法的實用性。

但是在使用過程中,由於互斥量本身特性和本文中實現方式帶來的一些弊病在所難免,例如不同程序之間的名字衝突、效率問題、建構函式發生錯誤、別名問題等。限於篇幅和筆者水平,這些問題不在此文中一一詳述。如有興趣,可來信討論,郵件地址:[email protected]

參考文獻:

[1] P1119 Windows程式設計》(第5版) Charles Petzold 北京大學出版社[2] P263 Visual C++ 6.0 技術內幕》(第5版修訂版) David J.Kruglinski Scot Wingo George Shepherd 希望出版社

[3] P1147 Windows程式設計》(第5版) Charles Petzold 北京大學出版社

[4] MSDN

相關推薦

Windows執行同步實現方法

一種Windows下執行緒同步的實現方法 Windows下的多執行緒與執行緒同步概述 多工是一個作業系統可以同時執行多個程式的能力。基本上,作業系統使用一個硬體時鐘為同時執行的每個程序分配“時間片”。如果時間片足夠小,並且機器也沒有由於太多的程式而超負荷,那麼在使用者看來,所

java中的執行同步實現方法(將方法設定為synchronized)

一. 簡要說明:  對於java中的執行緒同步來說,可以用synchronized關鍵字來修飾,既可以對方法進行修飾,也可以對變數進行修飾,而二者都可以實現執行緒的同步。本篇說的是第一種方法,第二種方法在下一篇中說明。 二. 例子:     AccountRunnable.

使用QThread執行的新方法 moveToThread

工作中使用QT開發遇到使用多執行緒,對於執行緒的建立,研究後發現有些疑惑。各執一詞先上兩個連結:     QThread似乎是很難的一個東西,特別是訊號和槽,有非常多的人(儘管使用者本人往往不知道)在用不恰當(甚至錯誤)的方式在使用QThread,隨便用goog

Windows執行同步

核心物件首先介紹核心物件的概念。應用程式在執行過程中會建立各種資源,如程序,執行緒,檔案,互斥量等等。這些資源均由作業系統管理。作業系統管理這些資源的方式就是:記錄這些資源的相關資訊,會在其內部生成資料塊。每種資源需要維護的資訊不同,因此每種資源擁有的資料塊格式也不同。這類資

Linux執行同步的幾方法

Linux下提供了多種方式來處理執行緒同步,最常用的是互斥鎖、條件變數和訊號量。一、互斥鎖(mutex)   鎖機制是同一時刻只允許一個執行緒執行一個關鍵部分的程式碼。 1. 初始化鎖   int pthread_mutex_init(pthread_mutex_t *m

Linux執行同步的幾常見方法

Linux下提供了多種方式來處理執行緒同步,最常用的是互斥鎖、條件變數和訊號量。一、互斥鎖(mutex)  鎖機制是同一時刻只允許一個執行緒執行一個關鍵部分的程式碼。 1. 初始化鎖  int pthread_mutex_init(pthread_mutex_t *mutex

window執行同步之(Critical Sections(關鍵程式碼段、關鍵區域、臨界區域)----轉載

轉載:https://www.cnblogs.com/cyblogs/p/9948379.html    關鍵區域(CriticalSection) 臨界區是為了確保同一個程式碼片段在同一時間只能被一個執行緒訪問,與原子鎖不同的是臨界區是多條指令的鎖定,而原子

JAVA中執行同步方法(7)彙總

同步的方法: 一、同步方法   即有synchronized關鍵字修飾的方法。 由於java的每個物件都有一個內建鎖,當用此關鍵字修飾方法時, 內建鎖會保護整個方法。在呼叫該方法前,需要獲得內建鎖,否則就處於阻塞狀態。 注: synchronized關鍵字也可以修飾靜態

分散式多執行同步實現

簡介:多執行緒請求同一個資源,導致併發問題,在不使用第三方外掛的情況下,用程式碼實現同步,初步程式碼如下,如果有什麼建議和意見,請留言,大家一起學習! 原理:多個伺服器中,選一臺伺服器作為中介,然後在各個伺服器同時爭搶同一個資源時候,都跳轉到中介的伺服器裡,然後在中介伺服器

C++中四執行同步方法

現在流行的程序執行緒同步互斥的控制機制,其實是由最原始最基本的4種方法實現的。由這4種方法組合優化就有了.Net和Java下靈活多變的,程式設計簡便的執行緒程序控制手段。     這4種方法具體定義如下 在《作業系統教程》ISBN 7-5053-6193-7 一書中能夠找到

Java中建立執行的幾方式以及執行同步的幾方式

執行緒同步自己及基本就用過Thread和Runnable這兩種方式,還有其他很多方式如下: Executor框架簡介 建立執行緒有幾種不同的方式?你喜歡哪一種?為什麼? 而執行緒同步會用的方式就更少了,只會synchronized,其他方式如下: 關於執

windows執行同步技術

轉載自: 天極網,, 摘要: 多執行緒同步技術是計算機軟體開發的重要技術,本文對多執行緒的各種同步技術的原理和實現進行了初步探討。  關鍵詞: VC++6.0; 執行緒同步;臨界區;事件;互斥;訊號量;   閱讀目錄:   使執行緒同步   臨界區  管理事件核心物件  

Windows執行同步方法

Summary: 對於多執行緒程式設計,一個很重要的問題就是解決由資料共享引起的資料競爭的問題,通過一定的執行緒同步的方法能避免資料競爭。在Win32多執行緒中,同步方法包括使用者態同步方式:InterLock、CriticalSection、SRWLock和核心態同步方式:Event、Semaphore、

執行實現方法

Java提供了三種實現同步機制的方法: (1)synchronized 關鍵字 Java語言中,每個物件都有一個物件鎖與之關聯,該鎖表明物件在任何時候只允許被一個執行緒所擁有,當一個執行緒呼叫一段synchronized程式碼時,需要先獲取這個鎖,然後去執行相應的程式碼,

思考(四十五):通用郵件服務SDK的實現方法

SDK 製作思路 SDK 不干涉使用方使用什麼網路模組、協議 SDK 不干涉使用方伺服器組內部架構 使用方只需要關注 SDK 介面用法,不需要關注 SDK 內部郵件協議、格式 Client SDK

JAVA溫習:多執行同步方法

1  wait方法:         該方法屬於Object的方法,wait方法的作用是使得當前呼叫wait方法所在部分(程式碼塊)的執行緒停止執行,並釋放當前獲得的呼叫wait所在的程式碼塊的鎖,並在其他執行緒呼叫notify或者notifyAll方法時恢復到競爭鎖

Win32用於C++的執行同步類(多執行實現加鎖解鎖)

使用Win32提供的臨界區可以方便的實現執行緒鎖: // 全域性: CRITICAL_SECTION cs; InitializeCriticalSection( & cs); // 執行緒1: EnterCriticalSection(

【多執行實現執行同步的幾方法

前言 最近小扁我被問到 實現執行緒同步有哪幾種方法,而我只知道使用同步關鍵字synchronized來實現而已(⊙o⊙),,所以有必要來學習一下實現執行緒同步的幾種方法;各位看官,若有非議(不接受反駁),請不吝賜教! 實現執行緒同步的幾種方法 從我自己

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

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

執行同步之——兩個執行序列順序列印奇數和偶數的兩實現

題目:一道經典的執行緒併發的問題,執行緒a列印1、3、5……,執行緒b列印2、4、6……,兩個執行緒交替執行輸出1、2、3、4、5、6…… 要點: package com.test; import java.util.concurrent.locks.