1. 程式人生 > >用C++封裝Win32訊號量,同步執行緒

用C++封裝Win32訊號量,同步執行緒

     在Win32環境下編寫多執行緒應用程式,也會常用到訊號量Semaphore來進行執行緒同步。與其相關的一組API包括:CreateSemaphore,ReleaseSemaphore,WaitForSingleObject,和CloseHandle。關於這些API的功能以及引數意義等這裡就不多說了。下邊,我封裝了一個訊號量類,以及測試程式碼。已由本人在VS2005環境下編譯,測試通過。

MySemaphore.h

#ifndef Semaphore_Header
#define Semaphore_Header

#include <iostream>
#include <Windows.h>
#include <assert.h>

using namespace std;

//------------------------------------------------------------------------

class CSemaphoreImpl
{
protected:
	CSemaphoreImpl(int n, int max);		
	~CSemaphoreImpl();
	void SetImpl();
	void WaitImpl();
	bool WaitImpl(long lMilliseconds);

private:
	HANDLE m_hSema;
};

inline void CSemaphoreImpl::SetImpl()
{
	if (!ReleaseSemaphore(m_hSema, 1, NULL))
	{
		cout<<"cannot signal semaphore"<<endl;
	}
}

//------------------------------------------------------------------------

/*

 訊號量同步機制
 訊號量提供一個計數值,可以進行原子操作。V 將計數值加1,使得
 等待該訊號量的執行緒可以被呼叫(呼叫Set()),P 將計數值減1,使
 當前執行緒被掛起,進行睡眠(呼叫Wait())。
 當訊號量的計數值被初始化為0時,呼叫P操作,將掛起當前執行緒。
 當訊號量被啟用,即呼叫V操作後,被掛起的執行緒就有機會被重新排程了。

*/

class CMySemaphore: private CSemaphoreImpl
{
public:

	/*
	 建立一個訊號量,訊號量計數值當前值為引數n,最大值為max。
	 如果只有n,則n必須大於0;如果同時有n和max,則n必須不小
	 於0,且不大於max
	*/
	CMySemaphore(int n);
	CMySemaphore(int n, int max);
	
	/*
	 銷燬一個訊號量
	*/
	~CMySemaphore();

	/*
	 對訊號量計數值做加1動作,訊號量變為有訊號狀態,使得
	 另一個等待該訊號量的執行緒可以被排程
	*/
	void Set();

	/*
	 對訊號量計數值做減1動作,訊號量變為無訊號狀態。若
	 計數值變得大於0時,訊號量才會變為有訊號狀態。
	*/
	void Wait();

	/*
	 在給定的時間間隔裡等待訊號量變為有訊號狀態,若成功,
	 則將計數值減1,否則將發生超時。
	*/
	void Wait(long lMilliseconds);

	/*
	 在給定的時間間隔裡等待訊號量變為有訊號狀態,若成功,
	 則將計數值減1,返回true;否則返回false。
	*/
	bool TryWait(long lMilliseconds);

private:
	CMySemaphore();
	CMySemaphore(const CMySemaphore&);
	CMySemaphore& operator = (const CMySemaphore&);
};

inline void CMySemaphore::Set()
{
	SetImpl();
}


inline void CMySemaphore::Wait()
{
	WaitImpl();
}


inline void CMySemaphore::Wait(long lMilliseconds)
{
	if (!WaitImpl(lMilliseconds))
		cout<<"time out"<<endl;
}

inline bool CMySemaphore::TryWait(long lMilliseconds)
{
	return WaitImpl(lMilliseconds);
}

#endif


MySemaphore.cpp

#include "MySemaphore.h"

CSemaphoreImpl::CSemaphoreImpl(int n, int max)
{
	assert (n >= 0 && max > 0 && n <= max);

	m_hSema = CreateSemaphore(NULL, n, max, NULL);
	if (!m_hSema)
	{
		cout<<"cannot create semaphore"<<endl;
	}
}

CSemaphoreImpl::~CSemaphoreImpl()
{
	CloseHandle(m_hSema);
}

void CSemaphoreImpl::WaitImpl()
{
	switch (WaitForSingleObject(m_hSema, INFINITE))
	{
	case WAIT_OBJECT_0:
		return;
	default:
		cout<<"wait for semaphore failed"<<endl;
	}
}

bool CSemaphoreImpl::WaitImpl(long lMilliseconds)
{
	switch (WaitForSingleObject(m_hSema, lMilliseconds + 1))
	{
	case WAIT_TIMEOUT:
		return false;
	case WAIT_OBJECT_0:
		return true;
	default:
		cout<<"wait for semaphore failed"<<endl;		
	}
	return false;
}

CMySemaphore::CMySemaphore(int n): CSemaphoreImpl(n, n)
{
}

CMySemaphore::CMySemaphore(int n, int max): CSemaphoreImpl(n, max)
{
}


CMySemaphore::~CMySemaphore()
{
}


    下邊是測試程式碼

// MySem.cpp : 定義控制檯應用程式的入口點。
//

#include "MySemaphore.h"
#include <process.h>

//建立一個訊號量,其計數值當前值為0,最大值為3
CMySemaphore g_MySem(0, 3);

//執行緒函式
unsigned int __stdcall StartThread(void *pParam)
{
	//休眠100毫秒,確保主執行緒函式main中
	//建立工作執行緒下一句g_MySem.Set();先執行
	Sleep(100);

	g_MySem.Wait(); //訊號量計數值減1

	cout<<"Do print StartThread"<<endl;

	return (unsigned int)0;
}

int main(int argc, char* argv[])
{
	HANDLE hThread;
	unsigned int uiThreadId;

	assert ( !g_MySem.TryWait(10) );

	g_MySem.Set(); //訊號量計數值加1

	g_MySem.Wait(); //訊號量計數值減1

	try
	{
		g_MySem.Wait(100);
		cout<<"must timeout"<<endl; //此處發生超時
	}
	catch (...)
	{
		cout<<"wrong exception"<<endl;
	}

	g_MySem.Set();
	g_MySem.Set();
	assert ( g_MySem.TryWait(0) );
	g_MySem.Wait();
	assert ( !g_MySem.TryWait(10) );

	//建立工作執行緒
	hThread = (HANDLE)_beginthreadex(NULL, 0, &StartThread, NULL, 0, &uiThreadId);

	g_MySem.Set();

	//等待執行緒結束
	DWORD dwRet = WaitForSingleObject(hThread,INFINITE);
	if ( dwRet == WAIT_TIMEOUT )
	{
		TerminateThread(hThread,0);
	}

	assert ( !g_MySem.TryWait(10) ); //若將斷言中的 ! 去掉,則會發生斷言錯誤

	//關閉執行緒控制代碼,釋放資源
	CloseHandle(hThread);

	system("pause");
	return 0;
}


    編譯,執行

    可見,在不同的執行緒,這裡是主執行緒函式main和工作執行緒函式StartThread都可以對訊號量物件g_MySem做 P操作。