程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> C++ 多線程實現

C++ 多線程實現

編輯:關於C語言
 

//這是2個線程模擬賣火車票的小程序
#include <windows.h>
#include <iostream.h>

DWORD WINAPI Fun1Proc(LPVOID lpParameter);//thread data
DWORD WINAPI Fun2Proc(LPVOID lpParameter);//thread data

int index=0;
int tickets=10;
HANDLE hMutex;
void main()
{
HANDLE hThread1;
HANDLE hThread2;
//創建線程

hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);

//創建互斥對象
hMutex=CreateMutex(NULL,TRUE,"tickets");
if (hMutex)
{
if (ERROR_ALREADY_EXISTS==GetLastError())
{
cout<<"only one instance can run!"<<endl;
return;
}
}
WaitForSingleObject(hMutex,INFINITE);
ReleaseMutex(hMutex);
ReleaseMutex(hMutex);

Sleep(4000);
}
//線程1的入口函數
DWORD WINAPI Fun1Proc(LPVOID lpParameter)//thread data
{
while (true)
{
ReleaseMutex(hMutex);
WaitForSingleObject(hMutex,INFINITE);
if (tickets>0)
{
Sleep(1);
cout<<"thread1 sell ticket :"<<tickets--<<endl;
}
else
break;
ReleaseMutex(hMutex);
}

return 0;
}
//線程2的入口函數
DWORD WINAPI Fun2Proc(LPVOID lpParameter)//thread data
{
while (true)
{
ReleaseMutex(hMutex);
WaitForSingleObject(hMutex,INFINITE);
if (tickets>0)
{
Sleep(1);
cout<<"thread2 sell ticket :"<<tickets--<<endl;
}
else
break;
ReleaseMutex(hMutex);
}

return 0;
}調用C r e a t e P r o c e s s函數時如何創建進程的主線程。如果想要創建一個或多個輔助函數,只需要讓一個已經在運行的線程來調用C r e a t e T h r e a d: HANDLE CreateThread( PSECURITY_ATTRIBUTES psa, DWORD cbStack, PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam, DWORD fdwCreate, PDWORD pdwThreadID); 當C r e a t e T h r e a d被調用時,系統創建一個線程內核對象。該線程內核對象不是線程本身,而是操作系統用來管理線程的較小的數據結構。可以將線程內核對象視為由關於線程的統計信息組成的一個小型數據結構。這與進程和進程內核對象之間的關系是相同的。 系統從進程的地址空間中分配內存,供線程的堆棧使用。新線程運行的進程環境與創建線程的環境相同。因此,新線程可以訪問進程的內核對象的所有句柄、進程中的所有內存和在這個相同的進程中的所有其他線程的堆棧。這使得單個進程中的多個線程確實能夠非常容易地互相通信。 注意C r e a t e T h r e a d函數是用來創建線程的Wi n d o w s函數。不過,如果你正在編寫C / C + +代碼,決不應該調用C r e a t e T h r e a d。相反,應該使用Visual C++運行期庫函數_ b e g i n t h r e a d e x。如果不使用M i c r o s o f t的Visual C++編譯器,你的編譯器供應商有它自己的C r e a t e T h r e d替代函數。不管這個替代函數是什麼,你都必須使用。本章後面將要介紹_ b e g i n t h r e a d e x能夠做什麼,它的重要性何在。 這就是Create Thread函數的概述,下面各節將要具體介紹C r e a t e T h r e a d的每個參數。 6.4.1 psa p s a參數是指向S E C U R I T Y _ AT T R I B U T E S結構的指針。如果想要該線程內核對象的默認安全屬性,可以(並且通常能夠)傳遞N U L L。如果希望所有的子進程能夠繼承該線程對象的句柄,必須設定一個S E C U R I T Y _ AT T R I B U T E S結構,它的b I n h e r i t H a n d l e成員被初始化為T R U E。詳細信息參見第3章。 6.4.2 cbStack c b S t a c k參數用於設定線程可以將多少地址空間用於它自己的堆棧。每個線程擁有它自己的堆棧。當C r e a t e P r o c e s s啟動一個進程時,它就在內部調用C r e a t e T h r e a d來對進程的主線程進行初始化。對於c b S t a c k參數來說,C r e a t e P r o c e s s使用存放在可執行文件中的一個值。可以使用鏈接程序的/ S TA C K開關來控制這個值: /STACK:[reserve] [,commit] r e s e r v e參數用於設定系統應該為線程堆棧保留的地址空間量。默認值是1 MB。C o m m i t參數用於設定開始時應該承諾用於堆棧保留區的物理存儲器的容量。默認值是1頁。當線程中的代碼執行時,可能需要多個頁面的存儲器。當線程溢出它的堆棧時,就生成一個異常條件(關於線程堆棧和堆棧溢出的異常條件的詳細說明,參見第1 6章,關於一般異常條件的處理的詳細說明,參見第2 3章)。系統抓取該異常條件,並且將另一頁(或者你為c o m m i t參數設定的任何值)用於保留空間,這使得線程的堆棧能夠根據需要動態地擴大。 當調用C r e a t e T h r e a d時,如果傳遞的值不是0,就能使該函數將所有的存儲器保留並分配給線程的堆棧。由於所有的存儲器預先作了分配,因此可以確保線程擁有指定容量的可用堆棧存儲器。保留空間的容量既可以是/ S TA C K鏈接程序設定的容量,也可以是C b S t a c k的值,誰大就用誰。分配的存儲器容量應該與傳遞的c b S t a c k值相一致。如果將0傳遞給C b S t a c k參數,C r e a t e T h r e a d就保留一個區域,並且將鏈接程序嵌入. e x e文件的/ S TA C K鏈接程序開關信息指明的存儲器容量分配給線程堆棧。 保留空間的容量用於為堆棧設置一個上限,這樣就可以抓住代碼中的循環遞歸錯誤。例如,你編寫一個遞歸自調用函數,該函數也包含導致循環遞歸的一個錯誤。每次函數調用自己的時候,堆棧上就創建一個新的堆棧框。如果系統不設定堆棧的最大值,該遞歸函數就永遠不會停止對自己的調用。進程的所有地址空間將被分配,大量的物理存儲器將被分配給該堆棧。通過設置一個堆棧限制值,就可以防止應用程序用完大量的物理存儲器,同時,也可以更快地知道何時程序中出現了錯誤(第1 6章中的S u m m a t i o n示例應用程序顯示了如何跟蹤和處理應用程序中的堆棧溢出)。 6.4.3 pfnStartAddr和pvParam p f n S t a r t A d d r參數用於指明想要新線程執行的線程函數的地址。線程函數的p v P a r a m參數與原先傳遞給C r e a t e T h r e a d的p v P a r a m參數是相同的。C r e a t e T h r e a d使用該參數不做別的事情,只是在線程啟動執行時將該參數傳遞給線程函數。該參數提供了一個將初始化值傳遞給線程函數的手段。該初始化數據既可以是數字值,也可以是指向包含其他信息的一個數據結構的指針。 創建多個線程,使這些線程擁有與起始點相同的函數地址,這是完全合乎邏輯的並且是非常有用的。例如,可以實現一個We b服務器,以便創建一個新線程來處理每個客戶機的請求。每個線程都知道它正在處理哪個客戶機的請求,因為當創建線程時,你傳遞了一個不同的p z P a r a m值。 記住,Wi n d o w s是個搶占式多線程系統,這意味著新線程和調用C r e a t e T h r e a d的線程可以同時執行。由於線程可以同時運行,就會出現一些問題。請看下面的代碼: DWORD WINAPI FirstThread(PVOID pvParam) { //Initialize a stack-based variable int x = 0; DWORD dwThreadID; //Create a new thread. HANDLE hThread = CreateThread(NULL, 0, SecondThread, (PVOID)&x, 0, &dwThreadId); //We don't reference the new thread anymore, //so close our handle to it. closeHandle(hThread); //Our thread is done. //BUG:our stack will be destroyed, //but SecondThread might try to access it. return(0); } DWORD WINAPI SecondThread(PVOID pvParam) { //Do some lengthy processing here. ... //Attempt to access the variable on FirstThread's stack. //NOTE:This may cause an access violation - it depends on timing! *((int *) pvParam) = 5; ... return(0); } 在上面這個代碼中,F i r s t T h r e a d可以在S e c o n d T h r e a d將5分配給F i r s t T h r e a d的x之前結束它的操作。如果出現這種情況,S e c o n d T h r e a d將不知道F i r s t T h r e a d已經不再存在,並且仍然試圖修改現在已經是個無效地址的內容。這會導致S e c o n d T h r e a d產生一次訪問違規,因為F i r s t T h r e a d的堆棧已經在F i r s t T h r e a d終止運行時被撤消。解決這個問題的方法之一是將x聲明為一個靜態變量,這樣,編譯器就為應用程序的數據部分中的x創建一個存儲區,而不是在堆棧上創建存儲區。 但是這使得函數成為不可重新進入的函數。換句話說,無法創建兩個執行相同函數的線程,因為兩個線程將共享該靜態變量。解決這個問題(和它的更復雜的變形)的另一種方法是使用正確的線程同步技術(第8、9章和1 0章介紹) 6.4.4 fdwCreate f d w C r e a t e參數可以設定用於控制創建線程的其他標志。它可以是兩個值中的一個。如果該值是0,那麼線程創建後可以立即進行調度。如果該值是C R E AT E _ S U S P E N D E D,系統可以完整地創建線程並對它進行初始化,但是要暫停該線程的運行,這樣它就無法進行調度。 C R E AT E _ S U S P E N D E D標志使得應用程序能夠在它有機會執行任何代碼之前修改線程的某些屬性。由於這種必要性很少,因此該標志並不常用。第5章介紹的J o b L a b應用程序說明了該標志的正確方法。 6.4.5 pdwThreadID C r e a t e T h r e a d的最後一個參數是p d w T h r e a d I D,它必須是D W O R D的一個有效地址,C r e a t e T h r e a d使用這個地址來存放系統分配給新線程的I D (進程和線程的I D已經在第4章中作了介紹)。 注意在Windows 2000(和Windows NT 4)下,可以(並且通常是這樣做的)為該參數傳遞N U L L。它告訴函數,你對線程的I D不感興趣,但是線程已經創建了。在Windows 95和Windows 98下,為該參數傳遞N U L L會導致函數運行失敗,因為函數試圖將I D寫入地址N U L L(這是不合法的)。因此線程不能創建。 當然,操作系統之間的不一致現象會給編程人員帶來一些問題。例如,在Wi n d o w s2 0 0 0下(即使為p d w T h r e a d I D參數傳遞了N U L L,它也創建了該線程)編寫和測試了一個應用程序,當後來在Windows 98上運行該應用程序時,C r e a t e T h r e a d將不創建新的線程。必須始終在你聲稱支持的所有操作系統(和所有版本)上充分測試應用程序。

 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved