VC++中多線程學習(MFC多線程)三(線程同步包含:原子互鎖、關鍵代碼段、互斥器Mutex、Semaphores(信號量)、Event Objects(事件))

目錄

​​​​​​​​​​​​

線程同步的必要性:

2.解決同步問題的方法

2.1原子互鎖家族函數

2.2Critical Sections(關鍵代碼段、關鍵區域、臨界區域)

2.3 互斥器Mutex

2.4 Semaphores(信號量)

2.5 Event Objects(事件)


線程同步的必要性:

我們知道操作系統的執行最小單位是線程,而一個進程包含了很多的線程,現在已經實現了

真正的並行,如雙核cpu,在每個核心裏開一個進程,則雙核cpu就可以開兩個並行運行的進程

而在每個進程類又可以開很多的線程,這裏需要強調的是在兩個核跑的兩個進程是實實在在的並行的, 不會互相干擾,但是在每個核的進程中又運行了很多的線程,而這些線程並不是並行的,而是串行的,即操作系統會在線程中不停的切換執行,在切換執行很快時,給我們的感覺像是並行,但是實際並不是並行,這裏大家需要注意,因此在同一個進程中的線程執行是串行的,這個大家需要理解。

在一個進程中,我們定義一個全局變量,此時有很多的線程都在調用或者修改這個全局變量,那麼會不會存在這樣的一個情況,就是其中一個進程剛修改了這個全局變量,還沒來得及使用就被切換到其他線程去了,而被切換的這個線程剛好也要修改或者使用這個全局變量,那個此時會不會出現問題呢?但是肯定的,會導致無法預估的錯誤,這也就是線程在操作這個變量時需要遵守一定的規則的重要性的原因,而線程同步就是解決這樣類似問題的方法,因此線程同步很重要。

1.出現情況的例子

//定義一個全局變量
int g_Num = 0;
UINT __cdecl ThreadProc(LPVOID pParam)
{
	//線程函數的目的是先進行累加在進行自減,程序執行完應該是g_Num = 0,對吧
	//但是運行情況會是什麼情況呢?
	for (int idx = 0;idx<100;++idx)
	{
		g_Num = g_Num + 1;
		CString strNum;
		Sleep(5);
		strNum.Format(_T("%d"), g_Num);
		g_Num = g_Num - 1;
	}
	return 0;
}


void CThreadSynDlg::OnBnClickedThreadsynBut()
{
	//連續開闢了50個線程,每個線程的線程函數都是ThreadProc
	for (int idx = 1; idx<=50;++idx)
	{
		AfxBeginThread(ThreadProc, NULL);
	}
	
}


void CThreadSynDlg::OnBnClickedResultBut()
{
	int realNum = g_Num;
	//經過調試會發現,得到的值都不是0,其原因就是線程在執行過程中可能就會被暫停
	//導致結果會不一樣

}

 

2.解決同步問題的方法

2.1原子互鎖家族函數

簡單來說,這種保護方法是基於,一旦一個線程去修改這個全局變量就不   允許其他線程再去修改這個全局變量。主要有一下函數:

   ① InterlockedIncrement 加1操作

   ② InterlockedDecrement 減1操作

   ③ InterlockedExchangeAdd加上“指定”的值,可以加上一個負數;

   ④ InterlockedExchange、InterlockedExchangePointer能夠以原子操作的方式用第二個參數的值來取代第一個參數的值;

還有很多可以參數微軟的文檔及MSDN。一般情況下,在多線程中如果對於某一個變量的值進行改變的話使用以上的互鎖函數比較方便,但是很多時候應用場合很複雜,比如對一個結構體進行操作,對類進行操作,對鏈表進行插入等等,上面的方法就無法滿足了,需要引入更高級的方法及:Critical Sections(關鍵代碼段、關鍵區域、臨界區域)

 

//定義一個全局變量
int g_Num = 0;
UINT __cdecl ThreadProc(LPVOID pParam)
{
	//線程函數的目的是先進行累加在進行自減,程序執行完應該是g_Num = 0,對吧
	//但是運行情況會是什麼情況呢?
	for (int idx = 0;idx<100;++idx)
	{
		//g_Num = g_Num + 1;
		InterlockedIncrement((LONG*)&g_Num);
		CString strNum;
		Sleep(5);
		strNum.Format(_T("%d"), g_Num);
		//g_Num = g_Num - 1;
		InterlockedDecrement((LONG*)&g_Num);
	}
	return 0;
}


void CThreadSynDlg::OnBnClickedThreadsynBut()
{
	//連續開闢了50個線程,每個線程的線程函數都是ThreadProc
	for (int idx = 1; idx<=50;++idx)
	{
		AfxBeginThread(ThreadProc, NULL);
	}
	
}


void CThreadSynDlg::OnBnClickedResultBut()
{
	int realNum = g_Num;
	//經過調試會發現,得到的值都是0,其原因就是線程在執行過程中使用了原子鎖進行操作,保證了安全性

}

2.2Critical Sections(關鍵代碼段、關鍵區域、臨界區域)

//定義一個全局對象
CStringArray g_ArrString;
UINT __cdecl ThreadProc(LPVOID pParam)
{
	//每個線程函數的目的是在自身的idx的基礎上進行100次循環累加,每次
	//循環都會把數字轉換成字符串存儲在動態數組對象中g_ArrString,
	//因爲每個線程都會向數組中添加100個字符串,總共50個線程,如果程序正常執行那麼字符串數組的元素個數應該爲5000
	//但是運行結果會是什麼情況呢?
	int startIdx = (int)pParam;
	for (int idx = startIdx;idx< startIdx+100;++idx)
	{
		CString str;
		Sleep(1);
		str.Format(_T("%d"), idx);
		g_ArrString.Add(str);
	}
	return 0;
}


void CThreadSynDlg::OnBnClickedThreadsynBut()
{
	//連續開闢了50個線程,每個線程的線程函數都是ThreadProc
	//傳入的參數爲每個線程idx乘上10
	for (int idx = 1; idx<=50;++idx)
	{
		AfxBeginThread(ThreadProc, (LPVOID)(idx*10));
	}
	//結果是雖然編譯通過了,但是在執行過程中出錯了,出現了異常
	//主要原因是多個線程同時操作動態數組,導致數組操作異常,因此需要
	//線程同步才能解決,同時使用原子方法是無法解決了,因此需要引入新的
	//解決方法即Critical Sections(關鍵代碼段、關鍵區域、臨界區域)
	
}

Critical Sections(關鍵代碼段、關鍵區域、臨界區域)使用方法

建立一個Critical Sections對象

1.初始化: InitializeCriticalSection()

2.刪除:DeleteCriticalSection()

3.進入:EnterCriticalSection() (可能造成阻塞,原因是一旦有一個線程使用了這個,其他線程在使用,只能等待之前使用

                                               這個的離開 也就是接到5的信號,那麼才能開始執行,中間會出現卡頓)

4.嘗試進入:TryEnterCriticalSection() (不會造成阻塞)

5.離開:LeaveCriticalSection();

//詳細的請參考微軟MSDN的文檔,搜索同步數據結構

//定義一個全局對象
CStringArray g_ArrString;
//定義一個Critical Sections的對象
CRITICAL_SECTION g_CS;
UINT __cdecl ThreadProc(LPVOID pParam)
{
	//每個線程函數的目的是在自身的idx的基礎上進行100次循環累加,每次
	//循環都會把數字轉換成字符串存儲在動態數組對象中g_ArrString,
	//因爲每個線程都會向數組中添加100個字符串,總共50個線程,如果程序正常執行那麼字符串數組的元素個數應該爲5000
	//但是運行結果會是什麼情況呢?
	int startIdx = (int)pParam;
	for (int idx = startIdx;idx< startIdx+100;++idx)
	{
		CString str;
		//Sleep(1);
		str.Format(_T("%d"), idx);
		//因爲修改數組的是g_ArrString.Add(str);,所以在他前面加入即可,後面離開
		EnterCriticalSection(&g_CS);
		g_ArrString.Add(str);
		LeaveCriticalSection(&g_CS);
	}
	return 0;
}


