• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            多線程程序設計的相關問題

            Posted on 2008-10-20 10:09 RichardHe 閱讀(993) 評論(0)  編輯 收藏 引用 所屬分類: [再轉]
            一、    什么是進程?什么是線程?
               進程是一大堆系統對象擁有權的集合。如進程擁有內存上下 文,文件句柄,可以派生出很多線程,也可以擁有很多DLL模塊。在windows系統中,進程并不完成實質的工作,只是提供一個相對獨立的運行環境,線程 才是完成實際工作的載體。線程從屬于進程,共享進程所擁有的系統對象。線程是操作系統調度的單位。實質上,線程就是一段可執行代碼。
            采用多進程的優點和缺點:
            優點:運行環境相對獨立,某一進程的崩潰一般不會影響到其它進程的執行。
            缺點:
            耗時耗資源:啟動一個進程需要申請大量的系統資源,其中包括虛擬內存、文件句柄以及加載各種必要的動態鏈接庫;線程則不需要以上動作,因為它共享進程中的所有資源。
            “系統準備一個進程環境可能需要好幾M的空間”
            通 信復雜:進程的地址空間獨立,進程A的地址X,在進程B中可能是無意義的,這樣,當進程間需要共享數據時,就需要特殊的機制來完成這些工作。線程則在同一 地址空間,數據共享方便快捷。“線程是一個物美價廉的選擇,在一個Windows上擁有500個線程是一件很輕易的事情,但是500個進程將是難以想象的 ”。

            二、    為什么需要多線程(解釋何時考慮使用線程)
            從用戶的角度考慮,就是為了得到更好的系統服務;從程序自身的角度考慮,就是使目標任務能夠盡可能快的完成,更有效的利用系統資源。綜合考慮,一般以下場合需要使用多線程:
            1、    程序包含復雜的計算任務時
            主要是利用多線程獲取更多的CPU時間(資源)。
            2、    處理速度較慢的外圍設備
            比如:打印時。再比如網絡程序,涉及數據包的收發,時間因素不定。使用獨立的線程處理這些任務,可使程序無需專門等待結果。
            3、    程序設計自身的需要
            WINDOWS系統是基于消息循環的搶占式多任務系統,為使消息循環系統不至于阻塞,程序需要多個線程的來共同完成某些任務。
            三、    使用多線程可能出現的問題(列舉問題)
            事 實上,單純的使用線程不會產生任何問題,其啟動、運行和結束都是非常簡單的事情。在Win32環境下,啟動:CreateThread,運行就是函數執行 的過程,中止就是函數返回的過程或者調用ExitThread。但是由于下列原因可能會使在使用線程的過程中帶來一系列問題:
            1、    版本問題
            多 任務的概念是隨著實際需求的提出而產生,最初的程序設計者并沒有考慮到代碼需要在多線程環境下運行,在多線程環境下使用這些代碼無疑將產生訪問沖突。最典 型的例子就是C runtime library。最早的C runtime library產生于20世紀70年代,當時連多任務都是一個新奇的概念,更別說什么多線程了,該版本的庫中使用了大量全局變量和靜態變量(產生競爭條件 的根源,對局部變量無此要求,因為局部變量都使用棧,每個線程都有自己的棧空間,另外在啟動線程時,給線程函數的參數應該是盡量使用值,而非指針或引用, 這樣可以避免因此帶來的沖突問題),如在該庫中統一使用一個errno變量來表明程序的錯誤碼,如果在多線程中使用該庫,并且都需要設置錯誤碼時,此時即 產生了一個沖突。
            VC為防止以上問題,提供了另外一個線程安全的C runtime library,因此在寫多線程程序時,需要注意所連接庫的版本是否正確(該過程一般由應用程序向導完成,因此平時編程并無此問題)。與此有關的還有一些 其它版本:單線程版、多線程版調試版和多線程發行版。
            2、    線程間共享資源時形成競爭條件(race condition)
            一般而 言,線程并不是單獨行動,通常是多個線程分工協作,完成一個大任務中的不同小任務,此時,這些線程之間就需要共同操作一些資源,比較典型的例子是多個線程 進行文件操作或屏幕打印的情況:線程A在寫文件進行了一半時,發生了context switch,另外一個線程B繼續進行寫文件操作,此時文件的內容將會凌亂不堪。甚至造成異常錯誤。典型的例子是,三個線程,線程A在堆中申請了一塊內存 并填入了一個值,線程B讀取了該值后將該內存釋放,如果線程C還要對該內存操作時,將導致異常。
            3、    線程間的通信問題
            線程協作完 成某一任務時,有時還需要通信以控制任務的執行步驟,典型的例子就是讀寫者線程:寫線程在對某內存區域寫完數據后,需要通知讀線程來取,讀完之后又需要通 知寫線程可以繼續往里寫入數據。更為廣泛的例子是:某線程需要等待某一事件發生,以決定是否繼續工作。此時,如果沒有正確控制線程的執行過程,將導致不可 預料的錯誤發生。
            4、    由于不規范的使用線程導致系統效率下降
            進程中包含了一個以上的線程,這些線程可能會動態的申請某些資源,如 某些數據庫線程可能會動態加載數據庫方面的動態鏈接庫,但是在該線程結束時,并沒有及時釋放該動態鏈接庫即被其他線程強行終止,于是該進程中的該動態鏈接 庫引用計數不為0,從而導致該動態鏈接庫在該進程中存有一個副本。當這種情況頻繁時,將對系統效率產生很大的影響。
            四、    線程的類型(解釋UI線程和WORKER線程的區別和聯系)
            嚴格說來,線程并沒有什么本質區別,但是Win32編程文檔中卻反復強調UI線程和Worker線程的區別。并給出了它們的定義:
            UI線程就是:擁有消息隊列和窗口的線程,并且它的主要職責是處理窗口消息。Worker線程則沒有消息隊列,但是當Worker線程產生一個用戶界面(消息框和模式對話框除外)時,則該線程則搖身一變,成為UI線程。
            問題:
            1、    線程的消息隊列和窗口的消息隊列
            在Win32中,每個線程都有它自己專屬的消息隊列,而窗口并不總是有消息隊列,因為一個UI線程可以創建很多個窗口。
            2、    UI線程到底跟Worker線程存在什么差別?
            職 責不一樣:UI線程負責處理與用戶界面有關的消息,一般而言,用戶界面消息來自用戶輸入(如鼠標鍵盤消息)、系統消息(如WM_PAINT)以及程序產生 的用戶自定義消息。因此,在該線程下一般不能存在等待(wait…)函數,這樣該線程就會掛起,從而影響消息隊列的處理。Worker線程不用處理用戶界 面消息,而是完成一般性的計算任務,該線程等待計算過程中必要的資源時,不會影響到界面的刷新動作。
            操作系統的管理不一樣:對UI線程來說,產生一個UI線程實際上產生了兩個線程,一個是其自身,另一個是操作系統為響應其GDI調用而產生的影子線程。
            3、    Worker線程變成UI線程有什么不好?
            Worker線程一般用于計算,此時如果它轉換為UI線程的話,將無暇顧及用戶界面的消息響應。
            4、    Worker線程可否擁有自己的消息隊列?
            Worker線程同樣可以擁有自己的消息隊列,該隊列一般通過PeekMessage()調用建立,通過GetMessage調用來解析。(具體實現看源碼)
            5、    用以下規則來管理win32中線程、消息和窗口的互動
            所有傳送給某一窗口的消息,將由產生該窗口的線程負責處理。
            五、    線程的啟動和中止(解釋啟動線程的不同方式及其它們的區別和實用場合)
            隨C Runtime Library庫的更新和編程環境的不同,線程的啟動方式也有所不同,以下介紹幾種典型的線程啟動方式。
            1、_beginthread和_endthread
            該 函數是C Runtime Library中的函數,它負責初始化函數庫;其原型如下unsigned long _beginthread( void( __cdecl *start_address )( void * ), unsigned stack_size, void *arglist );“該函數被認為是頭腦簡單的函數”,使用該函數導致無法有效的控制被創建線程,如不能在啟動時將該線程掛起,無法為該線程設置優先權等。另外,該函數 為隱藏Win32的實現細節,啟動線程的第一件事情即將自己的Handle關閉,因此也就無法利用這個Handle來等待該線程結束等操作。該函數是早期 的C Runtime Library的產物,不提倡使用,后期的改良版本為_beginthreadex。
            通過_beginthread啟動的線程在應當通過調用_endthread結束,以保證清除與線程相關的資源。
            2、_beginthreadex和_endthreadex
            該 函數是C Runtime Library中的一個函數,用標準C實現,相比_beginthread,_beginthreadex對線程控制更為有力(比前者多三個參數),是 _beginthread的加強版。其原型為unsigned long _beginthreadex( void *security, unsigned stack_size, unsigned ( __stdcall *start_address )( void * ), void *arglist, unsigned initflag, unsigned *thrdaddr );該函數返回新線程的句柄,通過該句柄可實現對線程的控制。雖然,該函數是用標準C寫的(即可不加修改就可以移植到其他系統執行),但是由于它與 Windows系統有著緊密的聯系(需要手動關閉該線程產生的Handle),因此實現時,往往需要包含windows.h。
            通過_beginthreadex啟動的線程通過調用_endthreadex做相關清理。
            3、CreateThread和ExitThread
            CreateThread 是Win32 API函數集中的一個函數,其原型為HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,DWORD dwStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,DWORD wCreationFlags,LPDWORD lpThreadId);該函數使用Win32編程環境中的類型約定,只適用于Windows系統。參數形式與_beginthreadex一致,對線程 控制能力也與之一致,只是該函數與C Runtime Library沒有任何關系,它不負責初始化該庫,因此在多線程環境中,如果使用該函數啟動線程,則不應使用C Runtime Library中的多線程版本的函數。取而代之的應該是功能相對應的 Win32 API函數;另外,應當自己手工提供線程同步的代碼。
            通過CreateThread創建的線程則通過ExitThread做清理工作。
            4、AfxBeginThread和AfxEndThread
            AfxBeginThread 是MFC提供的線程啟動方式,它是個重載函數,有兩種調用形式:Worker線程版和UI線程版。MFC對Win32線程做了小心的很好的封裝 (CWinThread),雖然其總是調用了_beginthreadex來啟動一個線程,但是其額外做的工作使得在MFC環境下,操作線程變得簡單明 了,并且不需要太多的關注細節問題。MFC在線程的封裝方面主要做了下列事情:
            1、    自動清除CWinThread對象
            2、    關閉線程handle,線程對象自動釋放
            3、    存儲了線程相關的重要參數,即線程handle和線程ID
            4、    輔之以其它MFC同步對象,方便的實現線程同步
            5、    使用了嚴格的斷言調試語句,使線程調試變得相對簡單

            “(C Runtime Library是用標準C開發的實用函數集)如果多線程程序中使用了標準C庫函數,并用CreateThread()和ExitThread(),則會導 致內存泄漏。解決這個問題的方法是用C運行庫(run-time library)函數來啟動和終止線程,而不用WIN32 API定義的CreateThread()和ExitThread()。在C運行庫函數中,它們的替代函數分別是_beginthreadex()和 _exitthreadex(),需要的頭文件是_process.h。在VC6.0下,還需在 Project->Settings->C/C++->Code Generation中選擇Multithreaded Runtime Library。當然,也可以通過避免使用C標準庫函數的方法來解決上述問題,WIN32提供了一些C標準庫函數的替代函數,例如,可用 wsprintf()和lstrlen()來代替sprintf()和strlen()。這樣,使用CreateThread()和 ExitThread()不會出現問題。”
            六、    線程的同步問題(介紹Windows的同步機制)
            1、    怎樣等待一個線程結束(忙等(busy loop)和高效的等(WaitForSingleObject))
            1)    忙等(busy loop)
            hThrd = CreateThread(NULL,0,ThreadFunc,(LPVOID)1,0,&threadId );
            for (;;)
            {
            GetExitCodeThread(hThrd, &exitCode);
            if ( exitCode != STILL_ACTIVE )
            break;
            }
            CloseHandle(hThrd);
            缺點:耗費CPU資源,且如果在UI線程中這樣等待將導致窗口無法刷新。不推薦使用。
            2)    高效的等待
            (1)WaitForSingleObject;
            關于WaitForSingleObject的參數,前者為等待的對象,后者為等待的時間,對某些執行時間較長的線程,可以設置一個合適的值,等待完這個時間后,更新界面,然后繼續等待,或者強行終止線程。
            將以上的等待部分的代碼改為:
            WaitForSingleObject(hThrd,INFINITE);
            該函數相當于Sleep函數,當需要等待的對象(句柄)沒有被觸發時,等待的線程將被自動掛起。該方法解決了耗費CPU時間的問題,但是在UI線程中,仍不能使用該方法來等待某一線程結束。
            解決方法之一:創建一個Worker管理者線程,在該線程中等待,工作者線程完成,然后由管理者線程發消息通知UI線程更新窗口。
            (2)WaitForMultipleObject
            該函數允許在同一時間等待多個對象,函數的原型如下:
            DWORD WaitForMultipleObject(DWORD nCount,CONST HANDE *lpHandles,BOOL bWaitAll,dwMilliseconds);
            第一個參數表示句柄數組的大小;等待的對象不能超過64
            第二個參數為句柄數組;
            第三個參數表明是否等待所有對象激發。True表示是。
            第四個參數為等待時間。
            關于WaitForMultipleObject的返回值:
            當bWaitAll為True時,返回值為WAIT_OBJECT_0;
            當bWaitAll為false時,返回值減去WAIT_OBJECT_0,就是激發對象所在的下標。
            應用:
            A)    解決多個工人n完成多個任務m(n<m)的問題(bWaitAll設置為false)
            解決的思路如下:先從m個任務中取出n個任務,對應地用n個工人去完成,然后利用該函數等待其中任意一個工人結束任務,一旦結束則讓其做另外一個任務
            B)    解決等待多個資源的問題(bWaitAll設置為true)
            哲學家就餐問題:5個哲學家在圓桌旁,每個哲學家左手邊放著1只筷子,哲學家做兩件事情,吃飯和思考,吃飯時同時需要其左右的兩只筷子。
            解決思路:將哲學家模擬為線程,筷子為資源,只有哲學家線程同時獲得兩個資源時,方可進一步動作(吃飯)。即:
            WaitForMultipleObjects(2, myChopsticks, TRUE, INFINITE);
            MyChopsticks是一個大小為5的核心對象數組。
                    (3)MsgWaitForMultipleObjects
                    原型:
            DWORD MsgWaitForMultipleObjects( DWORD nCount,CONST HANDLE pHandles,BOOL fWaitAll,DWORD dwMilliseconds,DWORD dwWakeMask);
                前 幾個參數含義同WaitForMultipleObject,最后一個是消息屏蔽標識,指示接收消息的類型。此外返回值也有額外的意義:當消息到達時,該 函數返回WAIT_OBJECT_0+nCount。以下是常見的使用MsgWaitForMultipleObjects的架構:
              while (!quit)
                {   // Wait for next message or object being signaled
                    DWORD   dwWake;
                    dwWake = MsgWaitForMultipleObjects(
                                            gNumPrinting,
                                            gPrintJobs,
                                            FALSE,
                                            INFINITE,
                                            QS_ALLEVENTS);

                    if (dwWake >= WAIT_OBJECT_0 && dwWake < WAIT_OBJECT_0 + gNumPrinting)
                    {  
                        //對象被觸發
                    } // end if
                    else if (dwWake == WAIT_OBJECT_0 + gNumPrinting)
                    {
                        //有消息到達
                        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
                        {   // Get Next message in queue
                             if (msg.message == WM_QUIT)
                             {
                                 quit = TRUE;
                                 exitCode = msg.wParam;
                                 break;
                             } // end if
                             TranslateMessage(&msg);
                             DispatchMessage(&msg);
                       } // end while
                    }
                } // end while
            2、    怎樣有效的控制一個線程
            在任何情況下,切記線程的核心屬性為:線程的句柄,線程的ID號。因此控制一個線程也需從這兩方面著手。
            1)    使用能返回線程Handle的啟動函數來啟動線程(除_beginthread外)
            2)    盡量不要使一個工作量較大的線程成為“悶葫蘆”,從而使該線程能夠接收外界通知消息;如下列代碼:


            MSG msg;
                while(1)
                {
                    PeekMessage(&msg,NULL,0,0,PM_REMOVE);
                    if(msg.message==WM_MY)
                        break;
                    Sleep(100);
                }
            注:GetMessage也是用來得到消息隊列中一條消息的函數,它們的區別在于GetMessage是同步的,即如果消息隊列中沒有消息的話,該線程將自動掛起。使用GetMessage可以使Worker線程成為一個一步一動的線程!
                MSG msg;
                while(GetMessage(&msg,NULL,0,0))
                {
                    if(msg.message==WM_MY)
                    {
                        //Do something here
            }
                }
            以上的過程也可以通過事件對象予以實現。
            懸而未決的問題:怎么控制一個正在等待其他事件的線程。如:一個TCP監聽線程,在某一Socket上listen,此時該線程處于掛起狀態!但是現在主線程又需要關閉該線程,應該怎么操作!

            3、    怎樣互斥訪問一個資源(CMutex和Critical Section)
            何時需要一個互斥對象?
            常 見的情形:多個線程需要不定時的操作同一鏈表(鎖鏈表的頭指針);多個線程需要不定時的進行寫文件或是進行屏幕輸出(鎖文件句柄或屏幕句柄);多個線程需 要不定時對某個計數器進行操作(鎖這個變量);在多線程環境嚇,凡是涉及到對全局變量、靜態變量、堆中的內存進行訪問時,都應該考慮,是否可能出現一個 race condition(競爭條件)。
            1)    互斥器
            Win32提供了對互斥資源訪問的一整套機制,其中之一就是互斥器,MFC將這些API函數加以封裝,形成了CMutex互斥類,使用這兩種方法都能夠實現對資源的互斥訪問。
            Win32中的API:
            CreateMutex:
            原型:
            HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,   BOOL bInitialOwner,  LPCTSTR lpName );
            第一個參數為安全屬性;
            第二個參數用來指示互斥器的擁有者是否為當前線程;
            第三個參數為互斥器的名稱;
            當不再需要互斥器時,應當調用CloseHandle關閉。
            約 定:互斥器產生之后,由某一線程完成鎖定工作(即調用Wait…函數),此時系統將該mutex的擁有權交于該線程,然后短暫地將該對象設置為激發態,于 是Wait…函數返回,做完相應的工作之后(如:修改鏈表指針、修改計數器、寫文件等),調用ReleaseMutex釋放擁有權。周而復始。
            MFC中的互斥器CMutex對象:
            A、    利用其構造函數產生一個互斥器對象
            HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes,BOOL bInitialOwner, LPCTSTR lpName);
            B、    配合CSingleLock或者CmutipleLock產生一個臨時對象,對產生的互斥器進行加鎖和釋放的動作;
            2)    臨界區
            另一個提供互斥訪問的機制是Critical Section,該機制較前一種方法廉價,因為它不屬于不是系統的核心對象;臨界區可以反復進入,這一點與Mutex有所區別,這需要我們在使用臨界區時,保證進入的次數要等于離開的次數。
            相關函數為InitializeCriticalSection、DeleteCriticalSection、EnterCriticalSection、LeaveCriticalSection。
            4、    怎樣等待多個不同(或者相同)資源(WaitForMultiObject)
            等待多個不同資源在多線程程序設計中時常遇到,如:等待某一線程結束和某一個資源被釋放,等待緩沖區和設備準備好兩個資源;這種現實情況,可以分別為不同的資源設置系統對象,然后利用WaitForMultiObject進行等待。
            5、    怎樣等待多個資源中的一個(使用CSemaphore)
            現實中還可能出現如下情形:客人租相機的問題:有若干客人需要,租相機,總相機數為n,相機租完后,客人必須等待,只要有一個相機,則某客人就可以等到租借。還有許多問題可以用這種Producer/consumer模型加以概括。
            這種情形即是等待多個資源中的一個的情況,在Win32程序設計中則經常使用信號量(Semaphore)來解決此問題。
            Win32系統中,信號量具有以下特性:
            一 個信號量可以被鎖定N次,N一般代表可用資源的個數,上例中即可代表相機的個數,信號量初始化后,在Win32環境下調用一次Wait…操作即表示對其的 一次鎖定,信號量的值相應加1,操作完后,調用ReleaseSemaphore操作,即代表資源釋放(上述例子中就是歸還相機)。MFC對Win32信 號量的相關API函數進行了封裝(CSemaphore),配合CMultiLock 或者 CSingleLock即可實現鎖定和資源釋放的動作。
            七、    線程間的通信
            線程間的通信有許多方法可以實現,視場合不同也有不同的應用,大致可以分為兩類:進程內的線程通信和進程間的通信。關于進程內線程的通信,前面所述的各種同步互斥等待機制也可歸屬線程間通信的范疇,
            1、    使用線程消息實現線程通信
            2、    使用事件對象實現線程通信
            Win32還提供了一種比較靈活的核心對象,該對象完全受控于程序(只是清除的時候由系統回收),這就是Event(事件)對象。事件對象一般用于線程間的通知。下面先看事件對象的一些屬性:
            創建一個事件對象可以調用Win32 API函數完成,也可以使用MFC封裝的事件對象。其API原型為:
            HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,BOOL bManualReset,BOOL bInitialState, LPCTSTR lpName );
            第二個參數指示事件對象是否為手動修改狀態(手動修改需要顯式調用ResEvent函數);第三個參數設置事件對象的初態,true為激發態,false為非激發態。第四個參數為事件的名字。
            事件對象自從創建后即在程序的控制下處于激發態和非激發態之間翻轉。
            八、    線程代碼的調試
            九、    什么是線程安全的代碼
            十、    多線程程序設計的幾個原則

            posts - 94, comments - 138, trackbacks - 0, articles - 94

            Copyright © RichardHe

            国产成人精品白浆久久69| 波多野结衣久久一区二区 | 国产激情久久久久影院小草| 久久人做人爽一区二区三区| 久久久噜噜噜久久| 久久精品三级视频| 精品国产婷婷久久久| 国内精品久久久久久中文字幕| 亚洲狠狠久久综合一区77777| 久久青青草原精品影院| 狠狠狠色丁香婷婷综合久久俺| 精品久久一区二区三区| 国内精品久久久久| 久久久精品一区二区三区| 亚洲狠狠综合久久| 国内精品久久久久久中文字幕| 精品乱码久久久久久夜夜嗨| 久久精品18| 亚洲精品无码久久不卡| 久久精品国产亚洲αv忘忧草| 精品多毛少妇人妻AV免费久久| 亚洲国产精品无码久久| 久久夜色精品国产网站| 国产精品久久久久9999高清| 99久久精品国产一区二区蜜芽| 精品久久久久久国产三级| 亚洲综合久久夜AV | 综合人妻久久一区二区精品| 久久精品国产精品亚洲毛片| 国产精品久久久久…| 久久精品国产黑森林| 久久久久亚洲AV无码观看| 久久久噜噜噜久久中文福利| 色综合色天天久久婷婷基地| 久久久久久国产a免费观看不卡| 香蕉久久夜色精品国产尤物| 久久久久久国产精品无码超碰| 久久香蕉国产线看观看乱码| 人妻无码久久精品| 精品久久久久久亚洲精品 | 亚洲国产精品综合久久网络|