一.建立線程最好使用C++運行庫中的_beginthreadex()函數建立進程,_beginthreadex使用CreateThread實現的,但針對C++語言作了一些處理。詳見《windows核心編程》第6章。
#pragma once#include <windows.h>#include <process.h>#include <tchar.h>#include <iostream>using namespace std;class ExampleTask{public:void startTask();unsigned static WINAPI taskMain(LPVOID param); //若線程調用函數為類成員則必須設定為static};unsigned WINAPI ExampleTask::taskMain(LPVOID param){cout<<"thread 1"<<endl;return 0;}void ExampleTask::startTask(){unsigned uiThreadId; //進程id_beginthreadex(NULL,//安全設定0,//stacksizetaskMain, //函數地址(PVOID) (INT_PTR) 1, //參數列表0,//0表示立即運行,CREATE_SUSPEND表示順延強制&uiThreadId);cout<<"start thread 1"<<endl;}int _tmain(int argc,TCHAR* argv[]){ExampleTask realTimeTask;realTimeTask.startTask();return 0;}
使用RemuseThread()來繼續執行進程:調用WaitForSingleObject()函數讓主線程等待子線程。
class Threadx{int loopstart;int loopend;int frequent;public:Threadx(int,int,int);unsigned static WINAPI ThreadStaticEntryPoint(LPVOID param);void ThreadEntry();string threadName;};Threadx::Threadx(int start,int lend,int freq):loopend(lend),loopstart(start),frequent(freq){}unsigned WINAPI Threadx::ThreadStaticEntryPoint(LPVOID param){Threadx* thread = reinterpret_cast<Threadx*>(param);thread->ThreadEntry();return 1;}void Threadx::ThreadEntry(){for (int i = loopstart; i < loopend; ++i) {if (i % frequent == 0) {cout<<threadName<<" "<<i<<endl;}}cout<<threadName<<" end"<<endl;}int _tmain(int args,TCHAR* argv[]){Threadx* th = new Threadx(0,20,2);HANDLE hth1;unsigned uiThreadId;hth1 = (HANDLE)_beginthreadex(NULL,//security0,//stack sizeThreadx::ThreadStaticEntryPoint,th, //CREATE_SUSPENDED, //延時建立,暫時不建立,0 for running or CREATE_SUSPENDED for suspended&uiThreadId);if (hth1 == 0) {cout<<"建立進程失敗!"<<endl;}th->threadName = "thread1";DWORD dwExitCode; GetExitCodeThread( hth1, &dwExitCode ); // should be STILL_ACTIVE = 0x00000103 = 259 printf( "initial thread 1 exit code = %u\n", dwExitCode ); ResumeThread(hth1);//使用resumeThread繼續運行threadWaitForSingleObject( hth1, INFINITE ); Threadx* th2 = new Threadx(1,25,3);th2->threadName = "thread 2";unsigned th2id;HANDLE hth2 = (HANDLE)_beginthreadex(NULL,0,Threadx::ThreadStaticEntryPoint,th2,CREATE_SUSPENDED,&th2id);GetExitCodeThread( hth2, &dwExitCode ); // should be STILL_ACTIVE = 0x00000103 = 259 printf( "initial thread 2 exit code = %u\n", dwExitCode ); ResumeThread(hth2);/*(1)C++主線程的終止,同時也會終止所有主線程建立的子線程,不管子線程有沒有執行完畢。所以上面的代碼中如果不調用WaitForSingleObject,則2個子線程t1和t2可能並沒有執行完畢或根本沒有執行。(2)如果某線程掛起,然後有調用WaitForSingleObject等待該線程,就會導致死結。所以上面的代碼如果不調用resumethread,則會死結。*/WaitForSingleObject( hth2, INFINITE );CloseHandle(hth1);CloseHandle(hth2);return 0;}
二.終止線程1.等待線程執行完自己return(推薦做法),然後系統會自動調用_endthreadex()函數釋放進程。2.在進程中調用_endthread(0)強行終止,可能存在資源沒被釋放就停止進程,導致記憶體泄露。
三.進程同步實現線程同步的方法:
使用者態:原子操作,臨界區
核心態:事件,訊號量,互斥量1.利用原子操作實現進程同步:
/*在下面的程式中,利用了全域變數ThreadData來進行線程間的同步,當子線程結束時改變該值,而父線程則迴圈判斷該值來確認子線程是否已經結束,當子線程結束時,父線程才繼續進行下面的操作。*/volatile bool ThreadAtom = true;//運行子線程unsigned WINAPI TreadProcess(LPVOID lParam){for (int i = 0; i < 10; ++i) {Sleep(1000);cout<<"子線程完成第 "<<i<<" 個任務"<<endl;if (i == 6) {//_endthreadex(0);//如果此處強行終止進程,threadatom就不會變成false主進程就會死迴圈}}cout<<"子線程結束"<<endl;ThreadAtom = false;return 1;}int _tmain(int argc,TCHAR* argv[]){unsigned threadid;HANDLE thread = (HANDLE)_beginthreadex(NULL,0,TreadProcess,0,0,&threadid);if (thread == 0) {cout<<"建立進程失敗!"<<endl;}while (ThreadAtom) {cout<<"主線程在等待子線程"<<endl;Sleep(1600);}//WaitForSingleObject(thread,INFINITE); //用waitforsingleobject也能達到同樣的目的cout<<"主線程結束"<<endl;CloseHandle(thread);system("pause");return 0;}以上只有1個子線程所以就直接修改ThreadAtom的值,若有多個子線程,則需用Interlocked系列函數來處理:InterLockExchange(&ThreadAtom,false);(詳見《windows 核心編程》第八章)。2.臨界區(Critical Section)保證在某一時刻只有一個線程能訪問資料的簡便辦法。在任意時刻只允許一個線程對共用資源進行訪問。如果有多個線程試圖同時訪問臨界區,那麼在有一個線程進入後其他所有試圖訪問此臨界區的線程將被掛起,並一直持續到進入臨界區的線程離開。臨界區在被釋放後,其他線程可以繼續搶佔,並以此達到用原子方式操作共用資源的目的。
//-----------------------------------------------//下面這個例子,進程ADD每次把g_num增加1,進程SUB每次把//g_num的值減少2。如果g_num小於0,則結束進程//-----------------------------------------------int g_num = 20;CRITICAL_SECTION g_cs;unsigned WINAPI ThreadAdd(LPVOID lpvoid){while (true) {if (TryEnterCriticalSection(&g_cs)) { //嘗試進入臨界區if (g_num < 0) {LeaveCriticalSection(&g_cs);break;}Sleep(1300);cout<<"進程ADD進入臨界區: g_num = "<<g_num<<endl;g_num += 1;cout<<"進程ADD離開臨界區: g_num = "<<g_num<<endl;LeaveCriticalSection(&g_cs);//出臨界區} else {Sleep(800);cout<<"臨界區被Sub進程佔用,下次再來吧"<<endl;}}cout<<"Add進程結束"<<endl;return 1;}unsigned WINAPI ThreadSubtract(LPVOID lpvoid){while (true) {TryEnterCriticalSection(&g_cs);//進入臨界區if (g_num < 0) {LeaveCriticalSection(&g_cs);break;}Sleep(600);cout<<"進程SUB進入臨界區: g_num = "<<g_num<<endl;g_num -= 2;cout<<"進程SUB離開臨界區: g_num = "<<g_num<<endl;LeaveCriticalSection(&g_cs);//出臨界區}cout<<"Sub進程結束"<<endl;return 1;}int _tmain(int argc,TCHAR* argv[]){HANDLEthAdd;HANDLEthSub;unsignedthAddId;unsignedthSubId;InitializeCriticalSection(&g_cs);//初始化臨界區變數thAdd = (HANDLE)_beginthreadex(NULL,0,ThreadAdd,0,0,&thAddId);thSub = (HANDLE)_beginthreadex(NULL,0,ThreadSubtract,0,0,&thSubId);if (thAdd == 0) {cout<<"建立Add進程失敗!"<<endl;}if (thSub == 0) {cout<<"建立Sub進程失敗!"<<endl;}WaitForSingleObject(thAdd,INFINITE);WaitForSingleObject(thSub,INFINITE);CloseHandle(thAdd);CloseHandle(thSub);DeleteCriticalSection(&g_cs); //釋放臨界區變數cout<<"主進程結束"<<endl;system("pause");return 0;}
3.事件(Event)
事件對象也可以通過通知操作的方式來保持線程的同步。並且可以實現不同進程中的線程同步操作。訊號量包含的幾個操作原語:
CreateEvent() // 建立一個訊號量 OpenEvent() // 開啟一個事件 SetEvent() // 回置事件 WaitForSingleObject() // 等待一個事件 WaitForMultipleObjects()// 等待多個事件WaitForMultipleObjects 函數原型: WaitForMultipleObjects( IN DWORD nCount, // 等待控制代碼數 IN CONST HANDLE *lpHandles, //指向控制代碼數組 IN BOOL bWaitAll, //是否完全等待標誌 IN DWORD dwMilliseconds //等待時間 )
下面來看一個例子:
#pragma once#include <windows.h>#include <tchar.h>#include <process.h>#include <iostream>#include <string>#include <algorithm>using namespace std;//-----------------------------------------------//2個線程協作反轉字串//-----------------------------------------------////////////////////////////////////////////////////////////////////////////下面例子中通過一個伺服器線程和用戶端線程來處理字串://剛開始伺服器處理等待狀態,當用戶端有請求的時候會先把請求發到一個共用記憶體緩衝中,//並觸發一個事件,這伺服器線程就會去查看緩衝並處理用戶端請求。當伺服器處理請求的//時候,用戶端處於等待狀態。/////////////////////////////////////////////////////////////////////////HANDLE g_clientEvent; //用戶端有事件給伺服器HANDLE g_serverEvent; //伺服器有資料給用戶端string buffer; //共用記憶體unsigned WINAPI serverThread(LPVOID lparam){WaitForSingleObject(g_clientEvent,INFINITE); // INFINITE: 長時間等待,差不多50天cout<<"伺服器接受到原始字串:"<<buffer<<endl;reverse(buffer.begin(),buffer.end());cout<<"伺服器處理字串:"<<buffer<<endl;SetEvent(g_serverEvent);return 0;}unsigned WINAPI clientThread(LPVOID lparam){string newbuffer(reinterpret_cast<char*>(lparam));buffer = newbuffer;cout<<"用戶端發送字串:"<<buffer<<endl;SetEvent(g_clientEvent);WaitForSingleObject(g_serverEvent,INFINITE);cout<<"用戶端接受到字串:"<<buffer<<endl;return 0;}int _tmain(int argc,TCHAR* argv[]){string buf = "reverse the string!"; g_clientEvent = CreateEvent(NULL,//SECURITY_ATTRIBUTES結構指標,可為NULLFALSE,//TRUE: 在WaitForSingleObject後必須手動調用ResetEvent清除訊號//FALSE:在WaitForSingleObject後,系統自動清除事件訊號 FALSE,//初始狀態NULL);//事件的名稱g_serverEvent = CreateEvent(NULL,FALSE,FALSE,NULL);unsigned thid;HANDLE serverTh = (HANDLE)_beginthreadex(NULL,0,serverThread,NULL,0,&thid);HANDLE clientTh = (HANDLE)_beginthreadex(NULL,0,clientThread,(void*)buf.data(),0,&thid);Sleep(5000);//讓兩個進程有時間執行完CloseHandle(clientTh);CloseHandle(serverTh);CloseHandle(g_clientEvent);CloseHandle(g_serverEvent);system("pause");return 0;}
4.訊號量(semaphore)//-----------------------------------------------
//作業系統題目:爸爸往盤子中放蘋果或者橘子,然後通知兒子來//拿蘋果,女兒來拿橘子。同時只能有一個人用盤子,盤子中最多//只放一個水果//-----------------------------------------------#define P(S) WaitForSingleObject(S,INFINITE) //等待某資源#define V(S) ReleaseSemaphore(S,1,NULL) //釋放一個資源HANDLE g_plate; //盤子HANDLE g_apple; //蘋果HANDLE g_orange; //橘子//兒子吃蘋果unsigned WINAPI EatApple(LPVOID lprama){while (true) {P(g_apple);P(g_plate);Sleep(100);cout<<"兒子吃掉了盤中的蘋果"<<endl;V(g_plate);}return 0;}//女兒吃橘子unsigned WINAPI EatOrange(LPVOID lprama){while (true) {P(g_orange);P(g_plate);Sleep(100);cout<<"女兒吃掉了盤中的橘子"<<endl;V(g_plate);}return 0;}/*// HANDLE CreateSemaphore ( // PSECURITY_ATTRIBUTE psa, // LONG lInitialCount, //開始時可供使用的資源數 // LONG lMaximumCount, //最大資源數 // PCTSTR pszName); *///主進程:爸爸往盤子中放水果int _tmain(int argc,TCHAR* argv[]){g_plate = CreateSemaphore(NULL,1,1,NULL);g_apple = CreateSemaphore(NULL,0,1,NULL);g_orange = CreateSemaphore(NULL,0,1,NULL);unsigned thid;HANDLE son = (HANDLE)_beginthreadex(NULL,0,EatApple,NULL,0,&thid);HANDLE daughter = (HANDLE)_beginthreadex(NULL,0,EatOrange,NULL,0,&thid);int input = 0; //輸入0退出cout<<"開始運行:0退出,1放蘋果,2放橘子"<<endl;while (true){cin>>input;if (input == 0) {break;} else if (input == 1) {cout<<"爸爸往盤子裡放了一個蘋果"<<endl;P(g_plate);V(g_apple);V(g_plate);} else if (input == 2) {cout<<"爸爸往盤子裡放了一個橘子"<<endl;P(g_plate);V(g_orange);V(g_plate);}Sleep(150);cout<<"請輸入0,1,2,繼續放水果"<<endl;}CloseHandle(son);CloseHandle(daughter);CloseHandle(g_plate);CloseHandle(g_orange);CloseHandle(g_apple);return 0;}
5.互斥量(Mutex)
兩個線程同時寫一個檔案:
//-----------------------------------------------//利用mutex實現進程同步:兩個進程同時寫檔案//-----------------------------------------------HANDLE g_mutext;int g_wid = 0;void writeFile(char* vlist){ofstream file("log.txt",ofstream::app);if (!file) {cerr<<"開啟檔案失敗!"<<endl;return;}file<<vlist<<endl;file.close();}unsigned WINAPI Writer1(LPVOID lparam){while (true){WaitForSingleObject(g_mutext,INFINITE);Sleep(1000);char str[200];sprintf(str,"Writer1 write:%d",g_wid++);cout<<str<<endl;writeFile(str);ReleaseMutex(g_mutext);}return 0;}unsigned WINAPI Writer2(LPVOID lparam){while (true){WaitForSingleObject(g_mutext,INFINITE);Sleep(1000);char str[200];sprintf(str,"Writer2 write:%d",g_wid++);cout<<str<<endl;writeFile(str);ReleaseMutex(g_mutext);}return 0;}int _tmain(int args,TCHAR* argv[]){g_mutext = CreateMutex(NULL,FALSE,NULL);//第二個參數:建立者線程是否有mutex所有權unsigned thid;HANDLE thw1 = (HANDLE)_beginthreadex(NULL,0,Writer1,NULL,0,&thid);HANDLE thw2 = (HANDLE)_beginthreadex(NULL,0,Writer2,NULL,0,&thid);Sleep(10000); //執行時間CloseHandle(thw1);CloseHandle(thw2);CloseHandle(g_mutext);return 0;}
參考
http://blog.csdn.net/ccing/article/details/6215998
下次再討論區對話存貯和多線程日誌庫。