void CThreadSynDlg::OnBnClickedThreadsynBut()
{
	//連續開闢了50個線程,每個線程的線程函數都是ThreadProc
	//傳入的參數爲每個線程idx乘上10
	//初始化關鍵代碼段,同時記得刪除
	InitializeCriticalSection(&g_CS);
	for (int idx = 1; idx<=50;++idx)
	{
		
		AfxBeginThread(ThreadProc, (LPVOID)(idx*10));
	}
	//結果是雖然編譯通過了,但是在執行過程中出錯了,出現了異常
	//主要原因是多個線程同時操作動態數組,導致數組操作異常,因此需要
	//線程同步才能解決,同時使用原子方法是無法解決了,因此需要引入新的
	//解決方法即Critical Sections(關鍵代碼段、關鍵區域、臨界區域)
	
}

//InitializeCriticalSection()
//DeleteCriticalSection()
//EnterCriticalSection()
//TryEnterCriticalSection()
//LeaveCriticalSection();
void CThreadSynDlg::OnBnClickedResultBut()
{
	//這裏的主要目的是爲了顯示計數的個數
	CString strCount;
	INT_PTR nCount = g_ArrString.GetCount();
	strCount.Format(_T("%d"), nCount);
	MessageBox(strCount);

	for (INT_PTR idx=0;idx<nCount;++idx)
	{
		OutputDebugString(g_ArrString.GetAt(idx));
	}

	//這裏進行刪除代碼段
	DeleteCriticalSection(&g_CS);
}

 結果是編譯通過,執行正確,結果是5000

 

通過Critical Sections技術,可以避免多線程間的衝突問題,程序可以順利的執行下去

下面就總結一下,該方法進行線程同步的特點

 

固有特點:

1.是一個用戶模式的對象,不是系統的核心對象;

2.因爲不是核心對象,所以執行速度快、有效率

3.因爲不是核心對象,所以不能跨進程使用

4.可以多次“進入”,但必須多次“退出”

5.最好不要同時進入或等到多個Critical Sections,容易造成死鎖;

    什麼是死鎖呢?簡單來說加入開啓兩個Critical Sections,第一線程進入第一個Critical Sections1,第二個線程進入

   Critical Sections2,但是同時呢,進入的Critical Sections1的線程又要打算進入Critical Sections2,而進入Critical Sections2的線程需要 進入

    Critical    Sections1,結果這兩個線程都在等,這樣進入死循環了,各自出不來,導致死鎖,只有線程死了才能解脫所以叫死鎖。

    所以避免死鎖的最好辦法就是進來少建立Critical Sections對象,這樣就避免死鎖了。

6.無法檢測到進入到Critical Sections裏面的線程當前是否已經退出,所以在Critical Sections儘量不要執行耗時的操作,,,

那麼還有沒有更好的方法進行線程間同步呢?當然有下面就介紹互斥器Mutex

 

2.3 互斥器Mutex

互斥器和2.2的關鍵代碼段方法不同,他是系統的核心對象,所以速度上要比關鍵代碼段方法慢點,當然他也有它的優點,使用方法方面和關鍵代碼段方法差不多,我們來看看:

 使用方法:

 1.創建一個互斥器:CreateMutex;

 2.打開一個已經存在的互斥器:OpenMutex;

 3.獲得互斥器的擁有權:WaitForSingleObject(),WaitForMultipleObjects(),,,等一類等待的函數,但是可能造成阻塞;

 4.釋放互斥器擁有權:ReleaseMutex;

 5.關閉互斥器:CloseHandle;

 

具體例子如下:

 

創建是在    void CThreadSynDlg::OnBnClickedThreadsynBut()函數中

銷燬是在    void CThreadSynDlg::OnBnClickedResultBut()函數中

 

處理是在線程函數中。

