1. 程式人生 > >C++ 經典執行緒同步 事件Event示例解析(十)

C++ 經典執行緒同步 事件Event示例解析(十)

下面給個多執行緒案例使用event

#define _CRT_SECURE_NO_WARNINGS

#include <windows.h>
#include <process.h>
#include <stdio.h>

struct ThreadInfo
{
	ThreadInfo();
	~ThreadInfo();

	HANDLE	fire;
	HANDLE	stop;

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

ThreadInfo::ThreadInfo()
{
	fire = CreateEvent(0, FALSE, FALSE, NULL);
	stop = CreateEvent(0, TRUE, FALSE, NULL);
}

ThreadInfo::~ThreadInfo()
{
	CloseHandle(stop);
	CloseHandle(fire);
}

unsigned __stdcall threadfunc(void* param)
{
	if (ThreadInfo* info = reinterpret_cast<ThreadInfo*>(param))
	{
		char buf[32];

		for (;;)
		{
			HANDLE h[2] = { info->fire, info->stop };
			DWORD ret = WaitForMultipleObjects(2, h, FALSE, INFINITE);
			if (ret == WAIT_OBJECT_0)
			{
				_snprintf(buf, sizeof(buf), "Thread %ld fired\n", GetCurrentThreadId());
				printf(buf);
			}
			else if (ret == WAIT_OBJECT_0+1)
			{
				_snprintf(buf, sizeof(buf), "Thread %ld clean shutdown\n", GetCurrentThreadId());
				printf(buf);
				break;
			}
			else
			{
				_snprintf(buf, sizeof(buf), "Thread %ld abnormal shutdown\n", GetCurrentThreadId());
				printf(buf);
				break;
			}
		}
	}
	return 0;
}

int main()
{
	const size_t N = 5;
	ThreadInfo info[N];
	HANDLE h[N];

	for (size_t i = 0; i != N; ++i)
	{
		unsigned threadid;
		h[i] = (HANDLE)_beginthreadex(0, 0, threadfunc, &info[i], 0, &threadid);
	}
	Sleep(5*1000);

	for (size_t i = 0; i != N; ++i)
		SetEvent(info[i].fire);
	Sleep(5*1000);

	for (size_t i = 0; i != N; ++i)
		SetEvent(info[i].stop);
	Sleep(5*1000);

	system("pause");
	return 0;
}

執行效果:

 解析上篇部落格中的案例(用事件Event來嘗試解決這個執行緒同步問題)。

#include<iostream>
#include <process.h>
#include <windows.h>
using namespace  std;

long g_nNum;
unsigned int __stdcall Fun(PVOID pPM);
const int THREAD_NUM = 10;
//事件與關鍵段
HANDLE  g_hThreadEvent;
CRITICAL_SECTION g_csThreadCode;
int main()
{
	cout<<"      經典執行緒同步 事件Event\n"<<endl;
	//初始化事件和關鍵段 自動置位,初始無觸發的匿名事件
	g_hThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 
	InitializeCriticalSection(&g_csThreadCode);

	HANDLE  handle[THREAD_NUM];	
	g_nNum = 0;
	int i = 0;
	while (i < THREAD_NUM) 
	{
		handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
		WaitForSingleObject(g_hThreadEvent, INFINITE); //等待事件被觸發
		i++;
	}
	WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);

	//銷燬事件和關鍵段
	CloseHandle(g_hThreadEvent);
	DeleteCriticalSection(&g_csThreadCode);
	system("pause");
	return 0;
}
unsigned int __stdcall Fun(PVOID pPM)
{
	int nThreadNum = *(int *)pPM; 
	SetEvent(g_hThreadEvent); //觸發事件

	Sleep(50);//some work should to do

	EnterCriticalSection(&g_csThreadCode);
	g_nNum++;
	Sleep(0);//some work should to do
	cout<<"執行緒編號為:"<<nThreadNum<<"全域性資源為:"<<g_nNum<<endl;
	LeaveCriticalSection(&g_csThreadCode);
	return 0;
}

執行效果如下:

 可以看出來,經典線執行緒同步問題已經圓滿的解決了——執行緒編號的輸出沒有重複,說明主執行緒與子執行緒達到了同步。全域性資源的輸出是遞增的,說明各子執行緒已經互斥的訪問和輸出該全域性資源

 PulseEvent 是一個不可靠函式,很少使用,存在的目的是向後相容。

主要原因如下:

一個執行緒等待一個同步物件可以從等待狀態被暫時移除了核心模式的APC,然後返回到等待狀態後, APC是完整的。如果在當執行緒已經從等待狀態中刪除的時間內出現在呼叫PulseEvent ,該執行緒將不會被釋放,因為PulseEvent釋放只有那些等候在那一刻它被稱為執行緒。因此,PulseEvent是不可靠的

,不應該被使用的新的應用。相反,使用條件變數。

對於手動重置事件物件,可以立即釋放所有等待的執行緒被釋放。然後該函式的事件物件的狀態重置為無訊號和回報。

對於自動重置事件物件,該功能將釋放一個等待執行緒後的狀態為非終止和回報,即使多個執行緒都在等待。

如果沒有執行緒在等待,如果沒有執行緒可以立即釋放, PulseEvent簡單地設定事件物件的狀態設定為無訊號和回報。

請注意,對於使用多物件等待函式來等待所有指定的物件來通知執行緒, PulseEvent可以設定事件物件的狀態為signaled和重置而不會造成等待函式返回為非終止。發生這種情況,如果不是所有指定的物件被同時發出訊號。

使用SignalObjectAndWait和PulseEvent與Windows 7時要特別小心,因為使用這些API的多個執行緒之間可以導致應用程式死鎖。是由SignalObjectAndWait訊號的執行緒呼叫PulseEvent訊號的SignalObjectAndWait呼叫等待的物件。在某些情況下, SignalObjectAndWait的呼叫者無法接收的等待物件的訊號狀態的時間,從而導致死鎖

上面說了那麼多了,就來細細的體會這個函式吧、手動重置事件和自動重置事件的案例。

下面案例整理中待續!!!

手動重置事件(案例)

自動重置事件(案例)