日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
網(wǎng)絡(luò)安全編程:多線程編程基礎(chǔ)知識

 線程是進程中的一個執(zhí)行單位(每個進程都必須有一個主線程),一個進程可以有多個線程,而一個線程只存在于一個進程中。在數(shù)據(jù)關(guān)系上,進程與線程是一對多的關(guān)系。線程不擁有系統(tǒng)資源,線程所使用的資源全部由進程向系統(tǒng)申請,線程擁有的是CPU的時間片。

創(chuàng)新互聯(lián)基于分布式IDC數(shù)據(jù)中心構(gòu)建的平臺為眾多戶提供雅安電信機房 四川大帶寬租用 成都機柜租用 成都服務(wù)器租用。

在單處理器上(或單核處理器上),同一個進程中的不同線程交替得到CPU的時間片。在多處理器上(或多核處理器上),不同的線程可以同時運行在不同的CPU上,這樣可以提高程序運行的效率。除此之外,在有些方面必須使用多線程。比如,如果在掃描磁盤并同時在程序界面上同步顯示當(dāng)前掃描的位置時,必須使用多線程。因為在程序界面上顯示和磁盤的掃描工作在同一個線程中,而且界面也在不停進行重新顯示,這樣就會導(dǎo)致軟件看起來像是卡死一樣。在這種情況下,分為兩個線程就可以解決該問題,界面的顯示由主線程完成,而掃描磁盤的工作由另外一個線程完成,兩個線程協(xié)同工作,這樣就可以達到實時顯示當(dāng)前掃描狀態(tài)的效果了。

首先了解一下線程的創(chuàng)建。線程的創(chuàng)建使用CreateThread()函數(shù),該函數(shù)的原型如下:

 
 
 
  1. HANDLE CreateThread( 
  2.  LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD 
  3.  DWORD dwStackSize, // initial stack size 
  4.  LPTHREAD_START_ROUTINE lpStartAddress, // thread function 
  5.  LPVOID lpParameter, // thread argument 
  6.  DWORD dwCreationFlags, // creation option 
  7.  LPDWORD lpThreadId // thread identifier 
  8. );

參數(shù)說明如下。

lpThreadAttributes:指明創(chuàng)建線程的安全屬性,為指向 SECURITY_ATTRIBUTES 結(jié)構(gòu)的指針,該參數(shù)一般設(shè)置為 NULL。

dwStackSize:指定線程使用缺省的堆棧大小,如果為 NULL,則與進程主線程棧相同。

lpStartAddress:指定線程函數(shù),線程即從該函數(shù)的入口處開始運行,函數(shù)返回時就意味著線程終止運行,該函數(shù)屬于一個回調(diào)函數(shù)。線程函數(shù)的定義形式如下:

 
 
 
  1. DWORD WINAPI ThreadProc( 
  2.  LPVOID lpParameter // thread data 
  3. );

線程函數(shù)的返回值為DWORD類型,線程函數(shù)只有一個參數(shù),該參數(shù)在CreateThread()函數(shù)中給出。該函數(shù)的函數(shù)名稱可以任意給定。很多時候并不能保證執(zhí)行了CreateThread()函數(shù)后線程就會立即啟動,線程的啟動需要等待CPU的調(diào)度,CPU將時間片給該線程時,該線程才會執(zhí)行,當(dāng)然這個時間短到可以忽略它。

lpParameter:該參數(shù)表示傳遞給線程函數(shù)的一個參數(shù),可以是指向任意數(shù)據(jù)類型的指針。這里是一個指針,可以方便的將多個參數(shù)通過結(jié)構(gòu)體等一次性傳到線程函數(shù)中。

dwCreationFlags:該參數(shù)指明創(chuàng)建線程后的線程狀態(tài),在創(chuàng)建線程后可以讓線程立刻執(zhí)行(這里的立即執(zhí)行的意思是不會受人為的去讓它處于等待狀態(tài)),也可以讓線程處于暫停狀態(tài)。如果需要立刻執(zhí)行,該參數(shù)設(shè)置為 0;如果要讓線程處于暫停狀態(tài),那么該參數(shù)設(shè)置為 CREATE_SUSPENDED,待需要線程執(zhí)行時調(diào)用ResumeThread()函數(shù)讓線程的狀態(tài)調(diào)整為等待運行的狀態(tài),然后由 CPU 分配時間片后去執(zhí)行。