//定義一個全局對象
CStringArray g_ArrString;
//定義一個互斥器Mutex的句柄
HANDLE ghMutex = NULL;
UINT __cdecl ThreadProc(LPVOID pParam)
{
	//每個線程函數的目的是在自身的idx的基礎上進行100次循環累加,每次
	//循環都會把數字轉換成字符串存儲在動態數組對象中g_ArrString,
	//因爲每個線程都會向數組中添加100個字符串,總共50個線程,如果程序正常執行那麼字符串數組的元素個數應該爲5000
	//但是運行結果會是什麼情況呢?
	int startIdx = (int)pParam;
	for (int idx = startIdx;idx< startIdx+100;++idx)
	{
		CString str;
		//Sleep(1);
		str.Format(_T("%d"), idx);
		
		//定義一個單一的等待函數,第一次參數是互斥器的對象,第二個是等待時間,這裏是一直等待
		//因爲現在的互斥器是激活態,這線程一旦調用立刻返回,此時互斥器進入非激活態,如果這時候
		//有其他線程也是用這個WaitForSingleObject函數不會立刻返回,只能等待互斥器的在此激活
		DWORD dwWaitResult = WaitForSingleObject(ghMutex, INFINITE);
		switch (dwWaitResult)
		{
		case WAIT_ABANDONED://這個其實就是沒用獲得互斥器的激活態,或者其他的線程正在使用
		case WAIT_OBJECT_0://如果返回的是該值,則說明當前的互斥器已經被當前線程擁有了,可以進場操作了
			g_ArrString.Add(str);
			ReleaseMutex(ghMutex);//如果處理完,就釋放一下,使互斥器激活態,以便其他線程可以使用
			break;
		
		}

		
		
	}
	return 0;
}


void CThreadSynDlg::OnBnClickedThreadsynBut()
{
	//連續開闢了50個線程,每個線程的線程函數都是ThreadProc
	//傳入的參數爲每個線程idx乘上10
	//1.創建一個互斥器:CreateMutex;同時記得釋放句柄
	//一旦創建成功,則處於激活態,也就是現在沒有任何一個線程在調用它,可以誰是等待線程的調用
	//一旦線程線程調用則該互斥器立刻處於非激活態,其他線程在調用只有等待該互斥器的在此激活
	ghMutex = CreateMutex(NULL, FALSE, NULL);
	for (int idx = 1; idx<=50;++idx)
	{
		
		AfxBeginThread(ThreadProc, (LPVOID)(idx*10));
	}
	
}

//InitializeCriticalSection()
//DeleteCriticalSection()
//EnterCriticalSection()
//TryEnterCriticalSection()
//LeaveCriticalSection();
void CThreadSynDlg::OnBnClickedResultBut()
{
	//這裏的主要目的是爲了顯示計數的個數
	CString strCount;
	INT_PTR nCount = g_ArrString.GetCount();
	strCount.Format(_T("%d"), nCount);
	MessageBox(strCount);

	for (INT_PTR idx=0;idx<nCount;++idx)
	{
		OutputDebugString(g_ArrString.GetAt(idx));
	}

	//關閉互斥器
	CloseHandle(ghMutex);
}

總結:

命名標準:Mutex可以跨進程使用,所以其名稱對整個系統而言是全局的,所以命名不能過於普通,類似Mutex、object等

最後獨一無二且易識別

 

互斥器的特點:

1.是一個系統核心對象,所以有安全描述指針,用完了要CloseHandle關閉句柄,這些是內核對象的共同特徵;

2.因爲是核心對象,所以執行速度會比Critical Sections慢幾乎100倍的時間

3.因爲是核心對象,而且可以命名,所以可以跨進程使用;

4.Mutex使用正確的情況下不會發生死鎖;

5.在“等待”一個Mutex的時候,可以指定“結束等待”的時間長度;

6.可以檢測到當前擁有互斥器所有權的線程是否已經退出!wait。。。。函數會返回:WAIT_ABANDONED

 

2.4 Semaphores(信號量)

和前幾個的處理方法不同,我們從前面的方法中可以看到,其實本質上都同一個時間只有一個線程處理一個對象,

