青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

桃源谷

心靈的旅行

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

用C++編寫synchronized method比較難

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

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

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

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

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

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


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

  • 在臨界區中間被return出來
  • 在臨界區中間發生異常exception

發生的場合, 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", 但是上面的代碼并不是這樣. 在臨界區中遞歸調用自己的話就會發生死鎖.

特別是,第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函數在一個線程中只能被調用一次. 如果想要用synchronized method的方法來實現這個的話,就變成了"是先有雞還是先有蛋"的話題了. 所以,用叫做pthread_once的函數來實現它,這也是在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)的方法都像這么樣寫代碼的話,效率變得非常低下.
  • 隨機成本大。速度慢。

就會產生上面那樣的新問題.


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

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

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

來看看它的具體實現吧. 首先做成的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_;
};

現在的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函數第一次被執行的時候如果恰好是多個線程同時執行的話, mutex 的構造函數就有被多次調用的可能性.關于理由,可以參考微軟中比較有名氣的blog文章The Old New Thing、在這里面有詳盡的描述,所以就我們就不在詳細敘述了*3。在這篇blog里使用了VC++的代碼作為例子,但是g++也是差不多的。所以“動態的初始化局部的靜態變量”是, 在線程所完全意識不到的情況下進行的*4


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

// 方法f

namespace /* anonymous */ {
  Mutex mutex;
}

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

  
// 處理

  
return;
}

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

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


這里的FAQ的10.12~10.16*5、在里面對自己的代碼的初始化順序已經證明了沒有問題,而且將來也不會出現問題,所以上面的方法是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模板那樣進行修改。

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


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

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

// 方法c' (假設)

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

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

  • 不允許有構造函數
  • 不允許有析構函數
  • 不允許編譯器生成拷貝構造函數, 賦值構造函數。
  • 不允許有private, protected 的成員
  • 不允許有虛函數

滿足以上規格的類型*6


大概有嚴格的制約,但是利用"定義非虛成員函數是沒有問題的"這個特性, 我們嘗試改良方法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類型是即使在上記例子中那樣的局部靜態變量以外,也能放心的使用全局變量,類變量了.

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


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

$ 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 。即使沒有進行動態的初始化(不調用構造函數)、僅僅是在目標文件上生成的目標代碼那樣的進行靜態初始化, mutex對象也能被正常的初始化, 所以這段代碼是OK的.


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


完結

*1:non portable 的意思

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

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

*4:2005/12追記: 在最近的g++中發生異常、參照這里 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互換構造體」這幾個章節

*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

