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

            每天早晨叫醒你的不是鬧鐘,而是夢想

              C++博客 :: 首頁 :: 聯(lián)系 :: 聚合  :: 管理
              62 Posts :: 0 Stories :: 5 Comments :: 0 Trackbacks

            常用鏈接

            留言簿(1)

            我參與的團(tuán)隊(duì)

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

            #

            ANSI/ISO C++ Professional Programmer's Handbook 》是這樣說的

            explicit Constructors
            A constructor that takes a single argument is, by default, an implicit conversion operator, which converts its argument to
            an object of its class (see also Chapter 3, "Operator Overloading"). Examine the following concrete example:
            class string
            {
            private:
            int size;
            int capacity;
            char *buff;
            public:
            string();
            string(int size); // constructor and implicit conversion operator
            string(const char *); // constructor and implicit conversion operator
            ~string();
            };
            Class string has three constructors: a default constructor, a constructor that takes int, and a constructor that
            constructs a string from const char *. The second constructor is used to create an empty string object with an
            initial preallocated buffer at the specified size. However, in the case of class string, the automatic conversion is
            dubious. Converting an int into a string object doesn't make sense, although this is exactly what this constructor does.

            Consider the following:
            int main()
            {
            string s = "hello"; //OK, convert a C-string into a string object
            int ns = 0;
            s = 1; // 1 oops, programmer intended to write ns = 1,
            }
            In the expression s= 1;, the programmer simply mistyped the name of the variable ns, typing s instead. Normally,
            the compiler detects the incompatible types and issues an error message. However, before ruling it out, the compiler first
            searches for a user-defined conversion that allows this expression; indeed, it finds the constructor that takes int.
            Consequently, the compiler interprets the expression s= 1; as if the programmer had written
            s = string(1);
            You might encounter a similar problem when calling a function that takes a string argument. The following example
            can either be a cryptic coding style or simply a programmer's typographical error. However, due to the implicit
            conversion constructor of class string, it will pass unnoticed:
            int f(string s);
            int main()
            {
            f(1); // without a an explicit constructor,
            //this call is expanded into: f ( string(1) );
            //was that intentional or merely a programmer's typo?
            }
            'In order to avoid such implicit conversions, a constructor that takes one argument needs to be declared explicit:
            class string
            {
            //...
            public:
            explicit string(int size); // block implicit conversion
            string(const char *); //implicit conversion
            ~string();
            };
            An explicit constructor does not behave as an implicit conversion operator, which enables the compiler to catch the
            typographical error this time:
            int main()
            {
            string s = "hello"; //OK, convert a C-string into a string object
            int ns = 0;
            s = 1; // compile time error ; this time the compiler catches the typo
            }
            Why aren't all constructors automatically declared explicit? Under some conditions, the automatic type conversion is
            useful and well behaved. A good example of this is the third constructor of string:
            string(const char *);

            The implicit type conversion of const char * to a string object enables its users to write the following:
            string s;
            s = "Hello";
            The compiler implicitly transforms this into
            string s;
            //pseudo C++ code:
            s = string ("Hello"); //create a temporary and assign it to s
            On the other hand, if you declare this constructor explicit, you have to use explicit type conversion:
            class string
            {
            //...
            public:
            explicit string(const char *);
            };
            int main()
            {
            string s;
            s = string("Hello"); //explicit conversion now required
            return 0;
            }
            Extensive amounts of legacy C++ code rely on the implicit conversion of constructors. The C++ Standardization
            committee was aware of that. In order to not make existing code break, the implicit conversion was retained. However, a
            new keyword, explicit, was introduced to the languageto enable the programmer to block the implicit conversion
            when it is undesirable. As a rule, a constructor that can be invoked with a single argument needs to be declared
            explicit. When the implicit type conversion is intentional and well behaved, the constructor can be used as an
            implicit conversion operator.
            posted @ 2011-04-05 18:00 沛沛 閱讀(266) | 評論 (0)編輯 收藏

            http://www.c-view.org/journal/006/gp_aa.htm 


            并不是我故意想弄糟你的心情,但是在這期專欄里,我們將討論多線程編程這一話題。正如上一期Generic里所說的,編寫異常安全(exception-safe)的程序是非常困難的,但是和編寫多線程程序比起來,那簡直就是兒戲。  

            多線程的程序是出了名的難編寫、難驗(yàn)證、難調(diào)試、難維護(hù),這通常是件苦差事。不正確的多線程程序可能可以運(yùn)行很多年也不出一點(diǎn)錯(cuò),直到滿足某些臨界的條件時(shí),才出現(xiàn)意想不到的奇怪錯(cuò)誤。  

            不用說,編寫多線程程序的程序員需要使用可能得到的所有幫助。這期專欄將專注于討論競爭條件(race conditions)——這通常是多線程程序中各種麻煩的根源——深入了解它并提供一些工具來防止競爭。令人驚異的是,我們將讓編譯器盡其所能來幫助你做這些事。

            僅僅一個(gè)不起眼的關(guān)鍵字 
            盡管C和C++標(biāo)準(zhǔn)對于線程都明顯的“保持沉默”,但它們以volatile關(guān)鍵字的形式,確實(shí)為多線程保留了一點(diǎn)特權(quán)。  

            就象大家更熟悉的const一樣,volatile是一個(gè)類型修飾符(type modifier)。它是被設(shè)計(jì)用來修飾被不同線程訪問和修改的變量。如果沒有volatile,基本上會導(dǎo)致這樣的結(jié)果:要么無法編寫多線程程序,要么編譯器失去大量優(yōu)化的機(jī)會。下面我們來一個(gè)個(gè)說明。  

            考慮下面的代碼:  

             1class Gadget
             2{
             3public:
             4    void Wait()
             5    {
             6        while (!flag_)
             7        {
             8            Sleep(1000); // sleeps for 1000 milliseconds
             9        }

            10    }

            11    void Wakeup()
            12    
            13        flag_ = true
            14    }

            15    
            16private
            17    bool flag_; 
            18}
            ;
            19
            20


             
            上面代碼中Gadget::Wait的目的是每過一秒鐘去檢查一下flag_成員變量,當(dāng)flag_被另一個(gè)線程設(shè)為true時(shí),該函數(shù)才會返回。至少這是程序作
            者的意圖,然而,這個(gè)Wait函數(shù)是錯(cuò)誤的.假設(shè)編譯器發(fā)現(xiàn)Sleep(1000)是調(diào)用一個(gè)外部的庫函數(shù),它不會改變成員變量flag_,那么編譯器就可
            以斷定它可以把flag_緩存在寄存器中,以后可以訪問該寄存器來代替訪問較慢的主板上的內(nèi)存。這對于單線程代碼來說是一個(gè)很好的優(yōu)化,但
            是在現(xiàn)在這種情況下,它破壞了程序的正確性:當(dāng)你調(diào)用了某個(gè)Gadget的Wait函數(shù)后,即使另一個(gè)線程調(diào)用了Wakeup,Wait還是會一直循環(huán)下去。
            這是因?yàn)閒lag_的改變沒有反映到緩存它的寄存器中去。編譯器的優(yōu)化未免有點(diǎn)太……樂觀了。

            在大多數(shù)情況下,把變量緩存在寄存器中是一個(gè)非常有價(jià)值的優(yōu)化方法,如果不用的話很可惜。C和C++給你提供了顯式禁用這種緩存優(yōu)化的機(jī)會。
            如果你聲明變量是使用了volatile修飾符,編譯器就不會把這個(gè)變量緩存在寄存器里——每次訪問都將去存取變量在內(nèi)存中的實(shí)際位置。這樣你
            要對Gadget的Wait/Wakeup做的修改就是給flag_加上正確的修飾:  

            class Gadget 
            { 
            public: 
                ... as above ... 
            private: 
                volatile bool flag_; 
            }; 



            大多數(shù)關(guān)于volatile的原理和用法的解釋就到此為止,并且建議你用volatile修飾在多個(gè)線程中使用的原生類型變量。然而,你可以用volatile
            做更多的事,因?yàn)樗巧衿娴腃++類型系統(tǒng)的一部分。




            把volatile用于自定義類型 
            volatile修飾不僅可以用于原生類型,也可以用于自定義類型。這時(shí)候,volatile修飾方式類似于const(你也可以對一個(gè)類型同時(shí)使用const
            和volatile).

            與const不同,volatile的作用對于原生類型和自定義類型是有區(qū)別的。就是說,原生類型有volatile修飾時(shí),仍然支持它們的各種操作(加、
            乘、賦值等等),然而對于class來說,就不是這樣。舉例來說,你可以把一個(gè)非volatile的int的值賦給一個(gè)volatile的int,但是你不能把一
            個(gè)非volatile的對象賦給一個(gè)volatile對象。 

            讓我們舉個(gè)例子來說明自定義類型的volatile是怎么工作的。  
            class Gadget

            {
            public:
                void Foo() volatile;
                void Bar();
                ...
            private:
                String name_;
                int state_;
            };
            ...
            
            Gadget regularGadget;
            volatile Gadget volatileGadget;



            如果你認(rèn)為volatile對于對象來說沒有什么作用的話,那你可要大吃一驚了。  
            volatileGadget.Foo(); // ok, volatile fun called for 
                                      // volatile object 
            regularGadget.Foo();  // ok, volatile fun called for 
                                      // non-volatile object 
            volatileGadget.Bar(); // error! Non-volatile function called for 
                                      // volatile object! 

            從沒有volatile修飾的類型到相應(yīng)的volatile類型的轉(zhuǎn)換是很平常的。但是,就象const一樣,你不能反過來把volatile類型轉(zhuǎn)換為非volatile類型。你必須用類型轉(zhuǎn)換運(yùn)算符:  
            Gadget& ref = const_cast<Gadget&>;(volatileGadget); 
            ref.Bar(); // ok 

            一個(gè)有volatile修飾的類只允許訪問其接口的一個(gè)子集,這個(gè)子集由類的實(shí)現(xiàn)者來控制。用戶只有用const_cast才可以訪問這個(gè)類型的全部接口。而且,象const一樣,類的volatile屬性會傳遞給它的成員(例如,volatileGadget.name_和volatileGadget.state_也是volatile變量)。

            volatile,臨界區(qū)和競爭條件 
            多線程程序中最簡單也是最常用的同步機(jī)制要算是mutex(互斥對象)了。一個(gè)mutex只提供兩個(gè)基本操作:Acquire和Release。一旦某個(gè)線程調(diào)用了Acquire,其他線程再調(diào)用Acquire時(shí)就會被阻塞。當(dāng)這個(gè)線程調(diào)用Release后,剛才阻塞在Acquire里的線程中,會有一個(gè)且僅有一個(gè)被喚醒。換句話說,對于一個(gè)給定的mutex,只有一個(gè)線程可以在Acquire和Release調(diào)用之間獲取處理器時(shí)間。在Acquire和Release調(diào)用之間執(zhí)行的代碼叫做臨界區(qū)(critical section)。(Windows的用語可能會引起一點(diǎn)混亂,因?yàn)閃indows把mutex本身叫做臨界區(qū),而Windows的mutex實(shí)際上指進(jìn)程間的mutex。如果把它們分別叫作線程mutex和進(jìn)程mutex可能會好些。)  

            Mutex是用來避免數(shù)據(jù)出現(xiàn)競爭條件。根據(jù)定義,所謂競爭條件就是這樣一種情況:多個(gè)線程對數(shù)據(jù)產(chǎn)生的作用要依賴于線程的調(diào)度順序的。當(dāng)兩個(gè)線程競相訪問同一數(shù)據(jù)時(shí),就會發(fā)生競爭條件。因?yàn)橐粋€(gè)線程可以在任意一個(gè)時(shí)刻打斷其他線程,數(shù)據(jù)可能會被破壞或者被錯(cuò)誤地解釋。因此,對數(shù)據(jù)的修改操作,以及有些情況下的訪問操作,必須用臨界區(qū)保護(hù)起來。在面向?qū)ο蟮木幊讨校@通常意味著你在一個(gè)類的成員變量中保存一個(gè)mutex,然后在你訪問這個(gè)類的狀態(tài)時(shí)使用這個(gè)mutex。  

            多線程編程高手看了上面兩個(gè)段落,可能已經(jīng)在打哈欠了,但是它們的目的只是提供一個(gè)準(zhǔn)備練習(xí),我們現(xiàn)在要和volatile聯(lián)系起來了。我們將把C++的類型和線程的語義作一個(gè)對比。

            在一個(gè)臨界區(qū)以外,任意線程會在任何時(shí)間打斷別的線程;這是不受控制的,所以被多個(gè)線程訪問的變量容易被改得面目全非。這和volatile的原意[1]是一致的——所以需要用volatile來防止編譯器無意地緩存這樣的變量。  

            在由一個(gè)mutex限定的臨界區(qū)里,只有一個(gè)線程可以進(jìn)入。因此,在臨界區(qū)中執(zhí)行的代碼有和單線程程序有相同的語義。被控制的變量不會再被意外改變——你可以去掉volatile修飾。  

            簡而言之,線程間共享的數(shù)據(jù)在臨界區(qū)之外是volatile的,而在臨界區(qū)之內(nèi)則不是。  

            你通過對一個(gè)mutex加鎖來進(jìn)入一個(gè)臨界區(qū),然后你用const_cast去掉某個(gè)類型的volatile修飾,如果我們能成功地把這兩個(gè)操作放到一起,那么我們就在C++類型系統(tǒng)和應(yīng)用程序的線程語義建立起聯(lián)系。這樣我們可以讓編譯器來幫我們檢測競爭條件。

            LockingPtr 
            我們需要有一個(gè)工具來做mutex的獲取和const_cast兩個(gè)操作。讓我們來設(shè)計(jì)一個(gè)LockingPtr類,你需要用一個(gè)volatile的對象obj和一個(gè)mutex對象mtx來初始化它。在LockingPtr對象的生命期中,它會保證mtx處于被獲取狀態(tài),而且也提供對去掉volatile修飾的obj的訪問。對obj的訪問類似于smart pointer,是通過operator->;和operator*來進(jìn)行的。const_cast是在LockingPtr內(nèi)部進(jìn)行。這個(gè)轉(zhuǎn)化在語義上是正確的,因?yàn)長ockingPtr在其生存期中始終擁有mutex。  

            首先,我們來定義和LockingPtr一起工作的Mutex類的框架:  

            class Mutex

            {

            public:

                void Acquire();

                void Release();

                ...    

            };


            為了使用LockingPtr,你需要用操作系統(tǒng)提供的數(shù)據(jù)結(jié)構(gòu)和底層函數(shù)來實(shí)現(xiàn)Mutex。  
            LockingPtr是一個(gè)模板,用被控制變量的類型作為模板參數(shù)。例如,如果你希望控制一個(gè)Widget,你就要這樣寫LockingPtr <Widget>;。  

            LockingPtr的定義很簡單,它只是實(shí)現(xiàn)了一個(gè)單純的smart pointer。它關(guān)注的焦點(diǎn)只是在于把const_cast和臨界區(qū)操作放在一起。  

            template <typename T>;

            class LockingPtr 
            {

            public:

               // Constructors/destructors

               LockingPtr(volatile T& obj, Mutex& mtx)

                   : pObj_(const_cast<T*>;(&obj)),

                    pMtx_(&mtx)

               {    mtx.Lock();    }

               ~LockingPtr()

               {    pMtx_->;Unlock();    }

               // Pointer behavior

               T& operator*()

               {    return *pObj_;    }

               T* operator->;()

               {   return pObj_;   }

            private:
               T* pObj_;

               Mutex* pMtx_;

               LockingPtr(const LockingPtr&);

               LockingPtr& operator=(const LockingPtr&);

            };

            盡管很簡單,LockingPtr對于編寫正確的多線程代碼非常有用。你應(yīng)該把線程間共享的對象聲明為volatile,但是永遠(yuǎn)不要對它們使用
            const_cast——你應(yīng)該始終是用LockingPtr的自動對象(automatic objects)。讓我們舉例來說明。

            比如說你有兩個(gè)線程需要共享一個(gè)vector<char>;對象: 

            class SyncBuf {
            
            public:
                void Thread1();
                void Thread2();
            private:
                typedef vector<char>; BufT;
                volatile BufT buffer_;
                Mutex mtx_; // controls access to buffer_
            };

            在一個(gè)線程的函數(shù)里,你只需要簡單地使用一個(gè)LockingPtr<BufT>;對象來獲取對buffer_成員變量的受控訪問:

            void SyncBuf::Thread1() {
            
                LockingPtr<BufT>; lpBuf(buffer_, mtx_);
                BufT::iterator i = lpBuf->;begin();
                for (; i != lpBuf->;end(); ++i) {
                    ... use *i ...
                }
            }

            這樣的代碼很容易編寫,也很容易理解——每當(dāng)你需要使用buffer_時(shí),你必須創(chuàng)建一個(gè)LockingPtr<BufT>;來指向它。當(dāng)你這樣做了以后,你就
            可以訪問vector的全部接口。

            這個(gè)方法的好處是,如果你犯了錯(cuò)誤,編譯器會指出它:  


            void SyncBuf::Thread2() {
                // Error! Cannot access 'begin' for a volatile object
                BufT::iterator i = buffer_.begin();
                // Error! Cannot access 'end' for a volatile object
                for (; i != lpBuf->;end(); ++i) {
                    ... use *i ...
                }
            }

            你不能訪問buffer_的任何函數(shù),除非你進(jìn)行了const_cast或者用LockingPtr。這兩者的區(qū)別是LockingPtr提供了一個(gè)有規(guī)則的方法來對一個(gè)volatile變量進(jìn)行const_cast。  
            LockingPtr有非常好的表達(dá)力。如果你只需要調(diào)用一個(gè)函數(shù),你可以創(chuàng)建一個(gè)無名的臨時(shí)LockingPtr對象,然后直接使用它:  

            unsigned int SyncBuf::Size() {

                return LockingPtr<BufT>;(buffer_, mtx_)->;size();

            }


            回到原生類型 
            我們已經(jīng)看到了volatile對于保護(hù)對象免于不受控的訪問是多么出色,并且看到了LockingPtr是怎么提供了一個(gè)簡單有效的辦法來編寫線程安全的代碼。現(xiàn)在讓我們回到原生類型,volatile對它們的作用方式是不同的。 

            讓我們來考慮一個(gè)多個(gè)線程共享一個(gè)int變量的例子。  


            class Counter
            {
            public:
                ...
                void Increment() { ++ctr_; }
                void Decrement() { --ctr_; }
            private:
                int ctr_;
            };


            如果Increment和Decrement是在不同的線程里被調(diào)用的,上面的代碼片斷里就有bug。首先,ctr_必須是volatile的。其次,即使是一個(gè)看上去是原子的操作,比如++ctr_,實(shí)際上也分為三個(gè)階段。內(nèi)存本身是沒有運(yùn)算功能的,當(dāng)對一個(gè)變量進(jìn)行增量操作時(shí),處理器會:  
            把變量讀入寄存器  
            對寄存器里的值加1  
            把結(jié)果寫回內(nèi)存  
            這個(gè)三步操作稱為RMW(Read-Modify-Write)。在一個(gè)RMW操作的Modify階段,大多數(shù)處理器都會釋放內(nèi)存總線,以使其他處理器能夠訪問內(nèi)存。  
            如果在這個(gè)時(shí)候另一個(gè)處理器對同一個(gè)變量也進(jìn)行RMW操作,我們就遇到了一個(gè)競爭條件:第二次寫入會覆蓋掉第一次的值。  

            為了防止這樣的事發(fā)生,你又要用到LockingPtr:  

            class Counter
            
            {
            public:
                ...
                void Increment() { ++*LockingPtr<int>;(ctr_, mtx_); }
                void Decrement() { --*LockingPtr<int>;(ctr_, mtx_); }
            private:
                volatile int ctr_;
                Mutex mtx_;
            };

            現(xiàn)在這段代碼正確了,但是和SyncBuf相比,這段代碼的質(zhì)量要差一些。為什么?因?yàn)閷τ贑ounter,編譯器不會在你錯(cuò)誤地直接訪問ctr_(沒有對它加鎖)時(shí)產(chǎn)生警告。雖然ctr_是volatile的,但是編譯器還是可以編譯++ctr_,盡管產(chǎn)生的代碼絕對是不正確的。編譯器不再是你的盟友了,你只有自己留意競爭條件。  
            那么你該怎么做呢?很簡單,你可以用一個(gè)高層的結(jié)構(gòu)來包裝原生類型的數(shù)據(jù),然后對那個(gè)結(jié)構(gòu)使用volatile。這有點(diǎn)自相矛盾,直接用volatile修飾原生類型是一個(gè)不好的用法,盡管這是volatile最初期望的用法!
            到現(xiàn)在為止,我們討論了具有volatile數(shù)據(jù)成員的類;現(xiàn)在讓我們來考慮設(shè)計(jì)這樣的類,它會作為更大的對象的一部分并且在線程間共享。這里,volatile的成員函數(shù)可以幫很大的忙。  

            在設(shè)計(jì)類的時(shí)候,你只對那些線程安全的成員函數(shù)加volatile修飾。你必須假定外面的代碼會在任何地方任何時(shí)間調(diào)用volatile成員函數(shù)。不要忘記:volatile相當(dāng)于自由的多線程代碼,并且沒有臨界區(qū);非volatile相當(dāng)于單線程的環(huán)境或者在臨界區(qū)內(nèi)。  

            比如說,你定義了一個(gè)Widget類,它用兩個(gè)方法實(shí)現(xiàn)了同一個(gè)操作——一個(gè)線程安全的方法和一個(gè)快速的不受保護(hù)的方法。

            class Widget
            
            {
            public:
                void Operation() volatile;
                void Operation();
                ...
            private:
                Mutex mtx_;
            };


            注意這里的重載(overloading)用法。現(xiàn)在Widget的用戶可以用一致的語法調(diào)用Operation,對于volatile對象可以得到線程安全性,對于普通對象可以得到速度。用戶必須注意把共享的Widget對象定義為volatile。  
            在實(shí)現(xiàn)volatile成員函數(shù)時(shí),第一個(gè)操作通常是用LockingPtr對this進(jìn)行加鎖,然后其余工作可以交給非volatile的同名函數(shù)做: 

            void Widget::Operation() volatile
            
            {
                LockingPtr<Widget>; lpThis(*this, mtx_);
                lpThis->;Operation(); // invokes the non-volatile function
            }

            小結(jié) 
            在編寫對線程程序的時(shí)候,使用volatile將對你十分有益。你必須堅(jiān)持下面的規(guī)則:  

            把所有共享對象聲明為volatile  
            不要對原生類型直接使用volatile  
            定義共享類時(shí),用volatile成員函數(shù)來表示它的線程安全性。  
            如果你這么做了,而且用了簡單的通用組件LockingPtr,你就可以寫出線程安全的代碼,并且大大減少對競爭條件的擔(dān)心,因?yàn)榫幾g器會替你操心,并且勤勤懇懇地為你指出哪里錯(cuò)了。  

            在我參與的幾個(gè)項(xiàng)目中,使用volatile和LockingPtr產(chǎn)生了很大效果。代碼十分整潔,也容易理解。我記得遇到過一些死鎖的情況,但是相對于競爭條件,我寧愿對付死鎖的情況,因?yàn)樗鼈冋{(diào)試起來容易多了。那些項(xiàng)目實(shí)際上根本沒有碰到過有關(guān)競爭條件的問題。  

            致謝 
            非常感謝James Kanze和Sorin Jianu提供了很有洞察力的意見。
            致謝 
            非常感謝James Kanze和Sorin Jianu提供了很有洞察力的意見。  

            附:濫用volatile的本質(zhì)?[2] 
            在上一期的專欄《Generic<Programming>;: volatile — Multithreaded Programmer's Best Friend》發(fā)表以后,我收到很多反饋意見。就像是注定的一樣,大部分稱贊都是私人信件,而抱怨都發(fā)到USENET新聞組comp.lang.c++.moderated和 comp.programming.threads里去了。隨后引起了很長很激烈的討論,如果你對這個(gè)主題有興趣,你可以去看看這個(gè)討論,它的標(biāo)題是“volatile, was: memory visibility between threads.”。  

            我知道我從這個(gè)討論中學(xué)到了很多東西。比如說,文章開頭的Widget的例子不太切題。長話短說,在很多系統(tǒng)(比如POSIX兼容的系統(tǒng))中,volatile修飾是不需要的,而在另一些系統(tǒng)中,即使加了volatile也沒有用,程序還是不正確。  

            關(guān)于volatile correctness,最重要的一個(gè)問題是它依賴于類似POSIX的mutex,如果在多處理器系統(tǒng)上,光靠mutex就不夠了——你必須用memory barriers。  

            另一個(gè)更哲理性的問題是:嚴(yán)格來說通過類型轉(zhuǎn)換把變量的volatile屬性去掉是不合法的,即使volatile屬性是你自己為了volatile correctness而加上去的。正如Anthony Williams指出的,可以想象一個(gè)系統(tǒng)可能把volatile數(shù)據(jù)放在一個(gè)不同于非volatile數(shù)據(jù)的存儲區(qū)中,在這種情況下,進(jìn)行地址變換會有不確定的行為。 

            另一個(gè)批評是volatile correctness雖然可以在一個(gè)較低層次上解決競爭條件,但是不能正確的檢測出高層的、邏輯的競爭條件。例如,你有一個(gè)mt_vector模版類,用來模擬std::vector,成員函數(shù)經(jīng)過正確的線程同步修正??紤]這段代碼:  

            volatile mt_vector<int>; vec; 
             
            if (!vec.empty()) { 
                vec.pop_back(); 
            } 

            這段代碼的目的是刪除vector里的最后一個(gè)元素,如果它存在的話。在單線程環(huán)境里,他工作地很好。然而如果你把它用在多線程程序里,這段代碼還是有可能拋出異常,盡管empty和pop_back都有正確的線程同步行為。雖然底層的數(shù)據(jù)(vec)的一致性有保證,但是高層操作的結(jié)果還是不確定的。  
            無論如何,經(jīng)過辯論之后,我還是保持我的建議,在有類POSIX的mutex的系統(tǒng)上,volatile correctness還是檢測競爭條件的一個(gè)有價(jià)值的工具。但是如果你在一個(gè)支持內(nèi)存訪問重新排序的多處理器系統(tǒng)上,你首先需要仔細(xì)閱讀你的編譯器的文檔。你必須知己知彼。
            最后,Kenneth Chiu提到了一篇非常有趣的文章http://theory.stanford.edu/~freunds/race.ps,猜猜題目是什么?“Type-Based Race Detection for Java”。這篇文章講了怎么對Java的類型系統(tǒng)作一點(diǎn)小小的補(bǔ)充,從而讓編譯器和程序員一起在編譯時(shí)檢測競爭條件。  

            Eppur si muove.[3] 
            譯注 
            [1] Volatile原意為易變的,反復(fù)無常的。  

            [2] 這是Andrei在他的下一篇Generic<Programming>;里對本文作的補(bǔ)充說明。  

            [3] 這是伽利略在被迫放棄他一直信仰的哥白尼的地動說時(shí)所說過的一句辯解的話,意思是“它(地球)畢竟仍在運(yùn)動”。

            posted @ 2011-04-05 17:41 沛沛 閱讀(438) | 評論 (0)編輯 收藏

            僅列出標(biāo)題
            共7頁: 1 2 3 4 5 6 7 
            国产视频久久| 久久久久人妻精品一区三寸蜜桃 | 欧美va久久久噜噜噜久久| 久久精品国产精品亜洲毛片| 久久精品国产亚洲AV嫖农村妇女| 久久精品国产亚洲av麻豆蜜芽| 国产精品永久久久久久久久久| 久久久久久久综合日本亚洲| 97香蕉久久夜色精品国产| 久久综合给合综合久久| 狠狠久久综合伊人不卡| 丁香五月综合久久激情| 狠狠色综合久久久久尤物| 国产成人精品久久综合| 久久99精品国产麻豆蜜芽| 久久精品国产99久久久香蕉| 久久综合精品国产一区二区三区| 久久久久久久综合综合狠狠| 久久影视国产亚洲| 亚洲日韩欧美一区久久久久我 | 色综合久久夜色精品国产| 久久久久亚洲AV无码专区网站| 久久久久久极精品久久久| 久久久WWW免费人成精品| 久久久久香蕉视频| 亚洲午夜久久久| 久久人人爽人人人人片av| 亚洲精品无码久久久久去q | 色欲综合久久躁天天躁| 麻豆精品久久久久久久99蜜桃| 精品久久久中文字幕人妻 | 久久精品国产亚洲AV嫖农村妇女| 丰满少妇人妻久久久久久| 欧美精品一区二区精品久久| 久久精品三级视频| 亚洲av成人无码久久精品| 久久最新精品国产| 国产精品亚洲综合久久| www.久久热| 午夜精品久久久久9999高清| 久久精品国产亚洲AV高清热 |