而信號量和他們的區別在與信號量的是同一個時間多個線程同時處理多個對象,這裏大家需要理解,前面的都是

一對一,而信號量是多對多。

使用方法:

 1.創建一個信號量:CreateSemaphore();

 2.打開一個已經存在的信號量:OpenSemaphore();

 3.獲得信號量的一個佔有權:WaitForSingleObject()、 WaitForMultipleObjects()等一類等待函數。。。。。可能造成阻塞

 4.釋放信號量的佔有權:ReleaseSemaphore()

 5.關閉信號量:CloseHandle()

HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,

    LONG lInitialCount,

    LONG lMaximumCount,

    LPCTSTR lpName );

  • lpSemaphoreAttributes
    [輸入]設置爲NULL。
  • lInitialCount
    [in]指定信號量對象的初始計數。此值必須大於或等於零且小於或等於lMaximumCount。當信號量的計數大於零時,將發出信號狀態;在信號量爲零時,將不發出信號狀態。每當等待函數釋放等待信號量的線程時,計數就會減少一。通過調用ReleaseSemaphore函數將計數增加指定的數量。
  • lMaximumCount
    [in]指定信號量對象的最大計數。該值必須大於零。
  • lpName
    [in]指向以空值結尾的字符串的長指針,該字符串指定信號量對象的名稱。名稱限制爲MAX_PATH字符,並且可以包含除反斜槓路徑分隔符(\)之外的任何字符。名稱比較區分大小寫。
    如果lpName與現有命名信號對象的名稱匹配,則lInitialCountlMaximumCount參數將被忽略,因爲它們已在創建過程中設置。
    每種對象類型(例如內存映射,信號量,事件,消息隊列,互斥體和看門狗計時器)都有其自己單獨的名稱空間。空字符串(“”)被視爲命名對象。在基於Windows桌面的平臺上,同步對象都共享相同的名稱空間。

 

來自 <https://docs.microsoft.com/en-us/previous-versions/aa911525(v=msdn.10)>

//定義一個全局對象
CStringArray g_ArrString;
//定義一個信號量的全局句柄
HANDLE ghSemaphore = NULL;
UINT __cdecl ThreadProc(LPVOID pParam)
{
	//每個線程函數的目的是在自身的idx的基礎上進行100次循環累加,每次
	//循環都會把數字轉換成字符串存儲在動態數組對象中g_ArrString,
	//因爲每個線程都會向數組中添加100個字符串,總共50個線程,如果程序正常執行那麼字符串數組的元素個數應該爲5000
	//但是運行結果會是什麼情況呢?
	int startIdx = (int)pParam;
	CString strOut;
	while (TRUE)
	{		
		
		//因爲現在的互斥器是激活態,這線程一旦調用立刻返回,此時互斥器進入非激活態,如果這時候
		//有其他線程也是用這個WaitForSingleObject函數會立刻返回,因爲這是多個線程可以同時進行操作
		//這裏不同的是不需要等待,因爲信號量可以多個線程同時操作他
		DWORD dwWaitResult = WaitForSingleObject(ghSemaphore, 0);
		switch (dwWaitResult)
		{
		case WAIT_OBJECT_0://如果返回的是該值,則說明當前信號量已經被當前線程擁有了,可以進場操作了
			strOut.Format(_T("Thred %d: wait succeeded!"), GetCurrentThreadId());
			OutputDebugString(strOut);
			/*
			可以加入其它要乾的活
			*/
			ReleaseSemaphore(ghSemaphore,1,NULL);//如果處理完,就釋放一下
			break;
		case WAIT_TIMEOUT:
			strOut.Format(_T("Thread %d: wait timed out!"), GetCurrentThreadId());
			OutputDebugString(strOut);
			break;
		
		}
		
		
	}
	return 0;
}
//CreateSemaphore
//OpenSemaphore
//WaitForSingleObject() WaitForMultipleObjects()
//ReleaseSemaphore()


