VC++學習之多線程(2)

創建一個線程,自然有一個相應的系統API來完成。CreateThread這個函數就用來創建線程的。

各種參數的用途我就不多說了,這裏直接貼一個我自己練習的例子



1、下面是一個創建一個線程的例子,當然,僅僅是創建;

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

DWORD WINAPI Fun1Pro(LPVOID laparameter);
int main()
{
	HANDLE hthread1;
	hthread1 = CreateThread(NULL, 0, Fun1Pro, NULL, 0, NULL);
	CloseHandle(hthread1);
	cout << "main thread is running \n";
	return 0;
}

DWORD WINAPI Fun1Pro(LPVOID Laparameter)
{
	printf("The New Thread is running\n");
	return 0;
}
在C++中,對於對象的引用,也是通過引用計數的方式來處理的,所以上述代碼中的closehandle這個函數,只是在主線程中關閉了線程的一個句柄,但是實際上創建的這個新線程還是存在的,只不過這個新線程的內核對象中對這個線程的引用計數-1.否則如果不執行這樣的一個函數,即使這個線程執行完了,也會因爲主線程中仍然有引用這個線程的痕跡從而這個線程的引用計數不爲0,則不能徹底刪除,只有當整個進程計數的時候,纔會進行清理。


運行這個程序之後可以發現,並沒有看到新線程輸出的那句話。

原因是這樣的,進程啓動之後先執行了主線程,因爲操作系統是通過分配給線程時間片的方式來讓線程運行的,所以,在主線程的時間片內,沒有特殊情況是沒人打斷他的,一直在執行,到了創建新的子線程的時候,因爲主線程的時間片沒到,所以,不能夠執行子線程,之後馬上又關閉了子線程的句柄,主線程執行完了,就說明整個進程也就結束了,回收了所有的資源,子線程基本上是沒用的。如果要想看見子線程運行的話,那麼就需要讓主線程暫停執行,這時候,操作系統中的分派器就會在隊列裏面找到一個新的線程來執行。

讓一個線程停止運行的的辦法就是利用sleep()函數,使其能夠暫停一下。

可以在main函數中return 0;這條語句的前面加上sleep他就 會在主線程結束之前暫停,從而執行新的子線程。


那麼主線程和子線程他們直接的運行順序到底是什麼樣的呢?由於線程和進程的運行時間都是又操作系統的時間片來決定的,我對代碼做了如下修改:

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

DWORD WINAPI Fun1Pro(LPVOID laparameter);
int index = 0;
int main()
{
	index = 0;
	HANDLE hthread1;
	hthread1 = CreateThread(NULL, 0, Fun1Pro, NULL, 0, NULL);

	CloseHandle(hthread1);
	while(index++<100)
	cout << "main thread is running \n";
	//Sleep(10);
	return 0;
}

DWORD WINAPI Fun1Pro(LPVOID Laparameter)
{
	while(index++<100)
	printf("The New Thread is running\n");
	return 0;
}

運行結果如圖:




從圖中我們就可以印證上面所說的了,線程的運行時間是靠時間片來決定的,當主線程的時間片運行完,但是整個主線程還沒運行完的時候,操作系統的分派器也會將主線程加入線程的就緒隊列,從隊列中找出新的子進程來執行。



2、利用互斥對象實現同步:

互斥對象屬於內核對象,他能夠保證線程對單個資源擁有互斥的訪問權,也就是說,只能由一個線程在同一時間訪問該資源。一個互斥對象包括:使用數量,線程ID,計數器。ID表明現在是哪個線程擁有這個互斥對象,計數器用於指明該線程擁有互斥對象的次數。

需要調用CreateMutex函數來創建互斥對象。通過WaitForSingleObject函數來請求互斥對象,通過ReleaseMutex函數來釋放互斥對象。

可能看到這裏大家還是不能明白爲什麼互斥對象可以實現進程間的同步作用。

首先,我們要創建一個互斥對象,此時這個互斥對象處於一個有信號狀態,當我們用wait函數來請求一個互斥對象的時候, 那麼我們此時的這個線程是可以獲得互斥對象的訪問權,從而開始執行wait函數後面的共享代碼區,同時,當調用一次wait的時候,互斥對象就從有信號變爲無信號狀態,因爲互斥對象就是保證對單個資源的互斥訪問,如果此時還有其他的新的線程來請求互斥對象的訪問權從而執行共享代碼的話,那麼此時他得到的互斥對象是無信號狀態,是不能夠繼續執行的,此時的線程會處於等待狀態。當第一個線程執行完共享代碼區的時候,我們要釋放現在所使用的這個互斥對象,即用releasemutex來進行釋放,他會將互斥對象從無信號狀態設置爲有信號狀態,此時根據請求的先後順序,之前在wait函數那裏等待互斥對象信號的兩個線程中的一個,就會被允許獲得互斥的對象的訪問權,從而執行共享存儲區的代碼。


其實上面的互斥對象和操作系統中的PV操作是同理的。

共享代碼就是在wait函數和release函數中間的代碼,每次只能允許一個線程獲得互斥對象的訪問權從而進入到臨界區裏面。wait是請求操作,release是恢復操作。


互斥對象還有一個重要的機制,就是他包括的線程的ID.如果是線程1請求了一個互斥對象,那麼如果線程二想用這個互斥對象之前,必須由線程1進行釋放,在釋放的過程中,互斥對象會拿出他維護的線程ID和釋放它的線程進行比較,只有兩者相同的情況下才能夠釋放這個互斥對象,否則如果線程ID和釋放者不一樣 ,則不能夠釋放互斥對象。


CreateMutex函數中的第二個參數有兩個取值,一個是false,一個是true.false代表當前的線程不擁有這個互斥對象,互斥對象處於有信號狀態,true呢表示當前的線程擁有這個互斥對象,即當前的線程和該互斥對象綁定,互斥對象處於無信號狀態,相當於默認的執行了wait函數。如果此時在這個線程裏面再調用wait函數,雖然此時的互斥對象是無信號狀態,但是因爲調用他的線程和互斥對象內部維護的線程ID是相等的,所以依然會獲得該互斥對象的控制權。但是此時有一個非常重要的一點,就是這個互斥對象的計數器+1了。一開始就說過,互斥對象的計數器代表了擁有這個互斥對象的線程擁有它的次數,所以每當調用一次wait函數的時候,計數器都會+1.所以,如果說當前線程不需要這個互斥對象的時候,需要調用兩次釋放函數,計數器纔會減爲0,這也就告訴了我們,釋放函數的功能實際上就是讓計數器-1.


3、關於互斥對象還有一個很重要的功能:

當一個線程裏面利用wait函數請求互斥對象執行完畢之後,在線程中並沒有調用release函數來釋放這個對象,所以在線程結束之後操作系統如果發現線程已經終止的話,他會自動幫我們釋放掉這個互斥對象,把它變爲有信號狀態。




4、如何保證只有一個實例運行:

我們都知道程序只是代碼,一個程序運行起來可以有多個實例,那麼如果只讓這個程序只有一個實例在運行呢。解決辦法就是利用命名的互斥對象來實現。原因就是,如果在調用創建互斥對象的函數的時候,如果之前已經有該命名的互斥對象存在,那麼就返回已經創建的互斥對象,不再創建 新的,這時getlasterror將返回error_alreday_exists。

所以,如果createmutex返回的是一個有效句柄,接下來就要判斷getlasterror的返回值是什麼,從而斷定我們 是不是在先前已經創建過了一個互斥對象,也就相當於已經有一個實例正在運行。


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