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

            Life is Good.

            Enhance Tech and English
            隨筆 - 65, 文章 - 20, 評(píng)論 - 21, 引用 - 0
            數(shù)據(jù)加載中……

            【轉(zhuǎn)】C++多線程入門(二)

            第三節(jié) 線程互斥


            本節(jié)介紹如下內(nèi)容

            1.    主動(dòng)對(duì)象

            2.    調(diào)度與原子操作

            3.    競(jìng)爭(zhēng)條件和數(shù)據(jù)一致性

            4.    為何需要互斥

            5.    互斥類接口定義

            6.    示例程序

            7.    互斥類的Unix和Windows實(shí)現(xiàn)

            1.     主動(dòng)對(duì)象(Active Object)

            第二節(jié)介紹Thread類的時(shí)候曾經(jīng)提到,每個(gè)子線程一旦被創(chuàng)建,就被賦予了自己的生命。當(dāng)一個(gè)線程類創(chuàng)建并啟動(dòng)之后,它將會(huì)以自己的步調(diào)主動(dòng)執(zhí)行其獨(dú)立線程,它和其他線程(包括主線程)的執(zhí)行是并行關(guān)系。

            為了詳細(xì)說(shuō)明這個(gè)問(wèn)題,先介紹一下什么是控制流(control flow)。計(jì)算機(jī)科學(xué)中,控制流指指令式(imperative)或函數(shù)式(functional)編程語(yǔ)言中語(yǔ)句、指令、函
            數(shù)調(diào)用的執(zhí)行(或求值)序列。

            單線程程序中,控制流始終是一線串珠,一旦控制流達(dá)到某個(gè)對(duì)象,由程序主動(dòng)調(diào)用對(duì)象函數(shù),在函數(shù)執(zhí)行完畢后,控制流返回主程序繼續(xù)執(zhí)行。對(duì)于被訪問(wèn)對(duì)象來(lái) 說(shuō),訪問(wèn)、修改、成員函數(shù)調(diào)用都是一個(gè)被動(dòng)的過(guò)程,此過(guò)程受控于該程序的控制流。所以,稱單線程程序的對(duì)象為被動(dòng)對(duì)象(Passive Object)。

            與被動(dòng)對(duì)象相對(duì)應(yīng)的是主動(dòng)對(duì)象。主動(dòng)對(duì)象和被動(dòng)對(duì)象的區(qū)別在于,主動(dòng)對(duì)象可以發(fā)起自己的線程,創(chuàng)建新的執(zhí)行序列,產(chǎn)生獨(dú)立于主線程的控制流分支。簡(jiǎn)而言 之,主動(dòng)對(duì)象可以獨(dú)立于主線程,自主制定自己的控制流。如果愿意,你可以實(shí)現(xiàn)一個(gè)和主線程沒(méi)有任何協(xié)調(diào)關(guān)系,天馬行空式的獨(dú)立線程。當(dāng)然,這樣的線程往往 也意味著毫無(wú)用處(娛樂(lè)作用除外)。

            2.     調(diào)度和原子操作

            從理論模型上說(shuō),多線程環(huán)境中,線程之間是獨(dú)立、對(duì)等關(guān)系。一個(gè)線程完全可以無(wú)視其他線程的存在。但實(shí)際多線程執(zhí)行環(huán)境中,線程數(shù)往往遠(yuǎn)大于CPU數(shù)量。 為了共享和分配資源,線程必需遵守一定規(guī)則,進(jìn)行必要的協(xié)調(diào)。操作系統(tǒng)中具體的規(guī)則執(zhí)行者是調(diào)度程序。因此,要想掌握多線程編程,就必需了解線程調(diào)度的基 本概念和特點(diǎn)。在此基礎(chǔ)上,能了解為什么需要在線程之間進(jìn)行協(xié)調(diào),進(jìn)而才能透徹理解如何協(xié)調(diào)多線程的并發(fā)執(zhí)行。

            現(xiàn)代操作系統(tǒng)中,存在許多不同的線程調(diào)度模型,這些調(diào)度模型的基本共同點(diǎn)就是線程執(zhí)行順序具有隨機(jī)性和不確定性。調(diào)度模型既無(wú)法事先知道某一時(shí)刻會(huì)存在多 少線程,也無(wú)法知道哪個(gè)線程會(huì)正在運(yùn)行,甚至也不知道某個(gè)線程確切會(huì)到什么時(shí)刻執(zhí)行結(jié)束,更不用說(shuō)預(yù)先安排在特定時(shí)刻執(zhí)行特定線程的特定語(yǔ)句。

            前面提到,控制流就是語(yǔ)句,指令或函數(shù)調(diào)用的順序序列。反映到時(shí)間軸上,就是一系列離散分布的執(zhí)行序列。線程調(diào)度作用于多個(gè)控制流的客觀效果,就是多個(gè)控 制流按同一條時(shí)間軸排列時(shí),是一個(gè)近似隨機(jī)的執(zhí)行序列;按每個(gè)控制流各自的時(shí)間軸排列時(shí),則是一個(gè)具有先后順序的執(zhí)行序列。

            在具體的程序執(zhí)行上,這個(gè)特點(diǎn)就表現(xiàn)為線程A的一個(gè)語(yǔ)句執(zhí)行完畢后,緊接著執(zhí)行的可能是另一個(gè)線程B的一個(gè)語(yǔ)句。甚至可能是線程A的一個(gè)語(yǔ)句還沒(méi)有執(zhí)行完畢,就接著執(zhí)行線程B的語(yǔ)句。

            對(duì)于后一點(diǎn)不用感到奇怪。因?yàn)椴僮飨到y(tǒng)中,調(diào)度程序作為系統(tǒng)底層實(shí)現(xiàn)的一部分,參與調(diào)度的操作指令可能比高級(jí)編程語(yǔ)言的基本語(yǔ)句要底層的多。同一個(gè)調(diào)度程 序,其調(diào)度的眾多線程可能由多種高級(jí)語(yǔ)言編寫,所以調(diào)度程序基本上不可能以某種高級(jí)編程語(yǔ)言的單條語(yǔ)句為單位安排執(zhí)行序列。

            通常,一條高級(jí)語(yǔ)言語(yǔ)句可能對(duì)應(yīng)多條匯編指令,而一條匯編指令可能對(duì)應(yīng)多條CPU微碼指令。而一條微碼指令,則可能對(duì)應(yīng)邏輯電路的一個(gè)具體電路邏輯。順便 提一下,這也是Verilog, VHDL等高級(jí)語(yǔ)言能夠綜合出具體的邏輯電路的基本原理。所不同的是,高級(jí)語(yǔ)言編譯器編譯的最終單位是匯編,而硬件描述語(yǔ)言綜合的最終單位是和微碼對(duì)應(yīng)的 電路器件單元(集成電路前端設(shè)計(jì)的內(nèi)容。記得以前暑期實(shí)習(xí),我窩在學(xué)校做的就是這么一個(gè)元件庫(kù),做的很不像樣子居然還得到了一定的肯定,多年后想起來(lái)還是 汗顏)。

            至于系統(tǒng)調(diào)度程序具體會(huì)定義怎樣的原子操作集合,會(huì)以什么粒度的指令為調(diào)度基本單位,這就是系統(tǒng)設(shè)計(jì)者各顯神通的地方了。個(gè)人認(rèn)為,作為高級(jí)語(yǔ)言的編程 者,記住什么操作是原子操作意義并不明顯。大多數(shù)場(chǎng)合,只要認(rèn)為,多線程的控制流在同一條時(shí)間軸上看來(lái)是完全隨機(jī)的就可以了。要記住,墨菲定律生效的時(shí) 候,看似不可能的事情都能成為現(xiàn)實(shí)。

            多線程程序設(shè)計(jì)中,一種樸素的設(shè)計(jì)思想是將線程具體化為主動(dòng)對(duì)象,主動(dòng)對(duì)象之間通過(guò)共享環(huán)境(全局資源)來(lái)維護(hù)當(dāng)前應(yīng)用的運(yùn)行狀態(tài);主動(dòng)對(duì)象間通過(guò)一套約 定的互斥和同步規(guī)則來(lái)實(shí)現(xiàn)通信;線程的具體執(zhí)行時(shí)序則由調(diào)度程序安排。主動(dòng)對(duì)象之間,應(yīng)盡量避免一個(gè)主動(dòng)對(duì)象直接調(diào)用另一個(gè)主動(dòng)對(duì)象的內(nèi)部方法(例如 suspend, stop, exit)直接控制另一個(gè)線程的執(zhí)行步調(diào)。主動(dòng)對(duì)象的行為應(yīng)該完全由其本身控制,和其他主動(dòng)對(duì)象的交互盡量只通過(guò)環(huán)境以及同步語(yǔ)句來(lái)實(shí)現(xiàn)。

            實(shí)際實(shí)現(xiàn)中,如果多個(gè)主動(dòng)對(duì)象都可以直接調(diào)用某個(gè)主動(dòng)對(duì)象的stop方法終止其運(yùn)行,這個(gè)程序是非常脆弱的,因?yàn)樵谡{(diào)度程序之外,不借助同步機(jī)制,主觀假 定線程執(zhí)行時(shí)序,人為更改線程執(zhí)行狀態(tài)是非常不明智的。即使這樣的程序可用,對(duì)于維護(hù)者來(lái)說(shuō)其難度也不亞于維護(hù)goto語(yǔ)句。

            這也就是前一篇文章所定義線程類中detach, start, stop沒(méi)有加鎖的原因,因?yàn)椴⒉幌M诙鄠€(gè)線程中調(diào)用同一個(gè)線程對(duì)象的這些方法。

            3.     競(jìng)爭(zhēng)條件和數(shù)據(jù)一致性

            共享環(huán)境是進(jìn)程全局資源的同義詞。在多線程并發(fā)執(zhí)行中,最常遇到的問(wèn)題就是共享環(huán)境被污染。具體體現(xiàn)就是全局?jǐn)?shù)據(jù)被破壞,全局文件內(nèi)容被破壞 … 。

            例如:
            有一個(gè)64位全局變量long globalVar = 0;主動(dòng)對(duì)象A想把它置為0x000A000B000C000D;假設(shè)這個(gè)操作分兩步執(zhí)行,先將高32位置為000A000B,再把低32位置為 000C000D。但不巧的是,對(duì)象A剛剛將高位置位,調(diào)度程序安排另一主動(dòng)對(duì)象B執(zhí)行。這時(shí),全局變量globalVar內(nèi)部的值是一個(gè)非法值,它是無(wú) 意義的。在B拿著這個(gè)值做進(jìn)一步處理時(shí),得出的也是錯(cuò)誤結(jié)果。這時(shí),稱為數(shù)據(jù)的一致性被破壞。

            線程調(diào)度的不確定性也可能導(dǎo)致程序執(zhí)行結(jié)果的不確定性。有時(shí)候這種不確定性會(huì)導(dǎo)致程序得到錯(cuò)誤的運(yùn)行結(jié)果。
            例如:
            為了對(duì)Thread類所生成的對(duì)象總數(shù)計(jì)數(shù),定義一個(gè)全局變量Unsigned int counter = 0; 在Thread類的構(gòu)造函數(shù)中,執(zhí)行++counter。現(xiàn)在假設(shè)有2個(gè)Thread對(duì)象objA和objB并發(fā)運(yùn)行,考慮如下兩個(gè)場(chǎng)景:
            Scenario1.操作序列如下:
            1.      counter = 0;
            2.      objA將counter值0從內(nèi)存讀入寄存器A;
            3.      objA將寄存器A值加1;
            4.      objA將寄存器A值寫回內(nèi)存,counter值為1;
            5.      objB將counter值1從內(nèi)存讀入寄存器B;
            6.      objB將寄存器B值加1;
            7.      objA將寄存器B值寫回內(nèi)存,counter值為2;
            8.      最終counter值為2。
            Scenario2.操作序列如下:
            1.      counter = 0;
            2.      objA將counter值0從內(nèi)存讀入寄存器A;
            3.      objB將counter值0從內(nèi)存讀入寄存器B
            4.      objA將寄存器A值加1;
            5.      objB將寄存器B值加1;
            6.      objA將寄存器A值寫回內(nèi)存,counter值為1;
            7.      objA將寄存器B值寫回內(nèi)存,counter值為1;
            8.      最終counter值為1。
            場(chǎng)景1的結(jié)果是設(shè)計(jì)的本意,場(chǎng)景2的結(jié)果對(duì)我們而言是錯(cuò)誤的。

            一個(gè)線程的執(zhí)行結(jié)果正確性取決于于其他線程執(zhí)行時(shí)序的條件,稱為競(jìng)爭(zhēng)條件。中文這樣翻譯不倫不類,但從英文字面理解非常容易。Race一般是計(jì)時(shí)賽,某位選手跑得快一點(diǎn),競(jìng)賽結(jié)果就有變化,最終結(jié)果由race condition決定,還是非常形象的。

            4.     為何需要互斥

            線程間協(xié)作問(wèn)題,通常可以歸為互斥和同步兩類。其中互斥又主要解決兩類問(wèn)題:維護(hù)數(shù)據(jù)一致性、避免競(jìng)爭(zhēng)條件的出現(xiàn)。

            解決一致性問(wèn)題,通俗說(shuō)就是,修改數(shù)據(jù)的線程通告其他線程“我正在修改你要訪問(wèn)的對(duì)象X,操作過(guò)程中不能保證這個(gè)數(shù)據(jù)的有效性,請(qǐng)不要使用此對(duì)象”。

            避免競(jìng)爭(zhēng)條件,通俗說(shuō)就是,某線程通告其他線程“我將要進(jìn)行涉及某對(duì)象的一系列操作A,在我未完成這一系列操作之前,如果有人和我同時(shí)執(zhí)行涉及此對(duì)象的操作序列B(B也可能就是A),將會(huì)影響我執(zhí)行結(jié)果的正確性,請(qǐng)不要進(jìn)行涉及此對(duì)象的操作”。

            這種操作序列A有時(shí)候也被稱為“原子性操作”,因?yàn)樗辉试S操作序列B在時(shí)間軸上和它交叉分布,必需保證在時(shí)間軸上看來(lái),操作序列A是一個(gè)不可分割的整體。(物理學(xué)早期,原子也被認(rèn)為是不可分割的)。

            以上冗長(zhǎng)的解釋,精簡(jiǎn)成一句話,就是“線程間需要互斥執(zhí)行”。需要互斥的操作對(duì)應(yīng)的代碼也有一個(gè)很學(xué)術(shù)的名稱-“關(guān)鍵域(或關(guān)鍵區(qū))”。

            那么如何實(shí)現(xiàn)互斥呢?一個(gè)簡(jiǎn)單的思路就是,設(shè)立一個(gè)權(quán)威仲裁者,給那些需要互斥執(zhí)行的線程頒發(fā)一個(gè)共用的通行證。某個(gè)線程想要進(jìn)入一個(gè)關(guān)鍵域執(zhí)行,需要先 申請(qǐng)可以進(jìn)入該關(guān)鍵域的通行證,如果別的線程已經(jīng)拿走了該通行證,則本線程等待,進(jìn)入休眠狀態(tài),退出調(diào)度。如果本線程的通行證使用完畢,則應(yīng)該將它歸還給 仲裁者,重新喚醒等待線程,參與調(diào)度,競(jìng)爭(zhēng)此通行證。

            比如,下列偽碼中,threadFuncA和threadFuncB就需要申請(qǐng)同一張通行證:
            例一:
            int globalCounter = 0;

            void threadFuncA (通行證類型 * 通行證)
            {
              獲取通行證;
              globalCounter++;
              歸還通行證;
            }

            Void threadFuncB (通行證類型 * 通行證)
            {
              獲取通行證;
              globalCounter *= 2;
              歸還通行證;
            }

            又比如,下列偽碼中,需要為ResourceClass類的對(duì)象引用計(jì)數(shù)器制定一張通行證

            例二:
            class ResourceClass
            {
            public:
              resource & reference()
              {
                獲取通行證;
            ++refcounter;
            printf(“當(dāng)前對(duì)象被引用了%u次”, refCounter);
            釋放通行證;
              }
            private:
              通行證類型 通行證;
              unsigned int refCounter;
            };

            ResourceClass rescObj
            Void threadFuncA()
            {
              rescObj-> reference();
            }

            Void threadFuncB()
            {
              rescObj-> reference();
            }

            最后一個(gè)例子,是為ResourceClass類的對(duì)象計(jì)數(shù)器制定一張通行證。
            例三:
            class ResourceClass
            {
            public:
              ResourceClass ()
              {
                獲取通行證;
            ++objcounter;
            printf(“當(dāng)前類創(chuàng)建了%u個(gè)對(duì)象”, objCounter);
            釋放通行證;
              }
            private:
              static通行證類型 通行證;
              unsigned int objCounter;
            };

            Void threadFuncA()
            {
              ResourceClass * rescObj = new ResourceClass ();
            }

            Void threadFuncB()
            {
              ResourceClass * rescObj = new ResourceClass ();
            }
            這三個(gè)例子中,例一是不同函數(shù)之間互斥,所以通行證的作用域要對(duì)兩個(gè)函數(shù)都可見(jiàn)。例二是同一個(gè)對(duì)象的內(nèi)部函數(shù)多次調(diào)用之間的互斥,所以只要保證該函數(shù)多次 調(diào)用時(shí)共用的都是當(dāng)前對(duì)象內(nèi)部同一份通行證即可。例三是同一個(gè)類的所有對(duì)象在創(chuàng)建時(shí)都要互斥,所以必需保證這個(gè)類的所有對(duì)象構(gòu)造時(shí)共用的時(shí)同一份通行證, 從而通行證被聲明為靜態(tài)成員。

            這里所說(shuō)的“通行證”在多線程編程中對(duì)應(yīng)一個(gè)專門的術(shù)語(yǔ)mutex,由“mutual exclusion”拼接而來(lái)。為什么不直接用“鎖”的概念呢?因?yàn)?#8220;鎖”并不能很好的表達(dá)互斥的含義。鎖是指一定條件下不允許當(dāng)前代碼段執(zhí)行的概念。如 上述例二或例三,不允許多個(gè)線程同時(shí)執(zhí)行同一函數(shù),這時(shí)說(shuō)這個(gè)函數(shù)被鎖定是很形象。但在例一中,A函數(shù)被鎖定,為什么B函數(shù)不能執(zhí)行呢?這就較難理解了。

            而且經(jīng)常有人感到疑惑,為什么“加鎖后,被鎖定的關(guān)鍵域只能串行執(zhí)行”。這個(gè)其實(shí)是指在各自的時(shí)間軸上,并行的控制流在經(jīng)過(guò)互斥執(zhí)行的代碼段時(shí),必需以先 后順序串行執(zhí)行。在今后的介紹中,mutex的申請(qǐng),用acquire()操作表示,mutex的歸還,用release()表示。舍棄lock(), unlock()的表示。

            為了深入理解,先來(lái)看一段使用忙等待實(shí)現(xiàn)互斥的代碼,用的是系統(tǒng)內(nèi)核中使用較多的“spin lock”互斥方法。

            例4.忙等待實(shí)現(xiàn)互斥
            //聲明為volatile,防止被編譯器優(yōu)化。
            volatile bool dataProcessNotDone = true;
            int criticalData = 0;

            unsigned threadFuncA( void* para )
            {
               //如果編譯器不支持volatile關(guān)鍵字,
            //打開優(yōu)化選項(xiàng)時(shí),此句可能直接變成死循環(huán)。
               while (dataProcessNotDone);   // spin lock,鎖定的是后續(xù)數(shù)據(jù)

               //被鎖定的代碼區(qū)
               printf("critical data is %d\n", CriticalData);
               return 0;
            }

            unsigned threadFuncB( void* para )
            {
               sleep(1000);
               criticalData++;
               dataProcessNotDone = false; //修改互斥變量
               return 0;
            }
            在高級(jí)語(yǔ)言中,利用spin lock實(shí)現(xiàn)復(fù)雜互斥條件非常困難,單單處理競(jìng)爭(zhēng)條件就令人望而生畏。Spin lock在每次等待解鎖的時(shí)間都很短時(shí),具有無(wú)需線程切換,無(wú)需再調(diào)度等獨(dú)特優(yōu)勢(shì)。但是在絕大多數(shù)應(yīng)用中,由于互斥等待時(shí)間不確定(可能很長(zhǎng)),多個(gè)線程等待spin lock解鎖的過(guò)程中,spinning的行為可能導(dǎo)致系統(tǒng)處于半癱瘓狀態(tài),會(huì)嚴(yán)重影響程序性能。

            除了忙等待之外,很多操作系統(tǒng)或線程庫(kù)都提供了互斥原語(yǔ)來(lái)實(shí)現(xiàn)互斥。如果有可能,應(yīng)當(dāng)盡量使用系統(tǒng)提供的接口實(shí)現(xiàn)互斥。否則,要考慮編譯器優(yōu)化中的常量代入,語(yǔ)句執(zhí)行順序重排,cpu指令序列優(yōu)化等依賴于具體軟硬件環(huán)境的復(fù)雜問(wèn)題(關(guān)鍵是這樣的付出沒(méi)有太大意義)。

            下面根據(jù)上述概念,抽象出互斥類的概念,定義如下Mutex類接口

            5.     互斥類接口定義

            文件mutex.h
            #ifndef __MUTEX_H__
            #define __MUTEX_H__
            // C++標(biāo)準(zhǔn)中以_或__開頭的名字不符合標(biāo)準(zhǔn),
            // 這一特點(diǎn)可以讓這樣定義的宏不會(huì)錯(cuò)誤覆蓋其他名字。

            class Mutex
            {
            public:
              Mutex();
              ~Mutex();
              bool acquire (bool block = true);
              void release();
            private:
              //依賴于具體實(shí)現(xiàn),后面再說(shuō)。
            };
            #endif
            其中,
            Mutex::acquire(),獲取互斥量,有阻塞和非阻塞兩種調(diào)用方式,阻塞方式,獲取互斥量失敗時(shí)線程休眠。非阻塞方式下,獲取失敗時(shí)直接返回。

            Mutex::release(),釋放互斥量。

            6.     示例程序


            下面的例子說(shuō)明了如何實(shí)現(xiàn)多線程環(huán)境下的Singleton模式。
            文件Singleton.h:
            #ifndef __SINGLETON_H__
            #define __SINGLETON_H__

            #include <stdio.h>
            #include "thread.h"
            #include "mutex.h"

            // Dummy class.
            class Helper {};

            // A wrapper class for Mutex class
            // It is exception safe.
            class Guard
            {
            public:
              Guard(Mutex & lock):mutex(lock)
              {
                mutex.acquire();
              }

              ~Guard()
              {
                mutex.release();
              }

            private:
              Mutex & mutex;
            };

            // Correct but possibly expensive multithreaded version
            class Singleton1
            {
            public:
              Helper * getInstance() {
                Guard guard(mutex);
                if (helper == NULL)
                {
                  helper = new Helper();
                }
                return helper;
              }

            private:
              static Mutex mutex;
              static Helper * helper;
            };

            // Broken multithreaded version
            // "Double-Checked Locking" idiom
            class Singleton2
            {
            public:
              Helper * getInstance() {
                
                if (helper == NULL)
                {
                  Guard guard(mutex);
                  if (helper == NULL)
                  {
                    helper = new Helper();
                  }
                }
                
                return helper;
              }

            private:
              static Mutex mutex;
              static Helper * helper;
            };

            //Thread class for test.
            template <typename T>
            class TestThread: public Thread
            {
            public:
              TestThread<typename T>(T & resource,  Helper *& res, Helper *&
            res2Cmp)
                :singleton(resource), instance(res), instance2Cmp(res2Cmp) {}

            protected:
              void * run (void *)
              {
                for (int i=0; i<100000; i++)
                {
                  instance = singleton.getInstance();
                  if (instance != instance2Cmp  
                      && instance != NULL
                      &&instance2Cmp != NULL
                     )
                  {
                    printf("Fail! %p <> %p.\n", instance, instance2Cmp);
                  }
                }
                return NULL;
              }
            private:
              T & singleton;
               Helper * & instance;
               Helper * & instance2Cmp;
            };

            #endif

            文件main.cpp
            #include <stdio.h>
            #include "singleton.h"

            #define SINGLETON Singleton1

            Mutex SINGLETON::mutex;
            Helper * SINGLETON::helper = NULL;

            int main(int argc, char** argv)
            {
              Helper * instance1= NULL;
              Helper * instance2 = NULL;
              SINGLETON singleton;

              TestThread<SINGLETON> thread1(singleton, instance1, instance2);
              TestThread<SINGLETON> thread2(singleton, instance2, instance1);
              thread1.start();
              thread2.start();
              thread1.wait();
              thread2.wait();
              printf("Finished!\n");
              return 0;
            }

            對(duì)此示例程序,說(shuō)明如下幾點(diǎn)。
            1.定義了一個(gè)新的Guard類,這樣做的好處是做到異常安全。比如:
            try
            {
              Mutex mutex;
              mutex.acquire();
              
            // Some operations
              if (errorHappened)
                throw Exception();
              
            mutex.release();
            }
            catch (Exception & e)
            {
              // Print error message;
            }
            這段代碼中,拋出異常時(shí),互斥量不能釋放,容易造成死鎖。使用Guard類重寫,
            可以實(shí)現(xiàn)異常安全:
            try
            {
              Mutex mutex;
              Guard guard(mutex);
              // Some operations
              if (errorHappened)
                throw Exception();
            }
            catch (Exception & e)
            {
              // Print error message;
            }

            2.Singleton1的實(shí)現(xiàn)可以確保多線程安全。但它是一個(gè)低效實(shí)現(xiàn)。假如有100次訪問(wèn),只有1次會(huì)修改instance指針,其余99次都是只讀操作。但是每次訪問(wèn)都需要進(jìn)行互斥量的獲取和釋放操作。

            取決于系統(tǒng)實(shí)現(xiàn)方式,互斥操作可能比整型變量的++操作慢一個(gè)數(shù)量級(jí)。有的實(shí)現(xiàn)提供的mutex其實(shí)是進(jìn)程級(jí)別的互斥,一次互斥操作,會(huì)進(jìn)入內(nèi)核態(tài),然后再返回用戶態(tài)。而有的線程庫(kù)提供的是Process local mutex,要稍快一些。但無(wú)論那種實(shí)現(xiàn),代價(jià)都較大。

            因此,為了改進(jìn)Singleton1的實(shí)現(xiàn)效率,"Double-Checked Locking" idiom被提了出來(lái)。其思路是如果instance指針為空再進(jìn)入互斥操作。由于獲取互斥量過(guò)程中,可能別的線程已經(jīng)將instance指針賦值,所以需要在獲得互斥量所有權(quán)之后,再次檢查instance指針值。這就是所謂”double check”中double的來(lái)歷。

            Double check的設(shè)計(jì)很聰明,但可惜無(wú)論在C++中還是在Java中,這么做其實(shí)都不能保證線程安全。考慮如下序列:
            Step1. 線程A檢查instance指針,發(fā)現(xiàn)其為空。獲取互斥量成功,運(yùn)行至語(yǔ)句helper = new Helper();
            在打開優(yōu)化選項(xiàng)時(shí),這個(gè)語(yǔ)句在優(yōu)化后可能變成2步子操作, 而且編譯器自動(dòng)調(diào)整了原語(yǔ)句的執(zhí)行順序(reordering):
            1)      分配內(nèi)存,將地址賦值給helper變量。此時(shí)helper值非空。
            2)      開始初始化此內(nèi)存。
            在運(yùn)行完子語(yǔ)句1后,線程發(fā)生切換,此時(shí)內(nèi)存尚未初始化。
            Step2. 線程B檢查instance指針,發(fā)現(xiàn)其非空。對(duì)instance所指對(duì)象進(jìn)行進(jìn)一步操作,由于此時(shí)對(duì)象是初始化還未完成無(wú)效數(shù)據(jù),程序崩潰。

            那么如何實(shí)現(xiàn)安全的double check呢?vc2005以后的版本,以及java 1.6以后的版本中,可以通過(guò)為helper加上volatile限定符,防止編譯優(yōu)化時(shí)調(diào)整指令執(zhí)行順序。最新的g++對(duì)volatile如何處理,沒(méi) 有查到相關(guān)資料。不過(guò)在C++中,只要本地匯編支持memoryBarrier指令,也可以通過(guò)在C++代碼中內(nèi)嵌匯編指令實(shí)現(xiàn)線程安全。
            在此不再詳細(xì)討論。

            除此之外,instance類型是否是基本類型,是否多核環(huán)境,都對(duì)不安全的double check版本運(yùn)行結(jié)果有微妙影響。

            3.無(wú)論編譯器如何實(shí)現(xiàn),無(wú)論硬件環(huán)境如何,即使它最慢,也應(yīng)該盡量使用系統(tǒng)提供的互斥原語(yǔ)。只有它是能夠確保安全的。通過(guò)系統(tǒng)接口實(shí)現(xiàn)互斥,可以避免考慮編譯優(yōu)化等復(fù)雜情況。一種觀點(diǎn)說(shuō)volatile可以確保上述double check有效,但是intel有技術(shù)人員專門從硬件的角度批駁了這個(gè)說(shuō)法,他告訴大家,即使編譯器不做這個(gè)reordering, 處理器也可能在指令級(jí)別做。唯一能確保安全的,還是由系統(tǒng)實(shí)現(xiàn)的互斥接口。(照此說(shuō)法,MS 和 Intel結(jié)成wintel聯(lián)盟還是必要的,呵呵)雙方說(shuō)法似乎都一樣權(quán)威時(shí),作為程序開發(fā)者,很難取舍,因此在此類問(wèn)題上還是應(yīng)該適當(dāng)保守。

            4.Mutex, volatile變量,普通變量在使用中的具體效率對(duì)比,是一個(gè)非常復(fù)雜的問(wèn)題。涉及到內(nèi)存,緩存,寄存器之間各種情況的同步問(wèn)題。不過(guò)大家可以針對(duì)一些簡(jiǎn)單的例子,測(cè)試一下執(zhí)行時(shí)間上的差異。


            7.     Unix實(shí)現(xiàn)

            下面是借助pthread的Mutex類實(shí)現(xiàn)。
            文件Mutex.h
            #ifndef __MUTEX_H__
            #define __MUTEX_H__

            #include <pthread.h>

            class Mutex
            {
            public:
              Mutex();
              virtual ~Mutex();
              virtual bool acquire (bool block = true);
              virtual void release();
            private:
              pthread_mutex_t handle;
            };
            #endif

            文件 Mutex.cpp
            #include "mutex.h"

            Mutex::Mutex()
            {
              pthread_mutex_init(&handle, NULL);
            }

            Mutex::~Mutex()
            {
              pthread_mutex_destroy(&handle);
            }

            bool Mutex::acquire(bool block)
            {
              if (block)
              {
                 return pthread_mutex_lock(&handle) == 0;
              }
              else
              {
                return pthread_mutex_trylock(&handle) == 0;
              }
            }

            void Mutex::release()
            {
              ReleaseMutex(handle);
            }


            Windows實(shí)現(xiàn)
            文件mutex.h
            #ifndef __MUTEX_H__
            #define __MUTEX_H__

            #include <windows.h>

            class Mutex
            {
            public:
              Mutex();
              virtual ~Mutex();
              virtual bool acquire (bool block = true);
              virtual void release();
            private:
              HANDLE handle;
            };
            #endif

            文件mutex.cpp
            #include "mutex.h"

            Mutex::Mutex()
            {
              handle = CreateMutex(NULL, false, NULL);
            }

            Mutex::~Mutex()
            {
              CloseHandle(handle);
            }

            bool Mutex::acquire(bool block)
            {
              //Use caution when calling the wait functions
              //and code that directly or indirectly creates windows.
              return WaitForSingleObject(handle, block ? INFINITE : 0) ==
            WAIT_OBJECT_0;
            }

            void Mutex::release()
            {
              ReleaseMutex(handle);
            }


            小結(jié)
            本節(jié)從控制流的角度進(jìn)一步介紹了什么是多線程執(zhí)行中的并發(fā),在此基礎(chǔ)上介紹了主動(dòng)對(duì)象的概念。在多線程編程中,需要考慮線程協(xié)作的地方,如果執(zhí)行順序理不清,為每一個(gè)線程畫一條時(shí)間軸,標(biāo)出各自時(shí)序,對(duì)分析問(wèn)題往往能有幫助。

            本節(jié)也介紹了多線程設(shè)計(jì)的一個(gè)基本思想,就是主動(dòng)對(duì)象要具有一定的獨(dú)立性,和其他線程的交互盡量只通過(guò)進(jìn)程環(huán)境、系統(tǒng)或線程庫(kù)提供的同步原語(yǔ)實(shí)現(xiàn)。

            為什么要互斥,這個(gè)基本問(wèn)題不能透徹理解的話,鎖的概念很容易把自己弄糊涂。互斥的粒度大小也就根本無(wú)法談起。

            互斥的效率問(wèn)題,在實(shí)際設(shè)計(jì)中是一個(gè)值得考慮的問(wèn)題。但是程序的正確性問(wèn)題是一個(gè)更重要的問(wèn)題。不能保證正確性,就不要用。保證正確性到實(shí)現(xiàn)高效性的過(guò)程,類似于學(xué)會(huì)走路到能夠飛奔的過(guò)程。對(duì)于初學(xué)者來(lái)說(shuō),欲速則不達(dá)。

            為了對(duì)互斥量的使用,“通行證”所有權(quán)的轉(zhuǎn)換,以及不同系統(tǒng)中Mutex的實(shí)現(xiàn)效率等有一個(gè)充分的感性認(rèn)識(shí),大家請(qǐng)多動(dòng)手實(shí)現(xiàn)才能真正有所收益,最終超越我本人的一己知見(jiàn)。

            最后,歡迎提出各種意見(jiàn),以便這個(gè)系列能夠錯(cuò)誤少一些,解釋準(zhǔn)確一些,幫助也更大一些。

             


            posted on 2011-02-22 15:01 Mike Song 閱讀(2127) 評(píng)論(0)  編輯 收藏 引用


            只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問(wèn)   Chat2DB   管理


            久久综合给合久久狠狠狠97色| 久久精品国产亚洲5555| 久久久久久久久久久免费精品 | 久久精品人人槡人妻人人玩AV | 无码人妻少妇久久中文字幕| 亚洲乱亚洲乱淫久久| 久久久久久免费一区二区三区| 97久久精品无码一区二区天美| 99久久无码一区人妻a黑| 九九精品99久久久香蕉| 精品久久一区二区三区| 国产毛片久久久久久国产毛片| 久久久久人妻精品一区三寸蜜桃| 久久亚洲AV无码西西人体| 色婷婷综合久久久久中文字幕| 亚洲国产香蕉人人爽成AV片久久 | 久久99这里只有精品国产| 久久人人爽人人人人片av| 色综合久久久久综合体桃花网 | 久久午夜无码鲁丝片午夜精品| 一级a性色生活片久久无| 久久www免费人成看片| www.久久99| 婷婷久久综合九色综合绿巨人 | 国内精品久久久久影院日本| 久久―日本道色综合久久| 久久国产免费直播| 亚洲va久久久噜噜噜久久天堂| 久久99中文字幕久久| 超级碰碰碰碰97久久久久| 久久精品国产亚洲AV麻豆网站| 国产2021久久精品| 亚洲国产精品无码久久久蜜芽| 人人狠狠综合久久亚洲婷婷| 热久久最新网站获取| 国产精品久久亚洲不卡动漫| 午夜精品久久久久9999高清| 996久久国产精品线观看| 久久天天躁夜夜躁狠狠躁2022| 久久精品成人免费网站| 伊人久久大香线蕉综合Av|