void CThreadSynDlg::OnBnClickedThreadsynBut()
{
	//連續開闢了50個線程,每個線程的線程函數都是ThreadProc
	//傳入的參數爲每個線程idx乘上10
	//1.創建信號量ghSemaphore = CreateSemaphore(NULL, 10, 10, NULL);
	//其中在創建的過程中我們需要告訴他多少個線程訪問多少個對象
	//第三個參數是有多少個對象可以執行
	//第二個參數是初始化時有多少個對象可以執行,一般要小於等於第三個參數的值且大於等於0
	ghSemaphore = CreateSemaphore(NULL, 10, 10, NULL);
	for (int idx = 1; idx<=20;++idx)
	{
		AfxBeginThread(ThreadProc, (LPVOID)(idx*10));
	}
	
}

//InitializeCriticalSection()
//DeleteCriticalSection()
//EnterCriticalSection()
//TryEnterCriticalSection()
//LeaveCriticalSection();
void CThreadSynDlg::OnBnClickedResultBut()
{
	//這裏的主要目的是爲了顯示計數的個數
	CString strCount;
	INT_PTR nCount = g_ArrString.GetCount();
	strCount.Format(_T("%d"), nCount);
	MessageBox(strCount);

	for (INT_PTR idx=0;idx<nCount;++idx)
	{
		OutputDebugString(g_ArrString.GetAt(idx));
	}

	//關閉信號量
	CloseHandle(ghSemaphore);
}

總結:

命名標準:Semaphore可以跨進程使用,所以其名稱對整個系統而言是全局的,所以命名不能過於普通,類似Mutex、object等

最後獨一無二且易識別

 

互斥器的特點:

1.是一個系統核心對象,所以有安全描述指針,用完了要CloseHandle關閉句柄,這些是內核對象的共同特徵;

2.因爲是核心對象,所以執行速度會比Critical Sections慢幾乎100倍的時間(相對而言,現在的cpu很快了,幾乎沒差別了)

3.因爲是核心對象,而且可以命名,所以可以跨進程使用;

4.Semaphore使用正確的情況下不會發生死鎖;

5.在“等待”一個Semaphore的時候,可以指定“結束等待”的時間長度;

6.非排他性的佔有,跟Critical Sections和Mutex不同,這兩種而言是排他性的佔有,即同一時間內只能有單一的線程獲得目標並擁有操作的權利,而Semaphores則不是這樣的,同一時間可以有多個線程獲得目標並進行操作

 

如果上面程序使用信號量方式去做向CStringArray中添加節點的同步可以嗎:

答案是可以的,此時只需要修改:

 CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,

    LONG lInitialCount,

    LONG lMaximumCount,

    LPCTSTR lpName );

的第三個參數爲1即可,也就是說每次只允許一個線程對象對其操作,這樣設置就和前面的互斥鎖沒什麼兩樣了。

 

2.5 Event Objects(事件)

Event 方式是最具有彈性的同步機制,因爲他的狀態完全由你決定,不會像Mutex和Semaphores的狀態一樣會根據

waitforsingleObject----等類似的調用而改變,所以你需要精確的告訴Event對象該做什麼事,以及什麼時候去做.

 

 