我的個人簡歷第一頁 我的個人簡歷第二頁
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            欧美日韩1234| 国产亚洲精品成人av久久ww| 亚洲大胆在线| 欧美激情精品久久久久久| 久久午夜精品| 亚洲精品午夜精品| 亚洲精品乱码久久久久| 免费在线观看精品| 日韩亚洲国产欧美| 亚洲素人在线| 狠狠入ady亚洲精品| 欧美成人xxx| 欧美日韩你懂的| 久久精品国产精品亚洲| 久久尤物视频| 亚洲免费在线观看视频| 久久精品导航| 99精品欧美一区二区蜜桃免费| 99热免费精品| 狠狠色2019综合网| 亚洲精品国产日韩| 国产亚洲一区精品| 91久久精品一区| 国产精品日韩专区| 免费日韩一区二区| 欧美色欧美亚洲另类二区| 久久gogo国模裸体人体| 久久综合久久美利坚合众国| 中国av一区| 久久久综合精品| 午夜精品久久久久久久久久久久| 久久久久久久性| 午夜精品区一区二区三| 欧美大片免费观看| 欧美在线观看视频一区二区| 欧美成人精品在线观看| 久久国产色av| 欧美日韩在线一区二区三区| 欧美 日韩 国产精品免费观看| 欧美手机在线视频| 亚洲国产日韩欧美| 好看的日韩视频| 亚洲视屏在线播放| 99精品免费视频| 久久婷婷国产综合国色天香| 欧美专区亚洲专区| 欧美日韩视频在线| 亚洲高清精品中出| 亚洲成人自拍视频| 欧美一区二区女人| 先锋影音国产精品| 国产精品久久久久久久9999 | 国产精品日韩久久久| 欧美成年人网站| 韩国欧美国产1区| 亚洲欧美www| 亚洲一区二区三区三| 欧美成人午夜激情| 欧美激情一区二区三区成人| 一区二区三区在线看| 欧美在线一二三区| 久久精品一区二区三区中文字幕| 欧美手机在线| 亚洲视频第一页| 亚洲视频在线一区| 国产精品www.| 中文日韩在线视频| 亚洲男人av电影| 国产精品天天看| 午夜欧美电影在线观看| 欧美一区久久| 国产麻豆午夜三级精品| 欧美一区二区三区四区在线观看地址| 午夜欧美视频| 国产一区二区三区在线播放免费观看| 欧美在线观看日本一区| 久久影院午夜论| 狠狠色噜噜狠狠色综合久| 久久久久久久综合狠狠综合| 美玉足脚交一区二区三区图片| 在线观看日韩国产| 欧美福利专区| 在线视频日本亚洲性| 欧美在线资源| 一区视频在线| 欧美久久在线| 午夜精品视频网站| 欧美aa国产视频| 夜夜嗨av一区二区三区网站四季av | 久久综合亚洲社区| 久久精品中文字幕一区二区三区 | 麻豆久久精品| 亚洲精品视频在线| 亚洲欧美亚洲| 国内精品美女在线观看| 久久香蕉国产线看观看av| 亚洲福利精品| 欧美一区二区三区播放老司机| 精品动漫一区| 欧美色区777第一页| 欧美亚洲免费高清在线观看| 亚洲欧美日韩综合国产aⅴ| 欧美一级理论片| 久久伊伊香蕉| 一本一本久久| 激情成人综合| 欧美三级电影大全| 久久久久国产免费免费| 一区二区欧美亚洲| 免费视频久久| 欧美与欧洲交xxxx免费观看| 91久久精品一区二区别| 国产精品久久国产愉拍| 久久综合久久久| 亚洲欧美日韩综合aⅴ视频| 亚洲国语精品自产拍在线观看| 午夜久久tv| 夜夜嗨av一区二区三区| 黄色精品免费| 国产日韩欧美日韩大片| 欧美久久久久久蜜桃| 久久一区二区三区超碰国产精品| 亚洲香蕉在线观看| 久久久福利视频| 黄色成人av在线| 国产精品免费aⅴ片在线观看| 免费观看亚洲视频大全| 久久精品二区亚洲w码| 亚洲视频大全| 99re热精品| 亚洲二区免费| 欧美96在线丨欧| 久久久精品五月天| 香蕉久久精品日日躁夜夜躁| 一区二区三区视频在线观看| 亚洲国产另类久久精品| 尹人成人综合网| 国产性色一区二区| 国产亚洲精品久| 国产日韩视频| 国产亚洲欧美日韩美女| 国产亚洲精品久久久久婷婷瑜伽 | 午夜国产不卡在线观看视频| 99精品国产福利在线观看免费| 亚洲国产精品久久久久婷婷884| 免费成人黄色片| 欧美高清视频一二三区| 欧美激情视频在线播放 | 亚洲第一天堂无码专区| 老色鬼精品视频在线观看播放| 欧美在线播放一区| 羞羞色国产精品| 久久国产精品网站| 久久综合狠狠综合久久激情| 久久婷婷麻豆| 亚洲第一成人在线| 亚洲国产成人精品视频| 亚洲国产欧洲综合997久久| 亚洲激情另类| 亚洲一区bb| 欧美一区二区在线看| 久久精品视频免费播放| 久久这里只有| 欧美日韩一卡二卡| 国产精品白丝av嫩草影院| 国产精品日韩久久久久| 国产专区综合网| 亚洲精品国产视频| 亚洲少妇诱惑| 久久先锋影音| 亚洲日本中文字幕免费在线不卡| 99热精品在线| 久久成人精品无人区| 免费永久网站黄欧美| 欧美性色综合| 韩国三级电影一区二区| 亚洲精品欧美精品| 亚洲欧洲av一区二区| 免费成人av在线看| 日韩亚洲精品电影| 欧美一区二区三区免费看 | 久久一区亚洲| 欧美日韩中文| 在线观看亚洲精品视频| 亚洲一区二区日本| 噜噜爱69成人精品| 99热精品在线观看| 久久久亚洲精品一区二区三区| 欧美日韩三级视频| 加勒比av一区二区| 亚洲欧美日韩成人高清在线一区| 欧美一区二粉嫩精品国产一线天| 亚洲大胆在线| 亚洲欧美中文日韩v在线观看| 欧美成人在线免费视频| 国产一区日韩欧美| 中国成人黄色视屏| 欧美激情中文字幕一区二区 | 美女免费视频一区| 亚洲一区中文|