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

            桃源谷

            心靈的旅行

            人生就是一場旅行,不在乎旅行的目的地,在乎的是沿途的風景和看風景的心情 !
            posts - 32, comments - 42, trackbacks - 0, articles - 0
              C++博客 :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

            用C++編寫synchronized method比較難

            Posted on 2010-07-17 12:17 lymons 閱讀(3347) 評論(0)  編輯 收藏 引用 所屬分類: C++CUnix/Linux文章翻譯

            在C++下編寫synchronized method比較難 (1)Add star

            在Java中有叫做synchronized這樣一個方便的關(guān)鍵字。使用這個關(guān)鍵字的話,就可以像下面那樣能夠簡單的進行"同步"method. 然而,被同步的method并不表示它就能在多線程中同時被執(zhí)行.

            public class Foo {    
                 public synchronized boolean getFoo() { 
                      
                 }

            那么、在C++ (with pthread)中如何能實現(xiàn)同樣的功能呢? 首先,有一個最簡單的方法就是下面這個.

            // 方法 a
            void Foo::need_to_sync(void{  
            static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  
            pthread_mutex_lock(
            &mutex);  
            // 臨界區(qū)處理  
            pthread_mutex_unlock(&mutex);  
            return;
            }


            這個方法, 暫且不說C語言, 就是在C++中下面的若干問題

            • 在臨界區(qū)中間被return出來
            • 在臨界區(qū)中間發(fā)生異常exception

            發(fā)生的場合, mutex沒有被解鎖unlock。我們可以像下面代碼那樣對這點進行改進.

            // 方法 b
            class ScopedLock : private boost::noncopyable {
            public:  explicit ScopedLock(pthread_mutex_t& m) : m_(m) {
                pthread_mutex_lock(
            &m_);
              }
              
            ~ScopedLock(pthread_mutex_t& m) {
                pthread_mutex_unlock(
            &m_);
              }
            private:
              pthread_mutex_t
            & m_;
            };

            void Foo::need_to_sync(void) {
              
            static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
              {
             
            // 雖然不加這個括號程序也沒有問題。
                ScopedLock lock(mutex);

                
            // 在此處添加處理
              }
              
            return;
            }

            OK。return和異常的問題就可以解決了. 但是, 上面并沒有完全解決這個問題,仍然有下面這個問題.

            1. 使用這個pthread_mutex_t并不是C++的.特別是存在下面的問題:
              • 不能和其他的Mutex類型做同樣的處理
              • 與其他的Mutex類型使用同一個ScopedLock類,則不能lock
            2. Java的synchronized方法雖然可以"遞歸lock", 但是上面的代碼并不是這樣. 在臨界區(qū)中遞歸調(diào)用自己的話就會發(fā)生死鎖.

            特別是,第2點的遞歸lock的問題是很重要的. 這里好好地使用glibc擴展的話就可以象下面那樣解決.

            / 方法 c

            void Foo::need_to_sync(void) {
              
            static pthread_mutex_t mutex = PTHREAD_MUTEX_RECURSIVE_INITIALIZER_NP;

            從NP*1這個后綴名就知道, 這個方法沒有可移植性. 必須使用pthread_mutex_init來初始化遞歸mutex,而pthread_mutex_init函數(shù)在一個線程中只能被調(diào)用一次. 如果想要用synchronized method的方法來實現(xiàn)這個的話,就變成了"是先有雞還是先有蛋"的話題了. 所以,用叫做pthread_once的函數(shù)來實現(xiàn)它,這也是在SUSv3中被記載的定則

            // 方法 d

            namespace /* anonymous */ {
              pthread_once_t      once 
            = PTHREAD_ONCE_INIT;
              pthread_mutex_t     mutex;
              pthread_mutexattr_t attr;

              
            void mutex_init() {
                pthread_mutexattr_init(
            &attr);
                pthread_mutexattr_settype(
            &attr, PTHREAD_MUTEX_RECURSIVE);
                pthread_mutex_init(
            &mutex, &attr);
              }
            }

            void Foo::need_to_sync(void) {
              pthread_once(
            &once, mutex_init);


              {
                ScopedLock 
            lock(mutex);

                
            // 処理

              }
              
            return;
            }

            上面的代碼就OK了。


            這就能夠解決遞歸lock的問題了.但是..., 這個方法

            • 這越來越不像C++的代碼了.。對每一個想要同步(synchronize)的方法都像這么樣寫代碼的話,效率變得非常低下.
            • 隨機成本大。速度慢。

            就會產(chǎn)生上面那樣的新問題.


            [] 在C++下編寫synchronized method比較難(2)Add star

            "不, 方法的同步應該是經(jīng)常必需的, 并不是沒有方便的辦法",這樣的說法也有吧. 是的, 有. 一般的辦法是下面那樣,

            • 做成一個Mutex,作為(non-POD型的, 即普通的)C++類
            • 做成一個Mutex類的實例,作為類變量, 或者是全局變量, 來同步化方法

            來看看它的具體實現(xiàn)吧. 首先做成的Mutex類是下面那樣*2

            class Mutex {
            public:
              Mutex() {
                pthread_mutex_init(
            &m_, 0);
              }
              
            void Lock(void) {
                pthread_mutex_lock(
            &m_);
              }
              
            void Unlock(void) {
                pthread_mutex_unlock(
            &m_);
              }
            private:
              pthread_mutex_t m_;
            };

            現(xiàn)在的Mutex類,被作為抽象基類(接口類)的場合也比較多. 在這里就不說了. ScopedLock類也需要有若干的修改. 想下面那樣寫就好.

            template<typename T> class ScopedLock {
            public:
              ScopedLock(T
            & m) : _m(m) {
                _m.Lock();
              }
              
            ~ScopedLock() {
                _m.Unlock();
              }
            private:
              T
            & _m;
            };

            用這個Mutex類來同步方法, 就可以像下面那樣寫. 首先是看看一個明顯的有錯誤的例子.

            // 方法e

            void Foo::need_to_sync(void) {
              
            static Mutex mutex;
              {
                ScopedLock
            <Mutex> lock(mutex);

                
            // 処理

              }
              
            return;
            }

            這是... 代碼雖然簡單易懂,但是很遺憾,它不能很好工作. NG!. Foo::need_to_sync函數(shù)第一次被執(zhí)行的時候如果恰好是多個線程同時執(zhí)行的話, mutex 的構(gòu)造函數(shù)就有被多次調(diào)用的可能性.關(guān)于理由,可以參考微軟中比較有名氣的blog文章The Old New Thing、在這里面有詳盡的描述,所以就我們就不在詳細敘述了*3。在這篇blog里使用了VC++的代碼作為例子,但是g++也是差不多的。所以“動態(tài)的初始化局部的靜態(tài)變量”是, 在線程所完全意識不到的情況下進行的*4


            接下來,要介紹一個在目前做的比較好的方法。 為了簡單我們使用了全局變量,但是即使作為類變量(類中的static成員變量)也是一樣的。這個方法就是使用“非局部的靜態(tài)變量”來做成Mutex。

            // 方法f

            namespace /* anonymous */ {
              Mutex mutex;
            }

            void Foo::need_to_sync(void) {
              ScopedLock
            <Mutex> lock(mutex);

              
            // 處理

              
            return;
            }

            這個是最流行的方法,而且基本上可以沒有問題就能工作得很好。

            在一個全局的類對象x存在,且在x的構(gòu)造函數(shù)中直接或者繞彎間接的調(diào)用Foo::need_to_sync函數(shù)的場合,會引起一些問題。也就是靜態(tài)的對象的初始化順序的問題,這個問題一般也被叫做"static initialization order fiasco" 。在執(zhí)行到mutex的構(gòu)造函數(shù)之前, mutex.Lock()有可能會被執(zhí)行。


            這里的FAQ的10.12~10.16*5、在里面對自己的代碼的初始化順序已經(jīng)證明了沒有問題,而且將來也不會出現(xiàn)問題,所以上面的方法是OK的。


            如果, 初始化順序的問題不能保證他沒有問題的話, 只好使用pthread_once的“方法d”,或者移植性低的“方法c”。我的個人感覺是方法c還是比較不錯的選擇。


            在最后我們嘗試考慮一下如何把方法c變成C++的代碼。

            // 方法c (重新討論)

            void Foo::need_to_sync(void) {
            static pthread_mutex_t mutex = PTHREAD_MUTEX_RECURSIVE_INITIALIZER_NP;

            目標是、

            • 隱藏pthread_mutex_t類型、讓自己寫的類的類型可見。
            • 在方法e,f中像使用ScopedLock模板那樣進行修改。

            當然,不讓它發(fā)生初始化順序的問題。


            [] 用C++編寫synchronized method比較難 (3)Add star

            這是方法c的改良。 首先, 為了避免發(fā)生初始化順序的問題, 必須是不允許調(diào)用構(gòu)造函數(shù)就能完成對象的初始化。因此,必須像下面那樣初始化mutex對象

            // 方法c' (假設(shè))

            void Foo::need_to_sync(void) {
            static StaticMutex mutex = { PTHREAD_MUTEX_RECURSIVE_INITIALIZER_NP, ........ };

            一般的不允許像這樣初始化C++類。為了實現(xiàn)上面那樣的初始化,StaticMutex類必須是POD型的。所謂POD型就是,

            • 不允許有構(gòu)造函數(shù)
            • 不允許有析構(gòu)函數(shù)
            • 不允許編譯器生成拷貝構(gòu)造函數(shù), 賦值構(gòu)造函數(shù)。
            • 不允許有private, protected 的成員
            • 不允許有虛函數(shù)

            滿足以上規(guī)格的類型*6


            大概有嚴格的制約,但是利用"定義非虛成員函數(shù)是沒有問題的"這個特性, 我們嘗試改良方法c的方案.


            ...像下面那樣如何?

            // 方法c'

            #define POD_MUTEX_MAGIC 0xdeadbeef
            #define STATIC_MUTEX_INITIALIZER           { PTHREAD_INITIALIZER,              POD_MUTEX_MAGIC }
            #define STATIC_RECURSIVE_MUTEX_INITIALIZER { PTHREAD_RECURSIVE_INITIALIZER_NP, POD_MUTEX_MAGIC }

            class PODMutex {
            public:
              
            void Lock() {
                assert(magic_ 
            == POD_MUTEX_MAGIC);
                pthread_mutex_lock(
            &mutex_);
              }
              
            void Unlock() {
                assert(magic_ 
            == POD_MUTEX_MAGIC);
                pthread_mutex_unlock(
            &mutex_);
              }
              typedef ScopedLock
            <PODMutex> ScopedLock;

            public:
              
            // 雖然編程了POD型, 但是不定義成public就是無效的
              pthread_mutex_t mutex_;
              
            const unsigned int magic_;
            };

            // ScopedLock類模板是留用了在方法e,f中做成的代碼.

            void Foo::need_to_sync(void) {
              
            static PODMutex mutex = STATIC_RECURSIVE_MUTEX_INITIALIZER;
              {
                PODMutex::ScopedLock 
            lock(mutex);

                
            // 處理.

              }
              
            return;
            }

            上面的代碼滿足了"隱藏了pthread_mutex_t型,留用了ScopedLock<>"這兩個目的. 這不就是有點兒像C++的代碼了嗎? 還有,PODMutex類型是即使在上記例子中那樣的局部靜態(tài)變量以外,也能放心的使用全局變量,類變量了.

            而且, 成員變量 magic_ 是, 一個const成員變量, 所以當使用編譯器自動生成的構(gòu)造函數(shù)來創(chuàng)建一個對象時就會發(fā)生錯誤. 因此,在構(gòu)建release版程序時把它剔除就好了.


            使用g++ -S來編譯上面的代碼, 生成匯編代碼. 我們就能看見下面那樣的局部的靜態(tài)變量.

            $ g++ -S sample.cpp
            $ c++filt < sample.s | lv
            (略)
            .size Foo::need_to_sync()::mutex, 28
            Foo::need_to_sync()::mutex:
            .long 0
            .long 0
            .long 0
            .long 1
            .long 0
            .long 0
            .long -559038737

            0,0,0,1,0,0 這樣的東西是 PTHREAD_RECURSIVE_INITIALIZER_NP , -559038737 則是 POD_MUTEX_MAGIC 。即使沒有進行動態(tài)的初始化(不調(diào)用構(gòu)造函數(shù))、僅僅是在目標文件上生成的目標代碼那樣的進行靜態(tài)初始化, mutex對象也能被正常的初始化, 所以這段代碼是OK的.


            隨便, 在使用boost庫的場合, 方法f之外的選擇余地幾乎沒有(至少是現(xiàn)在). 一看見ML等, (當然!!)就知道可能會出現(xiàn) order順序的問題. 但是, 就目前來講, 既要保證既要保證可移植性*7和速度,又要能做成與方法c相當?shù)腜ODMutex的方法好像還沒有出現(xiàn)吧.


            完結(jié)

            *1:non portable 的意思

            *2:遞歸mutex的例子的代碼太冗長了,這里就省略 了. 根據(jù)pthread_mutex_init來進行初始化,就使得做成遞歸mutex變得比較容易了.

            *3:這里記載了 g++ -S的結(jié)果和解說

            *4:2005/12追記: 在最近的g++中發(fā)生異常、參照這里 http://d.hatena.ne.jp/yupo5656/20051215/p2

            *5:日語文獻 ASIN:489471194X 中記載著翻譯版. 還有 static initialization order 的問題,在此處也有一些記載.

            *6:詳細是參看 ISO/IEC 14882:2003 或者是 JIS X 3014:2003 的「§3.9/10 C互換型」「§8.5.1/14 靜的記憶期間をもつC互換型の集成體の波括弧で囲んだ初期化子並びによる靜的な初期化」「§9/4 C互換構(gòu)造體」這幾個章節(jié)

            *7:Windows是如何做的? 可能不能解決這個命題 - 推測


            http://d.hatena.ne.jp/yupo5656/20051215/p2

            http://d.hatena.ne.jp/yupo5656/20041011#p1

            http://blogs.msdn.com/b/oldnewthing/archive/2004/03/08/85901.aspx

            http://d.hatena.ne.jp/yupo5656/20071008/p1

            我的個人簡歷第一頁 我的個人簡歷第二頁
            久久婷婷色综合一区二区| 国色天香久久久久久久小说| 久久青草国产精品一区| 日韩va亚洲va欧美va久久| 日韩人妻无码一区二区三区久久| 国产精品无码久久综合网| 人人狠狠综合久久88成人| 久久99热只有频精品8| 亚洲国产成人精品91久久久| 日日狠狠久久偷偷色综合0| 国产亚洲美女精品久久久2020| 国产亚洲欧美精品久久久| 久久人人爽人人爽AV片| 99久久精品无码一区二区毛片| 99久久无码一区人妻a黑| 久久久久久A亚洲欧洲AV冫| 精品久久久久久国产牛牛app| 99久久精品国产综合一区| 久久天天躁狠狠躁夜夜不卡| 94久久国产乱子伦精品免费| 日韩精品无码久久久久久| 人妻无码久久精品| 精品久久久久中文字| 精品国产乱码久久久久久郑州公司| 久久亚洲国产精品五月天婷| 青青草原综合久久大伊人精品| 久久99久久99精品免视看动漫 | 久久夜色撩人精品国产| 久久综合狠狠综合久久| 囯产极品美女高潮无套久久久| 欧美久久天天综合香蕉伊| 99久久精品国产一区二区| 久久中文娱乐网| 国内精品久久久久久野外| 丰满少妇人妻久久久久久4| 国产精品久久久久久吹潮| 亚洲国产另类久久久精品黑人 | 久久人人妻人人爽人人爽| 91精品国产综合久久婷婷| 久久亚洲电影| 亚洲国产日韩欧美综合久久|