使用方法:

    1.創建一個事件對象:CreateEvent;

    2.打開一個已經存在的事件對象:OpenEvent

    3.獲得事件的佔有權:waitforsingleObject等函數,可能會阻塞

    4.釋放事件的佔有權(設置爲激發狀態,以讓其他等待的線程甦醒:setEvent

    5.手動設置爲非激發態,ResetEvent

    6.關閉事件對象的句柄:closehandle

 

特點:

1.是一個系統核心對象,所以有安全描述指針,用完了要CloseHandle關閉句柄,這些是內核對象的共同特徵;

2.因爲是核心對象,所以執行速度會比Critical Sections慢幾乎100倍的時間(相對而言,現在的cpu很快了,幾乎沒差別了)

3.因爲是核心對象,而且可以命名,所以可以跨進程使用;

4.通常被用於overlapped I/O或者被用來設計某些自定義的同步對象。

 

 

https://docs.microsoft.com/zh-cn/windows/win32/sync/using-event-objects

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

#define THREADCOUNT 4 

HANDLE ghWriteEvent; 
HANDLE ghThreads[THREADCOUNT];

DWORD WINAPI ThreadProc(LPVOID);

void CreateEventsAndThreads(void) 
{
    int i; 
    DWORD dwThreadID; 

    // Create a manual-reset event object. The write thread sets this
    // object to the signaled state when it finishes writing to a 
    // shared buffer. 

    ghWriteEvent = CreateEvent( 
        NULL,               // default security attributes
        TRUE,               // manual-reset event
        FALSE,              // initial state is nonsignaled
        TEXT("WriteEvent")  // object name
        ); 

    if (ghWriteEvent == NULL) 
    { 
        printf("CreateEvent failed (%d)\n", GetLastError());
        return;
    }

    // Create multiple threads to read from the buffer.

    for(i = 0; i < THREADCOUNT; i++) 
    {
        // TODO: More complex scenarios may require use of a parameter
        //   to the thread procedure, such as an event per thread to  
        //   be used for synchronization.
        ghThreads[i] = CreateThread(
            NULL,              // default security
            0,                 // default stack size
            ThreadProc,        // name of the thread function
            NULL,              // no thread parameters
            0,                 // default startup flags
            &dwThreadID); 

        if (ghThreads[i] == NULL) 
        {
            printf("CreateThread failed (%d)\n", GetLastError());
            return;
        }
    }
}

void WriteToBuffer(VOID) 
{
    // TODO: Write to the shared buffer.
    
    printf("Main thread writing to the shared buffer...\n");

    // Set ghWriteEvent to signaled

    if (! SetEvent(ghWriteEvent) ) 
    {
        printf("SetEvent failed (%d)\n", GetLastError());
        return;
    }
}

void CloseEvents()
{
    // Close all event handles (currently, only one global handle).
    
    CloseHandle(ghWriteEvent);
}

int main( void )
{
    DWORD dwWaitResult;

    // TODO: Create the shared buffer

    // Create events and THREADCOUNT threads to read from the buffer

    CreateEventsAndThreads();

    // At this point, the reader threads have started and are most
    // likely waiting for the global event to be signaled. However, 
    // it is safe to write to the buffer because the event is a 
    // manual-reset event.
    
    WriteToBuffer();

    printf("Main thread waiting for threads to exit...\n");

    // The handle for each thread is signaled when the thread is
    // terminated.
    dwWaitResult = WaitForMultipleObjects(
        THREADCOUNT,   // number of handles in array
        ghThreads,     // array of thread handles
        TRUE,          // wait until all are signaled
        INFINITE);

    switch (dwWaitResult) 
    {
        // All thread objects were signaled
        case WAIT_OBJECT_0: 
            printf("All threads ended, cleaning up for application exit...\n");
            break;

        // An error occurred
        default: 
            printf("WaitForMultipleObjects failed (%d)\n", GetLastError());
            return 1;
    } 
            
    // Close the events to clean up

    CloseEvents();

    return 0;
}

DWORD WINAPI ThreadProc(LPVOID lpParam) 
{
    // lpParam not used in this example.
    UNREFERENCED_PARAMETER(lpParam);

    DWORD dwWaitResult;

    printf("Thread %d waiting for write event...\n", GetCurrentThreadId());
    
    dwWaitResult = WaitForSingleObject( 
        ghWriteEvent, // event handle
        INFINITE);    // indefinite wait

    switch (dwWaitResult) 
    {
        // Event object was signaled
        case WAIT_OBJECT_0: 
            //
            // TODO: Read from the shared buffer
            //
            printf("Thread %d reading from buffer\n", 
                   GetCurrentThreadId());
            break; 

        // An error occurred
        default: 
            printf("Wait error (%d)\n", GetLastError()); 
            return 0; 
    }

    // Now that we are done reading the buffer, we could use another
    // event to signal that this thread is no longer reading. This
    // example simply uses the thread handle for synchronization (the
    // handle is signaled when the thread terminates.)

    printf("Thread %d exiting\n", GetCurrentThreadId());
    return 1;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章