lpThreadId:該參數(shù)用于返回新創(chuàng)建線程的線程 ID。

如果線程創(chuàng)建成功,該函數(shù)返回線程的句柄,否則返回NULL。創(chuàng)建新線程后,該線程就開始啟動執(zhí)行了。但如果在dwCreationFlags中使用了CREATE_SUSPENDED參數(shù),那么線程并不馬上執(zhí)行,而是先掛起,等到調(diào)用ResumeThread后才開始啟動線程。線程的句柄需要通過CloseHandle()進行關(guān)閉,以便釋放資源。

寫一個簡單的多線程的例子,代碼如下:

 
 
 
  1. #include  
  2. #include  
  3. DWORD WINAPI ThreadProc(LPVOID lpParam) 
  4.   printf("ThreadProc \r\n"); 
  5.   return 0; 
  6. int main() 
  7.   HANDLE hThread = CreateThread(NULL,0,ThreadProc,NULL,0,NULL); 
  8.   printf("main \r\n"); 
  9.   CloseHandle(hThread); 
  10.   return 0; 
  11. }

代碼在主線程中打印一行“main”,在創(chuàng)建的新線程中會打印一行“ThreadProc”。編譯運行,查看其運行結(jié)果,如圖1所示。

圖1  多線程程序輸出結(jié)果

從圖1中看出,程序的輸出跟預(yù)期的結(jié)果并不相同。程序的問題出在了哪里呢?每個線程都有屬于自己的CPU時間片,當(dāng)主線程創(chuàng)建新線程后,主線程的CPU時間片并未結(jié)束,它會向下繼續(xù)執(zhí)行。由于主線程的代碼非常少,因此主線程在CPU分配的時間片中就執(zhí)行完成并退出了。由于主線程的結(jié)束,意味著進程也就結(jié)束并退出了。因此,在代碼中創(chuàng)建的線程雖然被創(chuàng)建了,但是根本就沒有執(zhí)行的機會。那么在這么短的代碼中,如何保證新創(chuàng)建的線程在主線程結(jié)束前就能得到執(zhí)行呢?或者說,主線程的運行需要等待新線程的完成才得以執(zhí)行。這里需要使用WaitForSingleObject()函數(shù),該函數(shù)的原型如下:

 
 
 
  1. DWORD WaitForSingleObject( 
  2.  HANDLE hHandle, // handle to object 
  3.  DWORD dwMilliseconds // time-out interval 
  4. );

參數(shù)說明如下。

hHandle:該參數(shù)為要等待的對象句柄。

dwMilliseconds:該參數(shù)指定等待超時的毫秒數(shù),如果設(shè)為 0,則立即返回,如果設(shè)為 INFINITE,則表示一直等待線程函數(shù)的返回。INFINITE 是系統(tǒng)定義的一個宏,其定義如下。

 
 
 
  1. #define INFINITE 0xFFFFFFFF

如果該函數(shù)失敗,則返回WAIT_FAILED;如果等待的對象編程激發(fā)狀態(tài),則返回WAIT_ OBJECT_0;如果等待對象變成激發(fā)狀態(tài)之前,等待時間結(jié)束了,將返回WAIT_TIMEOUT。

修改上面的代碼,在CreateThread()函數(shù)后面加入如下代碼:

 
 
 
  1. WaitForSingleObject(hThread, INFINITE);

添加WaitForSingleObject()函數(shù)以后,主線程會等待新創(chuàng)建的線程結(jié)束再繼續(xù)向下執(zhí)行主線程后續(xù)的代碼。這樣在控制臺上的輸出如圖2所示。

圖2  主線程等待子線程的執(zhí)行

WaitForSingleObject()只能等待一個線程,可是在程序中往往要創(chuàng)建多個線程來執(zhí)行,那么如果需要等待若干個線程的完成狀態(tài)的話,WaitForSingleObject()函數(shù)就無能為力了。不過,系統(tǒng)除了提供WaitForSingleObject()函數(shù)外,還提供了另外一個可以等待多個線程的完成狀態(tài)的函數(shù)WaitForMultipleObjects(),該函數(shù)的定義如下:

 
 
 
  1. DWORD WaitForMultipleObjects( 
  2.  DWORD nCount, // number of handles in array 
  3.  CONST HANDLE *lpHandles, // object-handle array 
  4.  BOOL fWaitAll, // wait option 
  5.  DWORD dwMilliseconds // time-out interval 
  6. );

