• <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>

            思勤無邪

            上學時,因我年齡最小,個頭也最小,上課時,就像大猩猩堆里的猴一般。如今,這猴偶爾也把最近的一些情況寫在這里。

               :: 首頁 :: 聯系 :: 聚合  :: 管理
              132 Posts :: 1 Stories :: 178 Comments :: 0 Trackbacks

            公告

                 吾日常三省吾身,曰思、曰勤、曰無邪。

            積分與排名

            • 積分 - 183659
            • 排名 - 141

            最新隨筆

            最新評論

            閱讀排行榜

            評論排行榜

            線程同步的方式有:
              臨界區
              管理事件內核對象
              信號量內核對象
              互斥內核對象
            分別介紹如下:

            使線程同步

              在程序中使用多線程時,一般很少有多個線程能在其生命期內進行完全獨立的操作。更多的情況是一些線程進行某些處理操作,而其他的線程必須對其處理結果進行了解。正常情況下對這種處理結果的了解應當在其處理任務完成后進行。

              如果不采取適當的措施,其他線程往往會在線程處理任務結束前就去訪問處理結果,這就很有可能得到有關處理結果的錯誤了解。例如,多個線程同時訪問同一個全局變量,如果都是讀取操作,則不會出現問題。如果一個線程負責改變此變量的值,而其他線程負責同時讀取變量內容,則不能保證讀取到的數據是經過寫線程修改后的。

              為了確保讀線程讀取到的是經過修改的變量,就必須在向變量寫入數據時禁止其他線程對其的任何訪問,直至賦值過程結束后再解除對其他線程的訪問限制。象這種保證線程能了解其他線程任務處理結束后的處理結果而采取的保護措施即為線程同步。

              線程同步是一個非常大的話題,包括方方面面的內容。從大的方面講,線程的同步可分用戶模式的線程同步和內核對象的線程同步兩大類。用戶模式中線程的同步方法主要有原子訪問和臨界區等方法。其特點是同步速度特別快,適合于對線程運行速度有嚴格要求的場合。

              內核對象的線程同步則主要由事件、等待定時器、信號量以及信號燈等內核對象構成。由于這種同步機制使用了內核對象,使用時必須將線程從用戶模式切換到內核模式,而這種轉換一般要耗費近千個CPU周期,因此同步速度較慢,但在適用性上卻要遠優于用戶模式的線程同步方式。

            臨界區

              臨界區(Critical Section)是一段獨占對某些共享資源訪問的代碼,在任意時刻只允許一個線程對共享資源進行訪問。如果有多個線程試圖同時訪問臨界區,那么在有一個線程進入后其他所有試圖訪問此臨界區的線程將被掛起,并一直持續到進入臨界區的線程離開。臨界區在被釋放后,其他線程可以繼續搶占,并以此達到用原子方式操作共享資源的目的。

              臨界區在使用時以CRITICAL_SECTION結構對象保護共享資源,并分別用EnterCriticalSection()和LeaveCriticalSection()函數去標識和釋放一個臨界區。所用到的CRITICAL_SECTION結構對象必須經過InitializeCriticalSection()的初始化后才能使用,而且必須確保所有線程中的任何試圖訪問此共享資源的代碼都處在此臨界區的保護之下。否則臨界區將不會起到應有的作用,共享資源依然有被破壞的可能。

            thread01.jpg
            圖1 使用臨界區保持線程同步

              下面通過一段代碼展示了臨界區在保護多線程訪問的共享資源中的作用。通過兩個線程來分別對全局變量g_cArray[10]進行寫入操作,用臨界區結構對象g_cs來保持線程的同步,并在開啟線程前對其進行初始化。為了使實驗效果更加明顯,體現出臨界區的作用,在線程函數對共享資源g_cArray[10]的寫入時,以Sleep()函數延遲1毫秒,使其他線程同其搶占CPU的可能性增大。如果不使用臨界區對其進行保護,則共享資源數據將被破壞(參見圖1(a)所示計算結果),而使用臨界區對線程保持同步后則可以得到正確的結果(參見圖1(b)所示計算結果)。代碼實現清單附下:

            // 臨界區結構對象
            CRITICAL_SECTION g_cs;
            // 共享資源
            char g_cArray[10];
            UINT ThreadProc10(LPVOID pParam)
            {
             // 進入臨界區
             EnterCriticalSection(&g_cs);
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[i] = 'a';
              Sleep(1);
             }
             // 離開臨界區
             LeaveCriticalSection(&g_cs);
             return 0;
            }
            UINT ThreadProc11(LPVOID pParam)
            {
             // 進入臨界區
             EnterCriticalSection(&g_cs);
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[10 - i - 1] = 'b';
              Sleep(1);
             }
             // 離開臨界區
             LeaveCriticalSection(&g_cs);
             return 0;
            }
            ……
            void CSample08View::OnCriticalSection()
            {
             // 初始化臨界區
             InitializeCriticalSection(&g_cs);
             // 啟動線程
             AfxBeginThread(ThreadProc10, NULL);
             AfxBeginThread(ThreadProc11, NULL);
             // 等待計算完畢
             Sleep(300);
             // 報告計算結果
             CString sResult = CString(g_cArray);
             AfxMessageBox(sResult);
            }

              在使用臨界區時,一般不允許其運行時間過長,只要進入臨界區的線程還沒有離開,其他所有試圖進入此臨界區的線程都會被掛起而進入到等待狀態,并會在一定程度上影響。程序的運行性能。尤其需要注意的是不要將等待用戶輸入或是其他一些外界干預的操作包含到臨界區。如果進入了臨界區卻一直沒有釋放,同樣也會引起其他線程的長時間等待。換句話說,在執行了EnterCriticalSection()語句進入臨界區后無論發生什么,必須確保與之匹配的LeaveCriticalSection()都能夠被執行到。可以通過添加結構化異常處理代碼來確保LeaveCriticalSection()語句的執行。雖然臨界區同步速度很快,但卻只能用來同步本進程內的線程,而不可用來同步多個進程中的線程。

              MFC為臨界區提供有一個CCriticalSection類,使用該類進行線程同步處理是非常簡單的,只需在線程函數中用CCriticalSection類成員函數Lock()和UnLock()標定出被保護代碼片段即可。對于上述代碼,可通過CCriticalSection類將其改寫如下:

            // MFC臨界區類對象
            CCriticalSection g_clsCriticalSection;
            // 共享資源
            char g_cArray[10];
            UINT ThreadProc20(LPVOID pParam)
            {
             // 進入臨界區
             g_clsCriticalSection.Lock();
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[i] = 'a';
              Sleep(1);
             }
             // 離開臨界區
             g_clsCriticalSection.Unlock();
             return 0;
            }
            UINT ThreadProc21(LPVOID pParam)
            {
             // 進入臨界區
             g_clsCriticalSection.Lock();
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[10 - i - 1] = 'b';
              Sleep(1);
             }
             // 離開臨界區
             g_clsCriticalSection.Unlock();
             return 0;
            }
            ……
            void CSample08View::OnCriticalSectionMfc()
            {
             // 啟動線程
             AfxBeginThread(ThreadProc20, NULL);
             AfxBeginThread(ThreadProc21, NULL);
             // 等待計算完畢
             Sleep(300);
             // 報告計算結果
             CString sResult = CString(g_cArray);
             AfxMessageBox(sResult);
            }

            管理事件內核對象

              在前面講述線程通信時曾使用過事件內核對象來進行線程間的通信,除此之外,事件內核對象也可以通過通知操作的方式來保持線程的同步。對于前面那段使用臨界區保持線程同步的代碼可用事件對象的線程同步方法改寫如下:

            // 事件句柄
            HANDLE hEvent = NULL;
            // 共享資源
            char g_cArray[10];
            ……
            UINT ThreadProc12(LPVOID pParam)
            {
             // 等待事件置位
             WaitForSingleObject(hEvent, INFINITE);
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[i] = 'a';
              Sleep(1);
             }
             // 處理完成后即將事件對象置位
             SetEvent(hEvent);
             return 0;
            }
            UINT ThreadProc13(LPVOID pParam)
            {
             // 等待事件置位
             WaitForSingleObject(hEvent, INFINITE);
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[10 - i - 1] = 'b';
              Sleep(1);
             }
             // 處理完成后即將事件對象置位
             SetEvent(hEvent);
             return 0;
            }
            ……
            void CSample08View::OnEvent()
            {
             // 創建事件
             hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
             // 事件置位
             SetEvent(hEvent);
             // 啟動線程
             AfxBeginThread(ThreadProc12, NULL);
             AfxBeginThread(ThreadProc13, NULL);
             // 等待計算完畢
             Sleep(300);
             // 報告計算結果
             CString sResult = CString(g_cArray);
             AfxMessageBox(sResult);
            }

              在創建線程前,首先創建一個可以自動復位的事件內核對象hEvent,而線程函數則通過WaitForSingleObject()等待函數無限等待hEvent的置位,只有在事件置位時WaitForSingleObject()才會返回,被保護的代碼將得以執行。對于以自動復位方式創建的事件對象,在其置位后一被WaitForSingleObject()等待到就會立即復位,也就是說在執行ThreadProc12()中的受保護代碼時,事件對象已經是復位狀態的,這時即使有ThreadProc13()對CPU的搶占,也會由于WaitForSingleObject()沒有hEvent的置位而不能繼續執行,也就沒有可能破壞受保護的共享資源。在ThreadProc12()中的處理完成后可以通過SetEvent()對hEvent的置位而允許ThreadProc13()對共享資源g_cArray的處理。這里SetEvent()所起的作用可以看作是對某項特定任務完成的通知。

              使用臨界區只能同步同一進程中的線程,而使用事件內核對象則可以對進程外的線程進行同步,其前提是得到對此事件對象的訪問權。可以通過OpenEvent()函數獲取得到,其函數原型為:

            HANDLE OpenEvent(
             DWORD dwDesiredAccess, // 訪問標志
             BOOL bInheritHandle, // 繼承標志
             LPCTSTR lpName // 指向事件對象名的指針
            );

              如果事件對象已創建(在創建事件時需要指定事件名),函數將返回指定事件的句柄。對于那些在創建事件時沒有指定事件名的事件內核對象,可以通過使用內核對象的繼承性或是調用DuplicateHandle()函數來調用CreateEvent()以獲得對指定事件對象的訪問權。在獲取到訪問權后所進行的同步操作與在同一個進程中所進行的線程同步操作是一樣的。

              如果需要在一個線程中等待多個事件,則用WaitForMultipleObjects()來等待。WaitForMultipleObjects()與WaitForSingleObject()類似,同時監視位于句柄數組中的所有句柄。這些被監視對象的句柄享有平等的優先權,任何一個句柄都不可能比其他句柄具有更高的優先權。WaitForMultipleObjects()的函數原型為:

            DWORD WaitForMultipleObjects(
             DWORD nCount, // 等待句柄數
             CONST HANDLE *lpHandles, // 句柄數組首地址
             BOOL fWaitAll, // 等待標志
             DWORD dwMilliseconds // 等待時間間隔
            );

              參數nCount指定了要等待的內核對象的數目,存放這些內核對象的數組由lpHandles來指向。fWaitAll對指定的這nCount個內核對象的兩種等待方式進行了指定,為TRUE時當所有對象都被通知時函數才會返回,為FALSE則只要其中任何一個得到通知就可以返回。dwMilliseconds在這里的作用與在WaitForSingleObject()中的作用是完全一致的。如果等待超時,函數將返回WAIT_TIMEOUT。如果返回WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1中的某個值,則說明所有指定對象的狀態均為已通知狀態(當fWaitAll為TRUE時)或是用以減去WAIT_OBJECT_0而得到發生通知的對象的索引(當fWaitAll為FALSE時)。如果返回值在WAIT_ABANDONED_0與WAIT_ABANDONED_0+nCount-1之間,則表示所有指定對象的狀態均為已通知,且其中至少有一個對象是被丟棄的互斥對象(當fWaitAll為TRUE時),或是用以減去WAIT_OBJECT_0表示一個等待正常結束的互斥對象的索引(當fWaitAll為FALSE時)。 下面給出的代碼主要展示了對WaitForMultipleObjects()函數的使用。通過對兩個事件內核對象的等待來控制線程任務的執行與中途退出:

            // 存放事件句柄的數組
            HANDLE hEvents[2];
            UINT ThreadProc14(LPVOID pParam)
            {
             // 等待開啟事件
             DWORD dwRet1 = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE);
             // 如果開啟事件到達則線程開始執行任務
             if (dwRet1 == WAIT_OBJECT_0)
             {
              AfxMessageBox("線程開始工作!");
              while (true)
              {
               for (int i = 0; i < 10000; i++);
               // 在任務處理過程中等待結束事件
               DWORD dwRet2 = WaitForMultipleObjects(2, hEvents, FALSE, 0);
               // 如果結束事件置位則立即終止任務的執行
               if (dwRet2 == WAIT_OBJECT_0 + 1)
                break;
              }
             }
             AfxMessageBox("線程退出!");
             return 0;
            }
            ……
            void CSample08View::OnStartEvent()
            {
             // 創建線程
             for (int i = 0; i < 2; i++)
              hEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
              // 開啟線程
              AfxBeginThread(ThreadProc14, NULL);
              // 設置事件0(開啟事件)
              SetEvent(hEvents[0]);
            }
            void CSample08View::OnEndevent()
            {
             // 設置事件1(結束事件)
             SetEvent(hEvents[1]);
            }

              MFC為事件相關處理也提供了一個CEvent類,共包含有除構造函數外的4個成員函數PulseEvent()、ResetEvent()、SetEvent()和UnLock()。在功能上分別相當與Win32 API的PulseEvent()、ResetEvent()、SetEvent()和CloseHandle()等函數。而構造函數則履行了原CreateEvent()函數創建事件對象的職責,其函數原型為:

            CEvent(BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );

              按照此缺省設置將創建一個自動復位、初始狀態為復位狀態的沒有名字的事件對象。封裝后的CEvent類使用起來更加方便,圖2即展示了CEvent類對A、B兩線程的同步過程:

            Thread02.jpg
            圖2 CEvent類對線程的同步過程示意

              B線程在執行到CEvent類成員函數Lock()時將會發生阻塞,而A線程此時則可以在沒有B線程干擾的情況下對共享資源進行處理,并在處理完成后通過成員函數SetEvent()向B發出事件,使其被釋放,得以對A先前已處理完畢的共享資源進行操作。可見,使用CEvent類對線程的同步方法與通過API函數進行線程同步的處理方法是基本一致的。前面的API處理代碼可用CEvent類將其改寫為:

            // MFC事件類對象
            CEvent g_clsEvent;
            UINT ThreadProc22(LPVOID pParam)
            {
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[i] = 'a';
              Sleep(1);
             }
             // 事件置位
             g_clsEvent.SetEvent();
             return 0;
            }
            UINT ThreadProc23(LPVOID pParam)
            {
             // 等待事件
             g_clsEvent.Lock();
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[10 - i - 1] = 'b';
              Sleep(1);
             }
             return 0;
            }
            ……
            void CSample08View::OnEventMfc()
            {
             // 啟動線程
             AfxBeginThread(ThreadProc22, NULL);
             AfxBeginThread(ThreadProc23, NULL);
             // 等待計算完畢
             Sleep(300);
             // 報告計算結果
             CString sResult = CString(g_cArray);
             AfxMessageBox(sResult);
            }

            信號量內核對象

              信號量(Semaphore)內核對象對線程的同步方式與前面幾種方法不同,它允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數目。在用CreateSemaphore()創建信號量時即要同時指出允許的最大資源計數和當前可用資源計數。一般是將當前可用資源計數設置為最大資源計數,每增加一個線程對共享資源的訪問,當前可用資源計數就會減1,只要當前可用資源計數是大于0的,就可以發出信號量信號。但是當前可用計數減小到0時則說明當前占用資源的線程數已經達到了所允許的最大數目,不能在允許其他線程的進入,此時的信號量信號將無法發出。線程在處理完共享資源后,應在離開的同時通過ReleaseSemaphore()函數將當前可用資源計數加1。在任何時候當前可用資源計數決不可能大于最大資源計數。

            thread03.jpg
            圖3 使用信號量對象控制資源

              下面結合圖例3來演示信號量對象對資源的控制。在圖3中,以箭頭和白色箭頭表示共享資源所允許的最大資源計數和當前可用資源計數。初始如圖(a)所示,最大資源計數和當前可用資源計數均為4,此后每增加一個對資源進行訪問的線程(用黑色箭頭表示)當前資源計數就會相應減1,圖(b)即表示的在3個線程對共享資源進行訪問時的狀態。當進入線程數達到4個時,將如圖(c)所示,此時已達到最大資源計數,而當前可用資源計數也已減到0,其他線程無法對共享資源進行訪問。在當前占有資源的線程處理完畢而退出后,將會釋放出空間,圖(d)已有兩個線程退出對資源的占有,當前可用計數為2,可以再允許2個線程進入到對資源的處理。可以看出,信號量是通過計數來對線程訪問資源進行控制的,而實際上信號量確實也被稱作Dijkstra計數器。

              使用信號量內核對象進行線程同步主要會用到CreateSemaphore()、OpenSemaphore()、ReleaseSemaphore()、WaitForSingleObject()和WaitForMultipleObjects()等函數。其中,CreateSemaphore()用來創建一個信號量內核對象,其函數原型為:

            HANDLE CreateSemaphore(
             LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全屬性指針
             LONG lInitialCount, // 初始計數
             LONG lMaximumCount, // 最大計數
             LPCTSTR lpName // 對象名指針
            );

              參數lMaximumCount是一個有符號32位值,定義了允許的最大資源計數,最大取值不能超過4294967295。lpName參數可以為創建的信號量定義一個名字,由于其創建的是一個內核對象,因此在其他進程中可以通過該名字而得到此信號量。OpenSemaphore()函數即可用來根據信號量名打開在其他進程中創建的信號量,函數原型如下:

            HANDLE OpenSemaphore(
             DWORD dwDesiredAccess, // 訪問標志
             BOOL bInheritHandle, // 繼承標志
             LPCTSTR lpName // 信號量名
            );

              在線程離開對共享資源的處理時,必須通過ReleaseSemaphore()來增加當前可用資源計數。否則將會出現當前正在處理共享資源的實際線程數并沒有達到要限制的數值,而其他線程卻因為當前可用資源計數為0而仍無法進入的情況。ReleaseSemaphore()的函數原型為:

            BOOL ReleaseSemaphore(
             HANDLE hSemaphore, // 信號量句柄
             LONG lReleaseCount, // 計數遞增數量
             LPLONG lpPreviousCount // 先前計數
            );

              該函數將lReleaseCount中的值添加給信號量的當前資源計數,一般將lReleaseCount設置為1,如果需要也可以設置其他的值。WaitForSingleObject()和WaitForMultipleObjects()主要用在試圖進入共享資源的線程函數入口處,主要用來判斷信號量的當前可用資源計數是否允許本線程的進入。只有在當前可用資源計數值大于0時,被監視的信號量內核對象才會得到通知。

              信號量的使用特點使其更適用于對Socket(套接字)程序中線程的同步。例如,網絡上的HTTP服務器要對同一時間內訪問同一頁面的用戶數加以限制,這時可以為沒一個用戶對服務器的頁面請求設置一個線程,而頁面則是待保護的共享資源,通過使用信號量對線程的同步作用可以確保在任一時刻無論有多少用戶對某一頁面進行訪問,只有不大于設定的最大用戶數目的線程能夠進行訪問,而其他的訪問企圖則被掛起,只有在有用戶退出對此頁面的訪問后才有可能進入。下面給出的示例代碼即展示了類似的處理過程:

            // 信號量對象句柄
            HANDLE hSemaphore;
            UINT ThreadProc15(LPVOID pParam)
            {
             // 試圖進入信號量關口
             WaitForSingleObject(hSemaphore, INFINITE);
             // 線程任務處理
             AfxMessageBox("線程一正在執行!");
             // 釋放信號量計數
             ReleaseSemaphore(hSemaphore, 1, NULL);
             return 0;
            }
            UINT ThreadProc16(LPVOID pParam)
            {
             // 試圖進入信號量關口
             WaitForSingleObject(hSemaphore, INFINITE);
             // 線程任務處理
             AfxMessageBox("線程二正在執行!");
             // 釋放信號量計數
             ReleaseSemaphore(hSemaphore, 1, NULL);
             return 0;
            }
            UINT ThreadProc17(LPVOID pParam)
            {
             // 試圖進入信號量關口
             WaitForSingleObject(hSemaphore, INFINITE);
             // 線程任務處理
             AfxMessageBox("線程三正在執行!");
             // 釋放信號量計數
             ReleaseSemaphore(hSemaphore, 1, NULL);
             return 0;
            }
            ……
            void CSample08View::OnSemaphore()
            {
             // 創建信號量對象
             hSemaphore = CreateSemaphore(NULL, 2, 2, NULL);
             // 開啟線程
             AfxBeginThread(ThreadProc15, NULL);
             AfxBeginThread(ThreadProc16, NULL);
             AfxBeginThread(ThreadProc17, NULL);
            }

            thread04.jpg
            圖4 開始進入的兩個線程

            thread05.jpg
            圖5 線程二退出后線程三才得以進入

              上述代碼在開啟線程前首先創建了一個初始計數和最大資源計數均為2的信號量對象hSemaphore。即在同一時刻只允許2個線程進入由hSemaphore保護的共享資源。隨后開啟的三個線程均試圖訪問此共享資源,在前兩個線程試圖訪問共享資源時,由于hSemaphore的當前可用資源計數分別為2和1,此時的hSemaphore是可以得到通知的,也就是說位于線程入口處的WaitForSingleObject()將立即返回,而在前兩個線程進入到保護區域后,hSemaphore的當前資源計數減少到0,hSemaphore將不再得到通知,WaitForSingleObject()將線程掛起。直到此前進入到保護區的線程退出后才能得以進入。圖4和圖5為上述代脈的運行結果。從實驗結果可以看出,信號量始終保持了同一時刻不超過2個線程的進入。

              在MFC中,通過CSemaphore類對信號量作了表述。該類只具有一個構造函數,可以構造一個信號量對象,并對初始資源計數、最大資源計數、對象名和安全屬性等進行初始化,其原型如下:

            CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );

              在構造了CSemaphore類對象后,任何一個訪問受保護共享資源的線程都必須通過CSemaphore從父類CSyncObject類繼承得到的Lock()和UnLock()成員函數來訪問或釋放CSemaphore對象。與前面介紹的幾種通過MFC類保持線程同步的方法類似,通過CSemaphore類也可以將前面的線程同步代碼進行改寫,這兩種使用信號量的線程同步方法無論是在實現原理上還是從實現結果上都是完全一致的。下面給出經MFC改寫后的信號量線程同步代碼:

            // MFC信號量類對象
            CSemaphore g_clsSemaphore(2, 2);
            UINT ThreadProc24(LPVOID pParam)
            {
             // 試圖進入信號量關口
             g_clsSemaphore.Lock();
             // 線程任務處理
             AfxMessageBox("線程一正在執行!");
             // 釋放信號量計數
             g_clsSemaphore.Unlock();
             return 0;
            }
            UINT ThreadProc25(LPVOID pParam)
            {
             // 試圖進入信號量關口
             g_clsSemaphore.Lock();
             // 線程任務處理
             AfxMessageBox("線程二正在執行!");
             // 釋放信號量計數
             g_clsSemaphore.Unlock();
             return 0;
            }
            UINT ThreadProc26(LPVOID pParam)
            {
             // 試圖進入信號量關口
             g_clsSemaphore.Lock();
             // 線程任務處理
             AfxMessageBox("線程三正在執行!");
             // 釋放信號量計數
             g_clsSemaphore.Unlock();
             return 0;
            }
            ……
            void CSample08View::OnSemaphoreMfc()
            {
             // 開啟線程
             AfxBeginThread(ThreadProc24, NULL);
             AfxBeginThread(ThreadProc25, NULL);
             AfxBeginThread(ThreadProc26, NULL);
            }


            互斥內核對象

              互斥(Mutex)是一種用途非常廣泛的內核對象。能夠保證多個線程對同一共享資源的互斥訪問。同臨界區有些類似,只有擁有互斥對象的線程才具有訪問資源的權限,由于互斥對象只有一個,因此就決定了任何情況下此共享資源都不會同時被多個線程所訪問。當前占據資源的線程在任務處理完后應將擁有的互斥對象交出,以便其他線程在獲得后得以訪問資源。與其他幾種內核對象不同,互斥對象在操作系統中擁有特殊代碼,并由操作系統來管理,操作系統甚至還允許其進行一些其他內核對象所不能進行的非常規操作。為便于理解,可參照圖6給出的互斥內核對象的工作模型:

            thread06.jpg
            圖6 使用互斥內核對象對共享資源的保護

              圖(a)中的箭頭為要訪問資源(矩形框)的線程,但只有第二個線程擁有互斥對象(黑點)并得以進入到共享資源,而其他線程則會被排斥在外(如圖(b)所示)。當此線程處理完共享資源并準備離開此區域時將把其所擁有的互斥對象交出(如圖(c)所示),其他任何一個試圖訪問此資源的線程都有機會得到此互斥對象。

              以互斥內核對象來保持線程同步可能用到的函數主要有CreateMutex()、OpenMutex()、ReleaseMutex()、WaitForSingleObject()和WaitForMultipleObjects()等。在使用互斥對象前,首先要通過CreateMutex()或OpenMutex()創建或打開一個互斥對象。CreateMutex()函數原型為:

            HANDLE CreateMutex(
             LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全屬性指針
             BOOL bInitialOwner, // 初始擁有者
             LPCTSTR lpName // 互斥對象名
            );

              參數bInitialOwner主要用來控制互斥對象的初始狀態。一般多將其設置為FALSE,以表明互斥對象在創建時并沒有為任何線程所占有。如果在創建互斥對象時指定了對象名,那么可以在本進程其他地方或是在其他進程通過OpenMutex()函數得到此互斥對象的句柄。OpenMutex()函數原型為:

            HANDLE OpenMutex(
             DWORD dwDesiredAccess, // 訪問標志
             BOOL bInheritHandle, // 繼承標志
             LPCTSTR lpName // 互斥對象名
            );

              當目前對資源具有訪問權的線程不再需要訪問此資源而要離開時,必須通過ReleaseMutex()函數來釋放其擁有的互斥對象,其函數原型為:

            BOOL ReleaseMutex(HANDLE hMutex);

              其唯一的參數hMutex為待釋放的互斥對象句柄。至于WaitForSingleObject()和WaitForMultipleObjects()等待函數在互斥對象保持線程同步中所起的作用與在其他內核對象中的作用是基本一致的,也是等待互斥內核對象的通知。但是這里需要特別指出的是:在互斥對象通知引起調用等待函數返回時,等待函數的返回值不再是通常的WAIT_OBJECT_0(對于WaitForSingleObject()函數)或是在WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1之間的一個值(對于WaitForMultipleObjects()函數),而是將返回一個WAIT_ABANDONED_0(對于WaitForSingleObject()函數)或是在WAIT_ABANDONED_0到WAIT_ABANDONED_0+nCount-1之間的一個值(對于WaitForMultipleObjects()函數)。以此來表明線程正在等待的互斥對象由另外一個線程所擁有,而此線程卻在使用完共享資源前就已經終止。除此之外,使用互斥對象的方法在等待線程的可調度性上同使用其他幾種內核對象的方法也有所不同,其他內核對象在沒有得到通知時,受調用等待函數的作用,線程將會掛起,同時失去可調度性,而使用互斥的方法卻可以在等待的同時仍具有可調度性,這也正是互斥對象所能完成的非常規操作之一。

              在編寫程序時,互斥對象多用在對那些為多個線程所訪問的內存塊的保護上,可以確保任何線程在處理此內存塊時都對其擁有可靠的獨占訪問權。下面給出的示例代碼即通過互斥內核對象hMutex對共享內存快g_cArray[]進行線程的獨占訪問保護。下面給出實現代碼清單:

            // 互斥對象
            HANDLE hMutex = NULL;
            char g_cArray[10];
            UINT ThreadProc18(LPVOID pParam)
            {
             // 等待互斥對象通知
             WaitForSingleObject(hMutex, INFINITE);
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[i] = 'a';
              Sleep(1);
             }
             // 釋放互斥對象
             ReleaseMutex(hMutex);
             return 0;
            }
            UINT ThreadProc19(LPVOID pParam)
            {
             // 等待互斥對象通知
             WaitForSingleObject(hMutex, INFINITE);
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[10 - i - 1] = 'b';
              Sleep(1);
             }
             // 釋放互斥對象
             ReleaseMutex(hMutex);
             return 0;
            }
            ……
            void CSample08View::OnMutex()
            {
             // 創建互斥對象
             hMutex = CreateMutex(NULL, FALSE, NULL);
             // 啟動線程
             AfxBeginThread(ThreadProc18, NULL);
             AfxBeginThread(ThreadProc19, NULL);
             // 等待計算完畢
             Sleep(300);
             // 報告計算結果
             CString sResult = CString(g_cArray);
             AfxMessageBox(sResult);
            }

              互斥對象在MFC中通過CMutex類進行表述。使用CMutex類的方法非常簡單,在構造CMutex類對象的同時可以指明待查詢的互斥對象的名字,在構造函數返回后即可訪問此互斥變量。CMutex類也是只含有構造函數這唯一的成員函數,當完成對互斥對象保護資源的訪問后,可通過調用從父類CSyncObject繼承的UnLock()函數完成對互斥對象的釋放。CMutex類構造函數原型為:

            CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );

              該類的適用范圍和實現原理與API方式創建的互斥內核對象是完全類似的,但要簡潔的多,下面給出就是對前面的示例代碼經CMutex類改寫后的程序實現清單:

            // MFC互斥類對象
            CMutex g_clsMutex(FALSE, NULL);
            UINT ThreadProc27(LPVOID pParam)
            {
             // 等待互斥對象通知
             g_clsMutex.Lock();
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[i] = 'a';
              Sleep(1);
             }
             // 釋放互斥對象
             g_clsMutex.Unlock();
             return 0;
            }
            UINT ThreadProc28(LPVOID pParam)
            {
             // 等待互斥對象通知
             g_clsMutex.Lock();
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[10 - i - 1] = 'b';
              Sleep(1);
             }
             // 釋放互斥對象
             g_clsMutex.Unlock();
             return 0;
            }
            ……
            void CSample08View::OnMutexMfc()
            {
             // 啟動線程
             AfxBeginThread(ThreadProc27, NULL);
             AfxBeginThread(ThreadProc28, NULL);
             // 等待計算完畢
             Sleep(300);
             // 報告計算結果
             CString sResult = CString(g_cArray);
             AfxMessageBox(sResult);
            }

              小結

              線程的使用使程序處理更夠更加靈活,而這種靈活同樣也會帶來各種不確定性的可能。尤其是在多個線程對同一公共變量進行訪問時。雖然未使用線程同步的程序代碼在邏輯上或許沒有什么問題,但為了確保程序的正確、可靠運行,必須在適當的場合采取線程同步措施。
            posted on 2006-10-10 16:14 思勤無邪 閱讀(5353) 評論(2)  編輯 收藏 引用 所屬分類: C++

            Feedback

            # re: Visual C++線程同步技術 2008-10-20 16:56 過路人
            不錯,很清晰,很詳細,謝謝了。  回復  更多評論
              

            # re: Visual C++線程同步技術 2011-12-21 15:00 jemmyLiu
            總結的好 正需要 謝謝啦  回復  更多評論
              

            无码人妻久久一区二区三区 | 久久夜色精品国产亚洲| 久久久久成人精品无码中文字幕 | 久久中文精品无码中文字幕| 国产一区二区精品久久岳| 青青青青久久精品国产h久久精品五福影院1421 | 久久九九亚洲精品| 午夜精品久久久久久影视riav| 久久精品一区二区三区AV| 久久久久国产精品| 久久婷婷五月综合国产尤物app| 亚洲AV日韩精品久久久久| 国产精品日韩深夜福利久久| 波多野结衣AV无码久久一区| 欧美精品一本久久男人的天堂| 色综合合久久天天给综看| 久久精品这里热有精品| 国内精品久久久久久久久电影网| 国产精品久久久久乳精品爆| 久久国产精品一国产精品金尊| 一本一本久久a久久精品综合麻豆| 久久99精品久久久久久hb无码 | 欧美精品福利视频一区二区三区久久久精品| 久久亚洲AV无码精品色午夜麻豆| 精品无码久久久久久国产| 72种姿势欧美久久久久大黄蕉| 久久久久亚洲AV无码观看| 久久久久久亚洲精品无码| 亚洲嫩草影院久久精品| 久久精品aⅴ无码中文字字幕不卡| 久久亚洲精品国产亚洲老地址 | 97久久国产综合精品女不卡 | 久久精品九九亚洲精品| 久久久久久精品久久久久| 午夜福利91久久福利| 欧美久久天天综合香蕉伊| 精品久久久久久无码中文字幕| 色综合久久精品中文字幕首页 | 热久久国产欧美一区二区精品| 国产午夜福利精品久久| 亚洲国产精品一区二区久久|