2012-04-18 76 views
3

我只是在尋找反饋(明顯的缺陷/改進方法),試圖在結構上實現原子讀/寫。鎖定自由原子狀態類 - 它是否正確?

將會有一個寫入線程和多個讀取線程。其目的是防止讀者對結構不一致的看法,同時不會妨礙作者太多。

我使用的是獲取和添加原子基元,在這種情況下由Qt框架提供。

例如:

OneWriterAtomicState實現:

template <class T> 
class OneWriterAtomicState 
{ 
public: 
    OneWriterAtomicState() 
     : seqNumber(0) 
    { 
    } 

    void setState(T& state) { 
     this->seqNumber.fetchAndAddOrdered(1); 
     this->state = state; 
     this->seqNumber.fetchAndAddOrdered(1); 
    } 

    T getState(){ 
     T result; 
     int seq; 
     bool seq_changed = true; 

     /* repeat while seq is ODD or if seq changes during read operation */ 
     while((seq=this->seqNumber.fetchAndAddOrdered(0)) & 0x01 || seq_changed) { 
      result = this->state; 
      seq_changed = (this->seqNumber.fetchAndAddOrdered(0)!=seq); 
     } 
     return result; 
    } 


private: 
    QAtomicInt seqNumber; 
    T state; 
} 

這裏是二版(memcpy的,讀者屈服,希望固定的getState()):

template <class T> 
class OneWriterAtomicState 
{ 

public: 
    OneWriterAtomicState() 
     : seqNumber(0) 
    { 
     /* Force a compile-time error if T is NOT a type we can copy with memcpy */ 
     Q_STATIC_ASSERT(!QTypeInfo<T>::isStatic); 
    } 

    void setState(T* state) { 
     this->seqNumber.fetchAndAddOrdered(1); 
     memcpy(&this->state,state,sizeof(T)); 
     this->seqNumber.fetchAndAddOrdered(1); 
    } 

    void getState(T* result){ 
     int seq_before; 
     int seq_after = this->seqNumber.fetchAndAddOrdered(0); 
     bool seq_changed = true; 
     bool firstIteration = true; 

     /* repeat while seq_before is ODD or if seq changes during read operation */ 
     while(((seq_before=seq_after) & 0x01) || seq_changed) { 

      /* Dont want to yield on first attempt */ 
      if(!firstIteration) { 
       /* Give the writer a chance to finish */ 
       QThread::yieldCurrentThread(); 
      } else firstIteration = false; 

      memcpy(result,&this->state,sizeof(T)); 
      seq_after = this->seqNumber.fetchAndAddOrdered(0); 
      seq_changed = (seq_before!=seq_after); 
     } 
    } 

    bool isInitialized() { return (seqNumber>0); } 

private: 
    QAtomicInt seqNumber; 
    T state; 
} ; 

#endif // ONEWRITERATOMICSTATE_H 

回答

2

該算法不完全正確。這裏有一個線程可能交錯的地方,讀者得到不一致的數據:

state initialized to {0,0} and seqNumber to 0 

Writer: 
seqNumber = 1; 
state.x = 1; 

Reader: 
seq = seqNumber; //1 
result = state; //{1,0} 
seq_changed = (seqNumber != seq); //false 

Writer: 
state.y = 1; 
seqNumber = 2; 

Reader: 
jumps back to the start of the loop 
seq = seqNumber; //2 
steps out of the loop because seq == 2 and seq_changed == false 

所以問題是,seqNumber在兩個地閱讀和有可能爲作家更新值之間的讀取。

while((seq=this->seqNumber.fetchAndAddOrdered(0)) & 0x01 || seq_changed) { 
    result = this->state; 
    seq_changed = (this->seqNumber.fetchAndAddOrdered(0)!=seq); 
    //If writer updates seqNumber here to even number bad things may happen 
} 

應該只是每次迭代中讀取一次:

T getState(){ 
    T result; 
    int seq; 
    int newseq = seqNumber.fetchAndAddOrdered(0); 
    bool seq_changed = true; 

    while((seq = newseq) & 0x01 || seq_changed) { 
     result = state; 
     newseq = seqNumber.fetchAndAddOrdered(0); 
     seq_changed = (newseq != seq); 
    } 
    return result; 
} 

我相信這應能正常工作,但我不能保證什麼。 :)至少你應該寫一個測試程序,就像在你的例子中那樣,但是在閱讀器中添加一個檢查不一致值的檢查。

值得考慮的一件事是使用原子增量(fetchAndAdd)是一種矯枉過正。只有一個線程編寫seqNumber,因此您可以使用簡單的原子存儲釋放和加載獲取操作,並且可以在許多處理器上以更快的速度執行它們。但我不知道這些操作是否可以使用QAtomicInt;文檔對此很不清楚。

編輯:和wilx是正確的,T必須是一個平凡複製的類型

+0

謝謝你是的,我看到交錯的問題。但不確定T需要簡單地複製......在這種情況下會出現什麼問題? – greTech 2012-04-18 22:15:13

+0

如果寫入程序線程被pre-mid-T-copy,那麼由於seqNumber檢查,讀者肯定會意識到狀態不一致? – greTech 2012-04-18 22:16:45

+1

@will:例如,如果您有'string'對象,其中複製構造函數必須複製一個內存緩衝區。字符串內部有一個指向內存緩衝區和字符串長度的指針。如果讀者在不一致狀態下看到指針和長度,則複製構造函數可能會導致訪問衝突。 – Timo 2012-04-19 06:14:14

1

我想這隻有在T的複製賦值運算符是原始的時纔有效並基本上只是一個按位複製。對於更復雜的T,在執行result = this->state;期間,最終可能會出現不一致的狀態。

所以,我會建議使用某種rwlocks作家偏好。

+0

我想避免使用鎖,以免妨礙作者線程。我相信你是對的,但似乎無法弄清楚爲什麼我可能在'result = this-> state中得到不一致的狀態;'對於複雜的T.我知道這個語句不是原子的,但是當seqNumber再次被創建時,狀態會一致嗎?見上面的評論。 – greTech 2012-04-18 22:29:28

+0

是的,我現在看到了。謝謝 – greTech 2012-04-19 08:44:26

1

如果你有一個基於優先級的線程調度和讀者有比你可能會遇到一個活鎖作家更高的優先級。想象一下,作家開始寫下價值,然後讀者進入主動等待狀態。由於讀者的高度重視,作者將永遠無法完成寫作。

解決方案是在等待循環中添加一個小小的延遲。

+0

好點謝謝 – greTech 2012-04-18 22:23:55