該函數(shù)的參數(shù)比WaitForSingleObject()函數(shù)多2個參數(shù),下面介紹這些參數(shù)。

nCount:該參數(shù)用于指明想要讓函數(shù)等待的線程的數(shù)量。該參數(shù)的取值范圍在 1 到 MAXIMUM_WAIT _OBJECTS 之間。

lpHandles:該參數(shù)是指向等待線程句柄的數(shù)組指針。

fWaitAll:該參數(shù)表示是否等待全部線程的狀態(tài)完成,如果設(shè)置為 TRUE,則等待全部。

dwMilliseconds:該參數(shù)與 WaitForSingleObject()函數(shù)中的 dwMilliseconds 用法相同。

WaitForSingleObject()和WaitForMultipleObjects()兩個函數(shù)除了可以等待線程外,還可以等待用于多線程同步和互斥的內(nèi)核對象。

在使用多線程的時候常常需要考慮和注意的問題很多。比如多線程同時對一個共享資源進行操作,通過線程需要按照一定的順序執(zhí)行等??匆粋€簡單的多線程例子:

 
 
 
  1. int g_Num_One = 0; 
  2. DWORD WINAPI ThreadProc(LPVOID lpParam) 
  3.   int nTmp = 0; 
  4.   for ( int i = 0; i < 10; i ++ ) 
  5.   { 
  6.     nTmp = g_Num_One; 
  7.     nTmp ++; 
  8.     // Sleep(1)的作用是讓出 CPU 
  9.     // 使其他線程被調(diào)度運行 
  10.     Sleep(1);
  11.     g_Num_One = nTmp; 
  12.   } 
  13.   return 0; 
  14. }

每個線程都有一個CPU時間片,當(dāng)自己的時間片運行完成后,CPU會停止該線程的運行,并切換到其他線程去運行。當(dāng)多線程同時操作一個共享資源時,這樣的切換會帶來隱形的問題。這里的代碼比較短,在一個CPU時間片內(nèi)肯定會完成,無法體現(xiàn)出因線程切換而產(chǎn)生的錯誤。為了達到能夠因線程切換導(dǎo)致的錯誤,在代碼中加入了Sleep(1),使得線程主動讓出CPU,讓CPU進行線程切換。在代碼中,線程處理的共享資源是全局變量g_Num_One變量。主函數(shù)創(chuàng)建線程的代碼如下:

 
 
 
  1. int main() 
  2.   HANDLE hThread[10] = { 0 }; 
  3.   int i; 
  4.   for ( i = 0; i < 10; i ++ ) 
  5.   { 
  6.     hThread[i] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL); 
  7.   } 
  8.   WaitForMultipleObjects(10, hThread, TRUE, INFINITE); 
  9.   for ( i = 0; i < 10; i ++ ) 
  10.   { 
  11.     CloseHandle(hThread[i]); 
  12.   } 
  13.   printf("g_Num_One = %d \r\n", g_Num_One); 
  14.   return 0; 
  15. }

在主函數(shù)中,通過CreateThread()創(chuàng)建了10個線程,每個線程都讓g_Num_One自增10次,每次的增量為1。那么10個線程會使得g_Num_One的結(jié)果變成100。編譯運行上面的代碼,查看輸出結(jié)果,如圖3所示。

圖3  多線程操作共享資源的錯誤結(jié)果

這個結(jié)果和預(yù)測的結(jié)果并不相同。為什么會產(chǎn)生這種不同呢?這里進行一次模擬分析。為了方便分析,把線程的數(shù)量縮小為兩個線程,分別是A線程和B線程。

① g_Num_One的初始值為0。

② 當(dāng)A線程中執(zhí)行nTmp = g_Num_One和nTmp++后(此時nTmp的值為1),因為Sleep(1)的原因發(fā)生了線程切換,此時g_Num_One的初始值仍然為0。

③ 當(dāng)B線程中執(zhí)行nTmp = g_Num_One和nTmp++后(此時nTmp的值也為1),因為Sleep(1)的原因又發(fā)生了線程切換。

④ A線程執(zhí)行g(shù)_Num_One = nTmp,此時g_Num_One的值為1,接著執(zhí)行下一次循環(huán)中的nTmp = g_Num_One和nTmp++的操作,又進行切換。

⑤ B線程執(zhí)行g(shù)_Num_One = nTmp,此時g_Num_One的值為1。

