VC多線程編程基礎

    今天重新看了孫鑫MFC的多線程那一課,感覺學了JAVA再看這一課收穫頗多。

    當初實訓時候看了很久只是知道怎麼抄代碼......

 

  • 爲什麼要使用多線程?

    顧名思義,多線程就是就是可以讓程序同時跑多段代碼。但如果你的CPU是單核的,那對不起,學過硬件基礎可以知道,這就是資源相關,一個CPU不能同時跑多段代碼。所以代碼只能輪流執行,但給人感覺是同時執行的。

 

    在多核情況下,多線程一般比單線程快。當然也有例外,比如用N個線程做文件IO的事情,由於IO一直負荷工作,再者線程的切換也需要代價,所以用N個線程做同一件事情不見得比單線程更優秀。在CSDN論壇裏,我見過一個很形象的比喻:廁所只有一個,幾個人併發搶廁所,最後也只有一個人能在裏邊,其他人都要乖乖在外邊等。

 

    所以,爲了效率,多線程要用在做不同的事情上。比如用一個線程做IO,一個線程做數據的計算,一個線程做通信...多線程要用在複雜的事情上才能顯示出它的優勢。

 

  • 線程的創建

    HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
                 DWORD dwStackSize,
                 LPTHREAD_START_ROUTINE lpStartAddress,
                 LPVOID lpParameter,
                 DWORD dwCreationFlags,
                 LPDWORD lpThreadId);
該函數在其調用進程的進程空間裏創建一個新的線程,並返回已建線程的句柄,其中各參數說明如下:

  • lpThreadAttributes:指向一個 SECURITY_ATTRIBUTES 結構的指針,該結構決定了線程的安全屬性,一般置爲 NULL;
  • dwStackSize:指定了線程的堆棧深度,一般都設置爲0;
  • lpStartAddress:表示新線程開始執行時代碼所在函數的地址,即線程的起始地址。一般情況爲(LPTHREAD_START_ROUTINE)ThreadFunc,ThreadFunc 是線程函數名;
  • lpParameter:指定了線程執行時傳送給線程的32位參數,即線程函數的參數;
  • dwCreationFlags:控制線程創建的附加標誌,可以取兩種值。如果該參數爲0,線程在被創建後就會立即開始執行;如果該參數爲CREATE_SUSPENDED,則系統產生線程後,該線程處於掛起狀態,並不馬上執行,直至函數ResumeThread被調用;
  • lpThreadId:該參數返回所創建線程的ID;

如果創建成功則返回線程的句柄,否則返回NULL。

 

    這裏說下LPVOID lpParameter這個參數。如果想傳入多個參數,要定義一個全局的結構體指針。賦值後把指針傳入,在線程函數裏在把LPVOID強制轉化成該結構體指針即可。

 

    VC這裏的多線程跟JAVA不同。在JAVA中,主線程結束了,其他線程還能繼續執行。但是在VC中,main函數退出了,其他線程就不再繼續執行了。

    如果線程函數不需傳參,可以這樣創建:

    HANDLE hThread = CreateThread(NULL,0,FunProc,NULL,0,NULL);//被創建後立即執行。

    如果在之後的程序中不適用該線程的引用

    CloseHandle(hThread);是一個很好的習慣。

    該函數可以使線程的內核對象計數-1.這樣,當線程結束時,就可以釋放該線程的內核對象。否則要等待進程結束時才能釋放。

  • Sleep函數

    操作系統爲每一個運行線程安排一定的CPU時間——時間片。如果main函數耗時很少,在時間片內執行完了,此時線程的函數都得不到執行。那怎麼辦呢?

 

    有兩個方法:

  1. 用while(condition) 做空循環。
  2. 用Sleep()函數。

    考慮到main函數做空循環也是需要付出代價的,所以用Sleep顯得更有效率。該函數傳入的時間是以毫秒爲單位。

 

  • 互斥對象

    如果多個線程都可以對一個資源進行讀寫,那怎麼保證不出差錯呢?

    在學JAVA時,老師講過一個很經典的例子。

 

    JIM和MARY共用一個信用卡賬戶,他們約定不讓賬戶的錢少於0。一天JIM去取錢,發現金額爲500,很開心,但是他突然睡着了。很不巧的是MARY也去取錢,此時金額一定是500,MARY取了500。等JIM醒來,他也取了500,此時賬戶上的錢是-500了。爲了防止此類事情發生,我們需要一個鎖,用這個鎖來鎖住這個賬戶。

 

用CreateMutex可以創造這種鎖:

    HANDLE hMutex=CreateMutex(NULL,TRUE,NULL); 

    第一個參數:  一般爲NULL,用缺省的安全性。 

    第二個參數:   創建進程希望立即擁有互斥體,則設爲TRUE,否則爲FALSE。一個互斥體同時只能由一個線程擁有。

   第三個參數NULL,匿名的互斥對象。

 

    一個線程可以多次擁有一個互斥對象,這種功能是靠計數器維護的。

    而WaitForSingleObject(hMutex,INFINITE);可以使計數器+1;

    ReleaseMutex(hMutex);可以使計數器-1。

    以上的兩句代碼市場嵌套在要保護的代碼中。注意,不能把兩句代碼寫在不同的函數裏,應該在哪裏申請到互斥對象就在哪裏釋放。

    當然如果我們不寫ReleaseMutex(hMutex);等到線程結束後,系統也會爲我們做類似的工作。

   

 

    如果在main函數中HANDLE hMutex=CreateMutex(NULL,TRUE,NULL);  那麼main函數就得到了互斥對象,那麼加了鎖的線程就得不到訪問的機會。所以要ReleaseMutex(hMutex)。

 

    其實在CreateMutex(NULL,TRUE,NULL);  之後,也可以寫WaitForSingleObject(hMutex,INFINITE);

    不過這時候計數器就爲2了,所以要ReleaseMutex(hMutex)兩次才能達到之前的效果。

 

  • 命名的互斥對象

    CreateMutex的第三個參數爲互斥對象的名字,如果給他命名,我們就可以用它來判斷我們實例化了幾個程序。

參考代碼如下:

 

發佈了36 篇原創文章 · 獲贊 52 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章