Windows下多线程编程技术及其实现
生活随笔
收集整理的這篇文章主要介紹了
Windows下多线程编程技术及其实现
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
本文首先討論16位Windows下不具備的線程的概念,然后著重講述在32位Windows?95環境下多線程的編程技術,最后給出利用該技術的一個實例,即基于Windows95下TCP/IP的可視電話的實現。??一、問題的提出?
??作者最近在開發基于Internet網上的可視電話過程中,碰到了這樣一個問題。在基于Internet網上的可視電話系統中,同時要進行語音采集、語音編解碼、圖象采集、圖象編解碼、語音和圖象?碼流的傳輸,?所有的這些事情,都要并行處理。特別是語音信號,如果進行圖象編解碼時間過長,語音信號得不到服務,通話就有間斷,如果圖象或語音處理時間過長,而不能及時的傳輸碼流數據,通信同樣也會中斷。這樣就要求我們實現一種并行編程,在只有一個CPU的機器上,也就是要將該CPU時間按照一定的優先準則分配給各個事件,定期處理某一事件而不會在某一事件處理過長,在32位Windows95或WindowsNT下,我們可以用多線程的編程技術來實現這種并行編程。實際上這種并行編程在很多場合下都是必須的。例如,在FileManager拷貝文件時,它顯示一個對話框,?列出源文件和目標文件的名稱,并在對話框中包含了一個Cancel按鈕。如果在文件拷貝過程中,點中Cancel按鈕,就會終止拷貝。?
??在16位Windows中,實現這類功能需要在FileCopy循環內部周期性地調用PeekMessage函數。如果正在讀一個很大的數據塊,則只有當這個塊讀完以后才能響應這個按鈕動作,如果從軟盤讀文件,則要花費好幾秒的時間,由于機器反應太遲鈍,你會頻繁地點中這個按鈕,以為系統不知道你想終止這個操作。如果把FileCopy指令放入另外一個線程,你就不需要在代碼中放一大堆PeekMessage函數,處理?用戶界面的線程將與它分開操作,這樣,點中Cancel按鈕后會立即得到響應。同樣的道理,在應用程序中創建一個單獨線程來處理所有打印任務也是很有用的,這樣,用戶可以在打印處理時繼續使用應用程序。?
??二、線程的概念?
??為了了解線程的概念,我們必須先討論一下進程的概念。?
??一個進程通常定義為程序的一個實例。在Win32中,?進程占據4GB的地址空間。與它們在MS-DOS和16位Windows操作系統中不同,?Win32進程是沒有活力的。這就是說,一個Win32進程并不執行什么指令,它只是占據著4GB的地址空間,此空間中有應用程序EXE文件的?代碼和數據。EXE需要的任意DLL也將它們的代碼和數據裝入到進程的地址空間。除了地址空間,進程還占有某些資源,比如文件、動態內存分配和線程。當進程終止時,在它生命期中創建的各種資源將被清除。?
??但是進程是沒有活力的,它只是一個靜態的概念。為了讓進程完成一些工作,進程必須至少占有一個線程,所以線程是描述進程內的執行,正是線程負責執行包含在進程的地址空間中的代碼。實際上,單個進程可以包含幾個線程,?它們可以同時執行進程的地址空間中的代碼。為了做到這一點,每個線程有自己的一組CPU寄存器和堆棧。?
??每個進程至少有一個線程在執行其地址空間中的代碼,如果沒有線程執行進程?地址空間中的代碼,?進程也就沒有繼續存在的理由,系統將自動清除進程及其地址空間。為了運行所有這些線程,操作系統為每個獨立線程安排一些CPU?時間,操作系統以輪轉方式向線程提供時間片,這就給人一種假象,好象這些線程都在同時運行。創建一個Win32進程時,它的第一個線程稱為主線程,它?由系統自動生成,然后可由這個主線程生成額外的線程,這些線程,又可生成更多的線程。?
??三、線程的編程技術?
??1、編寫線程函數?
??所有線程必須從一個指定的函?數開始執行,該函數稱為線程函數,它必須具有下列原型:?
????DWORDWINAPIYourThreadFunc(LPVOIDlpvThreadParm);?
??該函數輸入一個LPVOID型的參數,可以是一個DWORD型的整數,也可以是一個指向一個緩沖區的指針,?返回一個DWORD型的值。象WinMain函數一樣,這個函數并不由操作系統調用,?操作系統調用包含在KERNEL32.DLL中的非C運行時的一個內部函數,如StartOfThread,然后由StartOfThread函數建立起一個異常處理框架后,調用我們的函數。?
??2、創建一個線程?
??一個進程的主線程是由操作系統自動生成,如果你要讓一個主線程創建額外的線程,你可以調用來CreateThread完成。?
??HANDLECreateThread(LPSECURITY_ATTRIBUTES?lpsa,DWORDcbstack,LPTHREAD_START_ROUTINElpStartAddr,?
LPVOID?lpvThreadParm,DWORDfdwCreate,LPDWORDlpIDThread);?
??其中lpsa參數為一個指向SECURITY_ATTRIBUTES結構的指針。如果想讓對象為缺省安全屬性的話,可以傳一個NULL,如果想讓任一個子進程都可繼承一個該線程對象句柄,必須指定一個SECURITY_ATTRIBUTES結構,其中bInheritHandle成員初始化為TRUE。參數cbstack表示線程為自己所用堆棧分配的地址空間大小,0表示采用系統缺省值。?
??參數lpStartAddr用來表示新線程開始執行時代碼所在函數的地址,即為線程函數。lpvThreadParm為傳入線程函數的參數,fdwCreate參數指定控制線程創建的附加標志,可以取兩種值。如果該參數為0,線程就會立即開始執行,如果該參數為CREATE_SUSPENDED,則系統產生線程后,初始化CPU,登記CONTEXT結構的成員,準備好執行該線程函數中的第一條指令,但并不馬上執行,而是掛起該線程。最后一個參數lpIDThread?是一個DWORD類型地址,返回賦給該新線程的ID值。?
??3、終止線程?
??如果某線程調用了ExitThread?函數,就可以終止自己。?
??VOIDExitThread(UINTfuExitCode?);?
??這個函數為調用該函數的線程設置了退出碼fuExitCode后,?就終止該線程。調用TerminateThread函數亦可終止線程。?
??BOOLTerminateThread(HANDLE?hThread,DWORDdwExitCode);?
??該函數用來結束由hThread參數指定的線程,?并把dwExitCode設成該線程的退出碼。當某個線程不在響應時,我們可以用其他線程調用該函數來終止這個不響應的線程。?
??4、設定線程的相對優先級?
??當一個線程被首次創建時,它的優先級等同于它所屬進程的優先級。在單個進程內可以通過調用SetThreadPriority函數改變線程的相對優先級。一個線程的優先級是相對于其所屬的進程的優先級而言的。?
??BOOLSetThreadPriority(HANDLE?hThread,intnPriority);?
??其中參數hThread是指向待修改?優先級線程的句柄,nPriority可以是以下的值:?
??THREAD_PRIORITY_LOWEST,?
??THREAD_PRIORITY_BELOW_NORMAL,?
??THREAD_PRIORITY_NORMAL,?
??THREAD_PRIORITY_ABOVE_NORMAL,?
??THREAD_PRIORITY_HIGHEST?
??5、掛起及恢復線程?
??先前我提到過可以創建掛起狀態的線程(通過傳遞CREATE_SUSPENDED標志給函數CreateThread來實現)。當你這樣做時,系統創建指定線程的核心對象,創建線程的棧,在CONTEXT結構中初始化線程CPU注冊成員。然而,線程對象被分配了一個初始掛起計數值1,這表明了系統將不再分配CPU去執行線程。要開始執行一個線程,另一個線程必須調用ResumeThread并傳遞給它調用CreateThread時返回的線程句柄。?
??DWORD?ResumeThread(HANDLEhThread);?
??一個線程可以被掛起多次。如果一個線程被掛起3次,?則該線程在它被分配CPU之前必須被恢復3次。除了在創建線程時使用CREATE_SUSPENDED標志,你還可以用SuspendThread函數掛起線程。?
??DWORDSuspendThread(HANDLE?hThread);?
四、多線程編程技術的應用?
??我在前面說過,為了實現基于TCP/IP下的可視電話,就必須“并行”地執行語音采集、語音編解碼、圖象采集、圖象編解碼以及碼流數據的接收與發送。語音與圖象的采集由硬件采集卡進行,我們的程序只需初始化該硬件采集卡,然后實時讀取采集數據即可,但語音和圖象數據的編解碼以及碼流數據的傳輸都必須由程序去協調執行,決不能在某一件事件上處理過長,必須讓CPU輪流的為各個事件服務,Windows95下的線程正是滿足這種要求的編程技術。?
??下面我給出了利用Windows95?環境下的多線程編程技術實現的基于TCP/IP的可視電話的部分源碼,其中包括主窗口過程函數,以及主叫端與被叫端的TCP/IP接收線程函數和語音編解碼的線程函數。由于圖象編解碼的實時性比語音處理與傳輸模塊的實時性的?要求要低些,所以我以語音編解碼為事件去查詢圖象數據,然后進行圖象編解碼,而沒有為圖象編解碼去單獨實現一個線程。?
??在主窗口初始化時,?我用CREATE_SUSPENDED標志創建了兩個線程hThreadG7231和hThreadTCPRev。一個用于語音編解碼,它的線程函數為G723Proc,?該線程不斷查詢本地有無編好碼的語音和圖象的碼流,如有,則進行H.223打包,然后通過TCP的端口發送給對方。另外一個線程用于TCP/IP的接收,它的線程函數為AcceptThreadProcRev,該線程不斷偵?測TCP/IP端口有無對方傳來的碼流,如有,就接收碼流,進行H.223解碼后送入相應的緩沖區。該緩沖區的內容,由語音編解碼線程G723Proc查詢,并送入相應的解碼器。由于使用了多線程的編程技術,使得操作系統定時去服務語?音編解碼模塊和傳輸模塊,從而保證了通信的不中斷。?
??五、程序源碼?
//基于TCP/IP可視電話主窗口的窗口過程?
LONG?APIENTRY?MainWndProc(HWND?hWnd,UINT?message,UINT?wParam,?LONG?lParam)?
{?
?static?HANDLE?hThreadG7231,hThreadTCPListen,hThreadTCPRev;?
?DWORDThreadIDG7231,ThreadIDTCPListen,ThreadIDTCPRev;?
?static?THREADPACK?tp;?
?static?THREADPACK?tp1;?
?unsigned?char?Buf[80];?
?CAPSTATUS?capStatus;?
?switch?(message)?
??{?
???case?WM_CREATE:?
?????Init_Wsock(hWnd);?//初始化一些數據結構?
?????Init_BS(2,&bs);?
?????vd_tx_pdu.V_S?=?0;vd_tx_pdu.N_S?=?0;?
?????vd_rx_pdu.V_R?=?0;vd_tx_sdu.bytes?=?0;?
?????if(?dnldProg?(?hWnd,?"h324g723.exe")?)?
??????{?
???????//裝入語音編解碼的DSP核心?
???????MessageBox(hWnd,"Load?G.723.1?Kernel?Error","Error",MB_OK);?
???????PostQuitMessage(0);?}?
?????else?
???????MessageBox(hWnd,"Load?G.723.1?Kernel?OK!","Indication",MB_OK);?
?????//創建語音編解碼的線程?
?????parag7231.hWnd?=?hWnd;?
?????hThreadG7231=CreateThread?(NULL,?0,(LPTHREAD_START_ROUTINE)G723Proc,?
???????????????????(G7231DATA?*)?g7231,?
???????????????????CREATE_SUSPENDED,(LPDWORD)&ThreadIDG7231);?
?????if?(!hThreadG7231)?
??????{?
???????wsprintf(Buf,?"Error?in?creating?G7231?thread:?%d",GetLastError());?
???????MessageBox?(hWnd,?Buf,?"WM_CREATE",?MB_OK);}?
?????//創建TCP/IP接收線程?
?????tp1.hWnd?=?hWnd;?
?????hThreadTCPRev?=?CreateThread?(NULL,?0,(LPTHREAD_START_ROUTINE)AcceptThreadProcRev,?
????????????????????(G7231DATA?*)&tp1,CREATE_SUSPENDED,?
????????????????????(LPDWORD)&ThreadIDTCPRev);?
?????if?(!hThreadTCPRev)?
??????{?
???????wsprintf(Buf,?"Error?in?creating?TCP?Receive?thread:?%d",GetLastError());?
???????MessageBox?(hWnd,?Buf,?"WM_CREATE",?MB_OK);}?
?????//開始偵聽網絡?
?????SendMessage(hWnd,WM_COMMAND,IDM_LISTEN,NULL);?
?????break;?
???case?WM_VIDEO_ENCODE:?//圖象編碼?
?????if(needencode)EncodeFunction(hWnd);?
?????needencode?=?SendVideoToBuff(&vd_tx_sdu,?buff);?
?????frameMode=TRUE;?
?????capPreview(capWnd,FALSE);?
?????capOverlay(capWnd,FALSE);?
?????capGrabFrameNoStop(capWnd);?
?????break;?
???case?WM_VIDEO_DECODE:?//圖象解碼?
?????Video_Decod_begin?=?1;?
?????play_movie();?
?????Video_Decod_begin?=?0;?
?????break;?
???case?WM_COMMAND:?
?????switch(LOWORD(wParam))?
??????{?
???????case?IDM_CONNECT:?//響應對方的呼叫,接通可視電話?
?????????WskConnect(?hWnd?);?
?????????ResumeThread(hThreadTCPRev);?//運行TCP/IP接收線程?
?????????ResumeThread(hThreadG7231);?//運行語音編解碼線程?
?????????BeginG7231Codec();?//初始化圖象采集卡,并開始采集圖象?
?????????frameMode?=?FALSE;?
?????????capWnd?=?capCreateCaptureWindow((LPSTR)"Capture?Window",?
??????????????????????????WS_CHILD?|?WS_VISIBLE,?
??????????????????????????100,?100,?176,144?,?
??????????????????????????(HWND)?hWnd,?(int)?0);?
?????????capSetCallbackOnError(capWnd,?(FARPROC)ErrorCallbackProc)?;?
?????????capSetCallbackOnStatus(capWnd,?(FARPROC)StatusCallbackProc)?;?
?????????capSetCallbackOnFrame(capWnd,?(FARPROC)FrameCallbackProc)?;?
?????????capDriverConnect(capWnd,?0);?
?????????CenterCaptureWindow(hWnd,?capWnd);?
?????????capDlgVideoSource(capWnd);?
?????????capDlgVideoFormat(capWnd);?
?????????capDlgVideoCompression(capWnd);?
?????????capGetStatus(capWnd,&capStatus,sizeof(CAPSTATUS));?
?????????StartNewVideoChannel(hWnd,?capWnd)?;?
?????????image?=?image_one;?
?????????frameMode?=?TRUE;?
?????????capPreview(capWnd,FALSE);?
?????????capOverlay(capWnd,FALSE);?
?????????capGrabFrameNoStop(capWnd);?
?????????break;?
???????case?IDM_LISTEN:?//撥對方號碼,呼叫對方?
?????????sock?=?socket(?AF_INET,?SOCK_STREAM,?0);?
?????????if?(sock?==?INVALID_SOCKET)?{?
??????????MessageBox(hWnd,?"socket()?failed",?"Error",?MB_OK);?
??????????closesocket(sock);?
??????????break;}?
?????????if?(!FillAddr(hWnd,?&local_sin,?FALSE?))?//獲取TCP/IP地址和端口號?
??????????break;?
?????????EnableMenuItem(GetMenu(?hWnd?),?IDM_LISTEN,?MF_GRAYED);?
?????????SetWindowText(?hWnd,?"Waiting?for?connection..");?
?????????bind?(?sock?,?(struct?sockaddr?FAR?*)&local_sin,sizeof(local_sin);?
?????????if?(listen(?sock,?MAX_PENDING_CONNECTS?)?<0)?
??????????{?
???????????sprintf(szBuff,?"%d?is?the?error",?
???????????????WSAGetLastError());?MessageBox(hWnd,?szBuff,?"listen(sock)?failed",?
???????????????MB_OK);?
???????????break;}?
?????????tp.hWnd="hWnd;?//開始本地的TCP/IP接收線程"?
?????????_beginthread(AcceptThreadProc,0,&tp);?
?????????ResumeThread(hThreadG7231);?//?開始本地語音編解碼的線程?
?????????break;?
???????case?IDM_DISCONNECT:?//掛斷可視電話?
?????????CloseG7231Codec();?
?????????SuspendThread(hThreadG7231);?
?????????SuspendThread(hThreadTCPRev);?
?????????WSACleanup();?
?????????Init_Video_Decod_Again();?
?????????capSetCallbackOnError(capWnd,?NULL);?
?????????capSetCallbackOnStatus(capWnd,?NULL);?
?????????InvalidateRect(hWnd,NULL,1);?capSetCallbackOnFrame(capWnd,?NULL);?
?????????capSetCallbackOnVideoStream(capWnd,?NULL);?
?????????capDriverDisconnect(capWnd);?
?????????Init_Wsock(hWnd);?
?????????MessageBox(hWnd,?"Now?closing?the?Video?telephone","",MB_OK);?
?????????SetDisConnectMenus(hWnd);?
?????????SendMessage(hWnd,?WM_COMMAND,IDM_LISTEN,NULL);?
?????????break;?
???????case?IDM_EXIT:?
?????????CloseG7231Codec();?
?????????SendMessage(hWnd,?WM_CLOSE,?0,?0l);?
?????????break;?default:?
??????return?(DefWindowProc(hWnd,?message,?wParam,?lParam));?
??????}?
??????break;?
????case?WM_CLOSE:?
??????if?(IDOK?!="MessageBox("?hWnd,?"OK?to?close?window?",?gszAppName,?
??????????????????MB_ICONQUESTION?|?MB_OKCANCEL?))break?;?
????case?WM_DESTROY:?
??????WSACleanup();?
??????CloseG7231Codec();?
??????TerminateThread(hThreadG7231,0);?
??????TerminateThread(hThreadTCPRev,0);?
??????capSetCallbackOnError(capWnd,?NULL);?
??????capSetCallbackOnStatus(capWnd,?NULL);?
??????capSetCallbackOnFrame(capWnd,?NULL);?
??????capSetCallbackOnVideoStream(capWnd,?NULL);?
??????capDriverDisconnect(capWnd);?
??????FreeAll();?
??????PostQuitMessage(0);?
??????break;?
???default:?/*?Passes?it?on?if?unproccessed?*/?
???????return?(DefWindowProc(hWnd,?message,?wParam,?lParam));?
???}?
?return?(0);?
}?
//主叫方TCP/IP接收線程?
DWORD?WINAPI?AcceptThreadProc(?PTHREADPACK?ptp?)?
{?
?SOCKADDR_IN?acc_sin;?/*?Accept?socket?address?internet?style?*/?
?int?acc_sin_len;?/*?Accept?socket?address?length?*/?
?int?status;?
?acc_sin_len="sizeof(acc_sin);"?
?//調用阻塞函數accept,一直到遠端響應為止?
?sock="accept("?sock,(struct?sockaddr?FAR?*)?&acc_sin,(int?FAR?*)?&acc_sin_len?);?
?if?(sock?<?0)?
??{?
???sprintf(szBuff,?"%d?is?the?error",?WSAGetLastError());?
???????MessageBox(ptp->hWnd,?szBuff,?"accept(sock)?failed",?MB_OK);?
???return?(1);?
??}?
?SetConnectMenus(?ptp->hWnd?);?//遠端提機,可視電話接通?
?BeginG7231Codec();?
?while?(1)?
??{?
???beg1:?
????status?=?recv((SOCKET)sock,?r_mux_buf,MY_MSG_LENGTH,?NO_FLAGS_SET?);?
???if?(status?==?SOCKET_ERROR)?{?
????status?=?WSAGetLastError();?
????if(?status?==?10054?){?
?????MessageBox(ptp->hWnd,"對方掛斷電話","Indication",?MB_OK);?
?????SendMessage(?ptp->hWnd,?WM_COMMAND,IDM_DISCONNECT,NULL);?
?????_endthread();?
?????return?(1);?
?????}?
????goto?beg1;?
????}?
???if?(status)?{?
?????r_mux_buf[?status?]?=?'\0';?
?????if?(?r_mux_buf_filled?==?1?)?
??????r_mux_buf_overwrite?=?1;?
?????else?
?????r_mux_buf_filled?=?1;?
?????r_mux_buf_length?=?status;?
????}?
???else?
????{?
?????MessageBox(?hWnd,?"Connection?broken",?"Error",?MB_OK);?
?????SendMessage(?ptp->hWnd,?WM_COMMAND,IDM_DISCONNECT,NULL);?
?????_endthread();?
?????return?(2);?
????}?
???demux();?//線路碼流H.223解碼?
???}?
?return?(0);?
}?
//被叫方TCP/IP接收線程?
DWORD?WINAPI?AcceptThreadProcRev(?PTHREADPACK?ptp?)?
{?
?int?status;?
?while?(1)?
??{?
??beg2:?
??status?=?recv((SOCKET)sock,?r_mux_buf,MY_MSG_LENGTH,?NO_FLAGS_SET?);?
??if?(status?==?SOCKET_ERROR)?
???{?
????status?=WSAGetLastError();?
????if(?status?==?10054?)?
?????{?
??????MessageBox(ptp->hWnd,"對方掛斷電話","Indication",?MB_OK);?
??????SendMessage(?ptp->hWnd,?WM_COMMAND,IDM_DISCONNECT,NULL);?
??????return?(1);?
?????}?
????goto?beg2;?
????}?
??if?(status)?
???{?
????r_mux_buf[?status?]?=?'\0';?
????if(?r_mux_buf_filled?==?1?)?
?????r_mux_buf_overwrite?=?1;?
????else?
?????r_mux_buf_filled?=?1;?
?????r_mux_buf_length?=?status;?
???}?
??else?
???{?
????MessageBox(?hWnd,?"Connection?broken",?"Error",?MB_OK);?
????SendMessage(?ptp->hWnd,?WM_COMMAND,IDM_DISCONNECT,NULL);?
????return?(2);?
???}?
??demux();?
?}?/*?while?(forever)?*/?
?return?(0);?
}?
//語音編解碼線程?
DWORD?WINAPI?G723Proc(G7231DATA?*data)?
{?
?int?i,len;?
?Audio_tx_pduad_tx_pdu;?
?unsigned?char?mux[MAX_MUX_PDU_SIZE];?
?do?
??{?
???len?=?0;?
???//檢測本地有無語音,圖象碼流要傳輸?
???i?=?DetectAudioVideoData();?
???switch(i)?
????{?
?????case?AUDIO_ONLY:?//只有語音碼流?
???????AL2_CRC_coder(&ad_tx_sdu,&ad_tx_pdu);?
???????//H.223打包?
???????len?=?AL2_To_MUX(&ad_tx_pdu,?mux);?
???????break;?
?????case?VIDEO_ONLY:?//只有圖象碼流?
???????SDU_To_PDU(&vd_tx_sdu,&vd_tx_pdu);?
???????tx_AL3_I_PDU(&vd_tx_pdu?,&bs?,?1);?//H.223打包?
???????len?=?AL3_To_MUX(&vd_tx_pdu,mux);?
???????break;?
?????case?AUDIO_VIDEO:?//語音和圖象碼流?
???????AL2_CRC_coder(&ad_tx_sdu,&ad_tx_pdu);?
???????SDU_To_PDU(&vd_tx_sdu,&vd_tx_pdu);?
???????tx_AL3_I_PDU(&vd_tx_pdu?,&bs?,?1);?
???????//H.223打包?
???????len?=?AL2_AL3_To_MUX(&ad_tx_pdu,&vd_tx_pdu,mux);?
???????break;?
?????case?NO_AUDIO_VIDEO:?//此刻無碼流要傳輸?
???????break;?
????}?
???//TCP/IP發送碼流?
???if(len?!=?0)?
???send((SOCKET)sock,mux,len,0);?
???//是否接收到待解碼的碼流,有就調用解碼器?
???PutVideoStreamToDecod();?
??}?
?while(1);?
?return?(0);?
}
??作者最近在開發基于Internet網上的可視電話過程中,碰到了這樣一個問題。在基于Internet網上的可視電話系統中,同時要進行語音采集、語音編解碼、圖象采集、圖象編解碼、語音和圖象?碼流的傳輸,?所有的這些事情,都要并行處理。特別是語音信號,如果進行圖象編解碼時間過長,語音信號得不到服務,通話就有間斷,如果圖象或語音處理時間過長,而不能及時的傳輸碼流數據,通信同樣也會中斷。這樣就要求我們實現一種并行編程,在只有一個CPU的機器上,也就是要將該CPU時間按照一定的優先準則分配給各個事件,定期處理某一事件而不會在某一事件處理過長,在32位Windows95或WindowsNT下,我們可以用多線程的編程技術來實現這種并行編程。實際上這種并行編程在很多場合下都是必須的。例如,在FileManager拷貝文件時,它顯示一個對話框,?列出源文件和目標文件的名稱,并在對話框中包含了一個Cancel按鈕。如果在文件拷貝過程中,點中Cancel按鈕,就會終止拷貝。?
??在16位Windows中,實現這類功能需要在FileCopy循環內部周期性地調用PeekMessage函數。如果正在讀一個很大的數據塊,則只有當這個塊讀完以后才能響應這個按鈕動作,如果從軟盤讀文件,則要花費好幾秒的時間,由于機器反應太遲鈍,你會頻繁地點中這個按鈕,以為系統不知道你想終止這個操作。如果把FileCopy指令放入另外一個線程,你就不需要在代碼中放一大堆PeekMessage函數,處理?用戶界面的線程將與它分開操作,這樣,點中Cancel按鈕后會立即得到響應。同樣的道理,在應用程序中創建一個單獨線程來處理所有打印任務也是很有用的,這樣,用戶可以在打印處理時繼續使用應用程序。?
??二、線程的概念?
??為了了解線程的概念,我們必須先討論一下進程的概念。?
??一個進程通常定義為程序的一個實例。在Win32中,?進程占據4GB的地址空間。與它們在MS-DOS和16位Windows操作系統中不同,?Win32進程是沒有活力的。這就是說,一個Win32進程并不執行什么指令,它只是占據著4GB的地址空間,此空間中有應用程序EXE文件的?代碼和數據。EXE需要的任意DLL也將它們的代碼和數據裝入到進程的地址空間。除了地址空間,進程還占有某些資源,比如文件、動態內存分配和線程。當進程終止時,在它生命期中創建的各種資源將被清除。?
??但是進程是沒有活力的,它只是一個靜態的概念。為了讓進程完成一些工作,進程必須至少占有一個線程,所以線程是描述進程內的執行,正是線程負責執行包含在進程的地址空間中的代碼。實際上,單個進程可以包含幾個線程,?它們可以同時執行進程的地址空間中的代碼。為了做到這一點,每個線程有自己的一組CPU寄存器和堆棧。?
??每個進程至少有一個線程在執行其地址空間中的代碼,如果沒有線程執行進程?地址空間中的代碼,?進程也就沒有繼續存在的理由,系統將自動清除進程及其地址空間。為了運行所有這些線程,操作系統為每個獨立線程安排一些CPU?時間,操作系統以輪轉方式向線程提供時間片,這就給人一種假象,好象這些線程都在同時運行。創建一個Win32進程時,它的第一個線程稱為主線程,它?由系統自動生成,然后可由這個主線程生成額外的線程,這些線程,又可生成更多的線程。?
??三、線程的編程技術?
??1、編寫線程函數?
??所有線程必須從一個指定的函?數開始執行,該函數稱為線程函數,它必須具有下列原型:?
????DWORDWINAPIYourThreadFunc(LPVOIDlpvThreadParm);?
??該函數輸入一個LPVOID型的參數,可以是一個DWORD型的整數,也可以是一個指向一個緩沖區的指針,?返回一個DWORD型的值。象WinMain函數一樣,這個函數并不由操作系統調用,?操作系統調用包含在KERNEL32.DLL中的非C運行時的一個內部函數,如StartOfThread,然后由StartOfThread函數建立起一個異常處理框架后,調用我們的函數。?
??2、創建一個線程?
??一個進程的主線程是由操作系統自動生成,如果你要讓一個主線程創建額外的線程,你可以調用來CreateThread完成。?
??HANDLECreateThread(LPSECURITY_ATTRIBUTES?lpsa,DWORDcbstack,LPTHREAD_START_ROUTINElpStartAddr,?
LPVOID?lpvThreadParm,DWORDfdwCreate,LPDWORDlpIDThread);?
??其中lpsa參數為一個指向SECURITY_ATTRIBUTES結構的指針。如果想讓對象為缺省安全屬性的話,可以傳一個NULL,如果想讓任一個子進程都可繼承一個該線程對象句柄,必須指定一個SECURITY_ATTRIBUTES結構,其中bInheritHandle成員初始化為TRUE。參數cbstack表示線程為自己所用堆棧分配的地址空間大小,0表示采用系統缺省值。?
??參數lpStartAddr用來表示新線程開始執行時代碼所在函數的地址,即為線程函數。lpvThreadParm為傳入線程函數的參數,fdwCreate參數指定控制線程創建的附加標志,可以取兩種值。如果該參數為0,線程就會立即開始執行,如果該參數為CREATE_SUSPENDED,則系統產生線程后,初始化CPU,登記CONTEXT結構的成員,準備好執行該線程函數中的第一條指令,但并不馬上執行,而是掛起該線程。最后一個參數lpIDThread?是一個DWORD類型地址,返回賦給該新線程的ID值。?
??3、終止線程?
??如果某線程調用了ExitThread?函數,就可以終止自己。?
??VOIDExitThread(UINTfuExitCode?);?
??這個函數為調用該函數的線程設置了退出碼fuExitCode后,?就終止該線程。調用TerminateThread函數亦可終止線程。?
??BOOLTerminateThread(HANDLE?hThread,DWORDdwExitCode);?
??該函數用來結束由hThread參數指定的線程,?并把dwExitCode設成該線程的退出碼。當某個線程不在響應時,我們可以用其他線程調用該函數來終止這個不響應的線程。?
??4、設定線程的相對優先級?
??當一個線程被首次創建時,它的優先級等同于它所屬進程的優先級。在單個進程內可以通過調用SetThreadPriority函數改變線程的相對優先級。一個線程的優先級是相對于其所屬的進程的優先級而言的。?
??BOOLSetThreadPriority(HANDLE?hThread,intnPriority);?
??其中參數hThread是指向待修改?優先級線程的句柄,nPriority可以是以下的值:?
??THREAD_PRIORITY_LOWEST,?
??THREAD_PRIORITY_BELOW_NORMAL,?
??THREAD_PRIORITY_NORMAL,?
??THREAD_PRIORITY_ABOVE_NORMAL,?
??THREAD_PRIORITY_HIGHEST?
??5、掛起及恢復線程?
??先前我提到過可以創建掛起狀態的線程(通過傳遞CREATE_SUSPENDED標志給函數CreateThread來實現)。當你這樣做時,系統創建指定線程的核心對象,創建線程的棧,在CONTEXT結構中初始化線程CPU注冊成員。然而,線程對象被分配了一個初始掛起計數值1,這表明了系統將不再分配CPU去執行線程。要開始執行一個線程,另一個線程必須調用ResumeThread并傳遞給它調用CreateThread時返回的線程句柄。?
??DWORD?ResumeThread(HANDLEhThread);?
??一個線程可以被掛起多次。如果一個線程被掛起3次,?則該線程在它被分配CPU之前必須被恢復3次。除了在創建線程時使用CREATE_SUSPENDED標志,你還可以用SuspendThread函數掛起線程。?
??DWORDSuspendThread(HANDLE?hThread);?
四、多線程編程技術的應用?
??我在前面說過,為了實現基于TCP/IP下的可視電話,就必須“并行”地執行語音采集、語音編解碼、圖象采集、圖象編解碼以及碼流數據的接收與發送。語音與圖象的采集由硬件采集卡進行,我們的程序只需初始化該硬件采集卡,然后實時讀取采集數據即可,但語音和圖象數據的編解碼以及碼流數據的傳輸都必須由程序去協調執行,決不能在某一件事件上處理過長,必須讓CPU輪流的為各個事件服務,Windows95下的線程正是滿足這種要求的編程技術。?
??下面我給出了利用Windows95?環境下的多線程編程技術實現的基于TCP/IP的可視電話的部分源碼,其中包括主窗口過程函數,以及主叫端與被叫端的TCP/IP接收線程函數和語音編解碼的線程函數。由于圖象編解碼的實時性比語音處理與傳輸模塊的實時性的?要求要低些,所以我以語音編解碼為事件去查詢圖象數據,然后進行圖象編解碼,而沒有為圖象編解碼去單獨實現一個線程。?
??在主窗口初始化時,?我用CREATE_SUSPENDED標志創建了兩個線程hThreadG7231和hThreadTCPRev。一個用于語音編解碼,它的線程函數為G723Proc,?該線程不斷查詢本地有無編好碼的語音和圖象的碼流,如有,則進行H.223打包,然后通過TCP的端口發送給對方。另外一個線程用于TCP/IP的接收,它的線程函數為AcceptThreadProcRev,該線程不斷偵?測TCP/IP端口有無對方傳來的碼流,如有,就接收碼流,進行H.223解碼后送入相應的緩沖區。該緩沖區的內容,由語音編解碼線程G723Proc查詢,并送入相應的解碼器。由于使用了多線程的編程技術,使得操作系統定時去服務語?音編解碼模塊和傳輸模塊,從而保證了通信的不中斷。?
??五、程序源碼?
//基于TCP/IP可視電話主窗口的窗口過程?
LONG?APIENTRY?MainWndProc(HWND?hWnd,UINT?message,UINT?wParam,?LONG?lParam)?
{?
?static?HANDLE?hThreadG7231,hThreadTCPListen,hThreadTCPRev;?
?DWORDThreadIDG7231,ThreadIDTCPListen,ThreadIDTCPRev;?
?static?THREADPACK?tp;?
?static?THREADPACK?tp1;?
?unsigned?char?Buf[80];?
?CAPSTATUS?capStatus;?
?switch?(message)?
??{?
???case?WM_CREATE:?
?????Init_Wsock(hWnd);?//初始化一些數據結構?
?????Init_BS(2,&bs);?
?????vd_tx_pdu.V_S?=?0;vd_tx_pdu.N_S?=?0;?
?????vd_rx_pdu.V_R?=?0;vd_tx_sdu.bytes?=?0;?
?????if(?dnldProg?(?hWnd,?"h324g723.exe")?)?
??????{?
???????//裝入語音編解碼的DSP核心?
???????MessageBox(hWnd,"Load?G.723.1?Kernel?Error","Error",MB_OK);?
???????PostQuitMessage(0);?}?
?????else?
???????MessageBox(hWnd,"Load?G.723.1?Kernel?OK!","Indication",MB_OK);?
?????//創建語音編解碼的線程?
?????parag7231.hWnd?=?hWnd;?
?????hThreadG7231=CreateThread?(NULL,?0,(LPTHREAD_START_ROUTINE)G723Proc,?
???????????????????(G7231DATA?*)?g7231,?
???????????????????CREATE_SUSPENDED,(LPDWORD)&ThreadIDG7231);?
?????if?(!hThreadG7231)?
??????{?
???????wsprintf(Buf,?"Error?in?creating?G7231?thread:?%d",GetLastError());?
???????MessageBox?(hWnd,?Buf,?"WM_CREATE",?MB_OK);}?
?????//創建TCP/IP接收線程?
?????tp1.hWnd?=?hWnd;?
?????hThreadTCPRev?=?CreateThread?(NULL,?0,(LPTHREAD_START_ROUTINE)AcceptThreadProcRev,?
????????????????????(G7231DATA?*)&tp1,CREATE_SUSPENDED,?
????????????????????(LPDWORD)&ThreadIDTCPRev);?
?????if?(!hThreadTCPRev)?
??????{?
???????wsprintf(Buf,?"Error?in?creating?TCP?Receive?thread:?%d",GetLastError());?
???????MessageBox?(hWnd,?Buf,?"WM_CREATE",?MB_OK);}?
?????//開始偵聽網絡?
?????SendMessage(hWnd,WM_COMMAND,IDM_LISTEN,NULL);?
?????break;?
???case?WM_VIDEO_ENCODE:?//圖象編碼?
?????if(needencode)EncodeFunction(hWnd);?
?????needencode?=?SendVideoToBuff(&vd_tx_sdu,?buff);?
?????frameMode=TRUE;?
?????capPreview(capWnd,FALSE);?
?????capOverlay(capWnd,FALSE);?
?????capGrabFrameNoStop(capWnd);?
?????break;?
???case?WM_VIDEO_DECODE:?//圖象解碼?
?????Video_Decod_begin?=?1;?
?????play_movie();?
?????Video_Decod_begin?=?0;?
?????break;?
???case?WM_COMMAND:?
?????switch(LOWORD(wParam))?
??????{?
???????case?IDM_CONNECT:?//響應對方的呼叫,接通可視電話?
?????????WskConnect(?hWnd?);?
?????????ResumeThread(hThreadTCPRev);?//運行TCP/IP接收線程?
?????????ResumeThread(hThreadG7231);?//運行語音編解碼線程?
?????????BeginG7231Codec();?//初始化圖象采集卡,并開始采集圖象?
?????????frameMode?=?FALSE;?
?????????capWnd?=?capCreateCaptureWindow((LPSTR)"Capture?Window",?
??????????????????????????WS_CHILD?|?WS_VISIBLE,?
??????????????????????????100,?100,?176,144?,?
??????????????????????????(HWND)?hWnd,?(int)?0);?
?????????capSetCallbackOnError(capWnd,?(FARPROC)ErrorCallbackProc)?;?
?????????capSetCallbackOnStatus(capWnd,?(FARPROC)StatusCallbackProc)?;?
?????????capSetCallbackOnFrame(capWnd,?(FARPROC)FrameCallbackProc)?;?
?????????capDriverConnect(capWnd,?0);?
?????????CenterCaptureWindow(hWnd,?capWnd);?
?????????capDlgVideoSource(capWnd);?
?????????capDlgVideoFormat(capWnd);?
?????????capDlgVideoCompression(capWnd);?
?????????capGetStatus(capWnd,&capStatus,sizeof(CAPSTATUS));?
?????????StartNewVideoChannel(hWnd,?capWnd)?;?
?????????image?=?image_one;?
?????????frameMode?=?TRUE;?
?????????capPreview(capWnd,FALSE);?
?????????capOverlay(capWnd,FALSE);?
?????????capGrabFrameNoStop(capWnd);?
?????????break;?
???????case?IDM_LISTEN:?//撥對方號碼,呼叫對方?
?????????sock?=?socket(?AF_INET,?SOCK_STREAM,?0);?
?????????if?(sock?==?INVALID_SOCKET)?{?
??????????MessageBox(hWnd,?"socket()?failed",?"Error",?MB_OK);?
??????????closesocket(sock);?
??????????break;}?
?????????if?(!FillAddr(hWnd,?&local_sin,?FALSE?))?//獲取TCP/IP地址和端口號?
??????????break;?
?????????EnableMenuItem(GetMenu(?hWnd?),?IDM_LISTEN,?MF_GRAYED);?
?????????SetWindowText(?hWnd,?"Waiting?for?connection..");?
?????????bind?(?sock?,?(struct?sockaddr?FAR?*)&local_sin,sizeof(local_sin);?
?????????if?(listen(?sock,?MAX_PENDING_CONNECTS?)?<0)?
??????????{?
???????????sprintf(szBuff,?"%d?is?the?error",?
???????????????WSAGetLastError());?MessageBox(hWnd,?szBuff,?"listen(sock)?failed",?
???????????????MB_OK);?
???????????break;}?
?????????tp.hWnd="hWnd;?//開始本地的TCP/IP接收線程"?
?????????_beginthread(AcceptThreadProc,0,&tp);?
?????????ResumeThread(hThreadG7231);?//?開始本地語音編解碼的線程?
?????????break;?
???????case?IDM_DISCONNECT:?//掛斷可視電話?
?????????CloseG7231Codec();?
?????????SuspendThread(hThreadG7231);?
?????????SuspendThread(hThreadTCPRev);?
?????????WSACleanup();?
?????????Init_Video_Decod_Again();?
?????????capSetCallbackOnError(capWnd,?NULL);?
?????????capSetCallbackOnStatus(capWnd,?NULL);?
?????????InvalidateRect(hWnd,NULL,1);?capSetCallbackOnFrame(capWnd,?NULL);?
?????????capSetCallbackOnVideoStream(capWnd,?NULL);?
?????????capDriverDisconnect(capWnd);?
?????????Init_Wsock(hWnd);?
?????????MessageBox(hWnd,?"Now?closing?the?Video?telephone","",MB_OK);?
?????????SetDisConnectMenus(hWnd);?
?????????SendMessage(hWnd,?WM_COMMAND,IDM_LISTEN,NULL);?
?????????break;?
???????case?IDM_EXIT:?
?????????CloseG7231Codec();?
?????????SendMessage(hWnd,?WM_CLOSE,?0,?0l);?
?????????break;?default:?
??????return?(DefWindowProc(hWnd,?message,?wParam,?lParam));?
??????}?
??????break;?
????case?WM_CLOSE:?
??????if?(IDOK?!="MessageBox("?hWnd,?"OK?to?close?window?",?gszAppName,?
??????????????????MB_ICONQUESTION?|?MB_OKCANCEL?))break?;?
????case?WM_DESTROY:?
??????WSACleanup();?
??????CloseG7231Codec();?
??????TerminateThread(hThreadG7231,0);?
??????TerminateThread(hThreadTCPRev,0);?
??????capSetCallbackOnError(capWnd,?NULL);?
??????capSetCallbackOnStatus(capWnd,?NULL);?
??????capSetCallbackOnFrame(capWnd,?NULL);?
??????capSetCallbackOnVideoStream(capWnd,?NULL);?
??????capDriverDisconnect(capWnd);?
??????FreeAll();?
??????PostQuitMessage(0);?
??????break;?
???default:?/*?Passes?it?on?if?unproccessed?*/?
???????return?(DefWindowProc(hWnd,?message,?wParam,?lParam));?
???}?
?return?(0);?
}?
//主叫方TCP/IP接收線程?
DWORD?WINAPI?AcceptThreadProc(?PTHREADPACK?ptp?)?
{?
?SOCKADDR_IN?acc_sin;?/*?Accept?socket?address?internet?style?*/?
?int?acc_sin_len;?/*?Accept?socket?address?length?*/?
?int?status;?
?acc_sin_len="sizeof(acc_sin);"?
?//調用阻塞函數accept,一直到遠端響應為止?
?sock="accept("?sock,(struct?sockaddr?FAR?*)?&acc_sin,(int?FAR?*)?&acc_sin_len?);?
?if?(sock?<?0)?
??{?
???sprintf(szBuff,?"%d?is?the?error",?WSAGetLastError());?
???????MessageBox(ptp->hWnd,?szBuff,?"accept(sock)?failed",?MB_OK);?
???return?(1);?
??}?
?SetConnectMenus(?ptp->hWnd?);?//遠端提機,可視電話接通?
?BeginG7231Codec();?
?while?(1)?
??{?
???beg1:?
????status?=?recv((SOCKET)sock,?r_mux_buf,MY_MSG_LENGTH,?NO_FLAGS_SET?);?
???if?(status?==?SOCKET_ERROR)?{?
????status?=?WSAGetLastError();?
????if(?status?==?10054?){?
?????MessageBox(ptp->hWnd,"對方掛斷電話","Indication",?MB_OK);?
?????SendMessage(?ptp->hWnd,?WM_COMMAND,IDM_DISCONNECT,NULL);?
?????_endthread();?
?????return?(1);?
?????}?
????goto?beg1;?
????}?
???if?(status)?{?
?????r_mux_buf[?status?]?=?'\0';?
?????if?(?r_mux_buf_filled?==?1?)?
??????r_mux_buf_overwrite?=?1;?
?????else?
?????r_mux_buf_filled?=?1;?
?????r_mux_buf_length?=?status;?
????}?
???else?
????{?
?????MessageBox(?hWnd,?"Connection?broken",?"Error",?MB_OK);?
?????SendMessage(?ptp->hWnd,?WM_COMMAND,IDM_DISCONNECT,NULL);?
?????_endthread();?
?????return?(2);?
????}?
???demux();?//線路碼流H.223解碼?
???}?
?return?(0);?
}?
//被叫方TCP/IP接收線程?
DWORD?WINAPI?AcceptThreadProcRev(?PTHREADPACK?ptp?)?
{?
?int?status;?
?while?(1)?
??{?
??beg2:?
??status?=?recv((SOCKET)sock,?r_mux_buf,MY_MSG_LENGTH,?NO_FLAGS_SET?);?
??if?(status?==?SOCKET_ERROR)?
???{?
????status?=WSAGetLastError();?
????if(?status?==?10054?)?
?????{?
??????MessageBox(ptp->hWnd,"對方掛斷電話","Indication",?MB_OK);?
??????SendMessage(?ptp->hWnd,?WM_COMMAND,IDM_DISCONNECT,NULL);?
??????return?(1);?
?????}?
????goto?beg2;?
????}?
??if?(status)?
???{?
????r_mux_buf[?status?]?=?'\0';?
????if(?r_mux_buf_filled?==?1?)?
?????r_mux_buf_overwrite?=?1;?
????else?
?????r_mux_buf_filled?=?1;?
?????r_mux_buf_length?=?status;?
???}?
??else?
???{?
????MessageBox(?hWnd,?"Connection?broken",?"Error",?MB_OK);?
????SendMessage(?ptp->hWnd,?WM_COMMAND,IDM_DISCONNECT,NULL);?
????return?(2);?
???}?
??demux();?
?}?/*?while?(forever)?*/?
?return?(0);?
}?
//語音編解碼線程?
DWORD?WINAPI?G723Proc(G7231DATA?*data)?
{?
?int?i,len;?
?Audio_tx_pduad_tx_pdu;?
?unsigned?char?mux[MAX_MUX_PDU_SIZE];?
?do?
??{?
???len?=?0;?
???//檢測本地有無語音,圖象碼流要傳輸?
???i?=?DetectAudioVideoData();?
???switch(i)?
????{?
?????case?AUDIO_ONLY:?//只有語音碼流?
???????AL2_CRC_coder(&ad_tx_sdu,&ad_tx_pdu);?
???????//H.223打包?
???????len?=?AL2_To_MUX(&ad_tx_pdu,?mux);?
???????break;?
?????case?VIDEO_ONLY:?//只有圖象碼流?
???????SDU_To_PDU(&vd_tx_sdu,&vd_tx_pdu);?
???????tx_AL3_I_PDU(&vd_tx_pdu?,&bs?,?1);?//H.223打包?
???????len?=?AL3_To_MUX(&vd_tx_pdu,mux);?
???????break;?
?????case?AUDIO_VIDEO:?//語音和圖象碼流?
???????AL2_CRC_coder(&ad_tx_sdu,&ad_tx_pdu);?
???????SDU_To_PDU(&vd_tx_sdu,&vd_tx_pdu);?
???????tx_AL3_I_PDU(&vd_tx_pdu?,&bs?,?1);?
???????//H.223打包?
???????len?=?AL2_AL3_To_MUX(&ad_tx_pdu,&vd_tx_pdu,mux);?
???????break;?
?????case?NO_AUDIO_VIDEO:?//此刻無碼流要傳輸?
???????break;?
????}?
???//TCP/IP發送碼流?
???if(len?!=?0)?
???send((SOCKET)sock,mux,len,0);?
???//是否接收到待解碼的碼流,有就調用解碼器?
???PutVideoStreamToDecod();?
??}?
?while(1);?
?return?(0);?
}
轉載于:https://www.cnblogs.com/qq78292959/archive/2008/10/13/2077108.html
總結
以上是生活随笔為你收集整理的Windows下多线程编程技术及其实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 查找xml文件中某接点的值
- 下一篇: UML轻松入门--类和对象