到第⑤步時,不繼續(xù)往下分析了,已經(jīng)可以看出原因。g_Num_One的值是最后一次nTmp進行賦值后的值(線程中的局部變量屬于線程內(nèi)私有的,雖然是同一個線程函數(shù),但是nTmp在每個線程中是私有的)。

解決該問題,這里使用的是臨界區(qū)。臨界區(qū)對象是一個CRITICAL_SECTION的數(shù)據(jù)結(jié)構(gòu),Windows操作系統(tǒng)使用該數(shù)據(jù)結(jié)構(gòu)對關(guān)鍵代碼進行保護,以確保多線程下的共享資源。在同一時間內(nèi),Windows只允許一個線程進入臨界區(qū)。

臨界區(qū)的函數(shù)有4個,分別是初始化臨界區(qū)對象(InitializeCriticalSection())、進入臨界區(qū)(EnterCriticalSection())、離開臨界區(qū)(LeaveCriticalSection())和刪除臨界區(qū)對象(DeleteCriticalSection())。臨界區(qū)很好的保護了共享資源,臨界區(qū)在現(xiàn)實生活中有很多類似的例子。比如,在進行體檢的時候,一個體檢室內(nèi)只有一個體檢醫(yī)生,體檢醫(yī)生會叫一個患者進去體檢,這時其他人是不能進入的,當(dāng)這個患者離開后,下一個患者才可以進入。這里體檢醫(yī)生就是一個共享的資源,而每個體檢的患者是多個不同的線程。臨界區(qū)就是以類似的方式保護了共享資源不被破壞的。下面依次來看一下這四個函數(shù)關(guān)于臨界區(qū)的函數(shù)的定義,分別如下:

 
 
 
  1. VOID InitializeCriticalSection( 
  2.  LPCRITICAL_SECTION lpCriticalSection // critical section 
  3. ); 
  4. VOID EnterCriticalSection( 
  5.  LPCRITICAL_SECTION lpCriticalSection // critical section 
  6. ); 
  7. VOID LeaveCriticalSection( 
  8.  LPCRITICAL_SECTION lpCriticalSection // critical section 
  9. ); 
  10. VOID DeleteCriticalSection( 
  11.  LPCRITICAL_SECTION lpCriticalSection // critical section 
  12. );

這4個API函數(shù)的參數(shù)都是指向CRITICAL_SECTION結(jié)構(gòu)體的指針。修改上面有問題的代碼,修改后的代碼如下:

 
 
 
  1. #include  
  2. #include  
  3. int g_Num_One = 0; 
  4. CRITICAL_SECTION g_cs; 
  5. DWORD WINAPI ThreadProc(LPVOID lpParam) 
  6.   int nTmp = 0; 
  7.   for ( int i = 0; i < 10; i ++ ) 
  8.   { 
  9.     // 進入臨界區(qū) 
  10.     EnterCriticalSection(&g_cs); 
  11.     nTmp = g_Num_One; 
  12.     nTmp ++; 
  13.     Sleep(1); 
  14.     g_Num_One = nTmp; 
  15.     // 離開臨界區(qū) 
  16.     LeaveCriticalSection(&g_cs); 
  17.   } 
  18.   return 0; 
  19. int main() 
  20.   InitializeCriticalSection(&g_cs); 
  21.   HANDLE hThread[10] = { 0 }; 
  22.   int i; 
  23.   for ( i = 0; i < 10; i ++ ) 
  24.   { 
  25.     hThread[i] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL); 
  26.   } 
  27.   WaitForMultipleObjects(10, hThread, TRUE, INFINITE); 
  28.   printf("g_Num_One = %d \r\n", g_Num_One); 
  29.   for ( i = 0; i < 10; i ++ ) 
  30.   { 
  31.     CloseHandle(hThread[i]); 
  32.   } 
  33.   DeleteCriticalSection(&g_cs); 
  34.   return 0; 
  35. }

編譯以上代碼并運行,輸出結(jié)果為想要的正確結(jié)果,即g_Num_One的值為100。除了使用臨界區(qū)以外,對于線程的同步與互斥還有其他方法,這里就不一一進行介紹了。在開發(fā)多線程程序時,要注意多線程的同步與互斥問題。臨界區(qū)對象只能用于多線程的互斥。


本文標(biāo)題:網(wǎng)絡(luò)安全編程:多線程編程基礎(chǔ)知識
網(wǎng)站鏈接:http://www.5511xx.com/article/cohgcgc.html