簡體   English   中英

線程安全設置

[英]Thread-safe Settings

我正在編寫一些可以從我的多線程應用程序中的任何位置訪問的設置類。 我會經常閱讀這些設置(因此讀取訪問應該很快),但它們不是經常編寫的。

對於原始數據類型,看起來boost::atomic提供了我需要的東西,所以我提出了類似這樣的東西:

class UInt16Setting
{
    private:
        boost::atomic<uint16_t> _Value;
    public:
        uint16_t getValue() const { return _Value.load(boost::memory_order_relaxed); }
        void setValue(uint16_t value) { _Value.store(value, boost::memory_order_relaxed); }
};

問題1:我不確定內存排序。 我認為在我的應用程序中我並不關心內存排序(是嗎?)。 我只想確保getValue()始終返回一個未損壞的值(舊的或新的)。 我的內存訂購設置是否正確?

問題2:這種方法是否使用boost::atomic推薦用於這種同步? 或者是否有其他構造提供更好的讀取性能?

我還需要在我的應用程序中使用一些更復雜的設置類型,比如std::string或者例如boost::asio::ip::tcp::endpoint s的列表。 我認為所有這些設置值都是不可變的。 因此,一旦我使用setValue()設置值,值本身( std::string或端點列表本身)就不再發生變化。 所以我只想確保我得到舊值或新值,但不是一些損壞的狀態。

問題3:這種方法是否適用於boost::atomic<std::string> 如果沒有,有什么替代方案?

問題4:更復雜的設置類型如端點列表如何? 你會推薦像boost::atomic<boost::shared_ptr<std::vector<boost::asio::ip::tcp::endpoint>>>嗎? 如果不是,那會更好嗎?

Q1,如果在讀取原子后沒有嘗試讀取任何共享的非原子變量,請更正。 內存屏障僅同步對原子操作之間可能發生的非原子變量的訪問

Q2我不知道(但見下文)

Q3應該工作(如果編譯)。 然而,

 atomic<string> 

可能沒有鎖定

Q4應該可行,但同樣,實現不可能是無鎖的(實現lockfree shared_ptr具有挑戰性和專利挖掘的領域)。

因此,如果您的配置包含大小超過1個機器字的數據(CPU本機原子通常有效),那么讀者 - 編寫者鎖定(如Damon在評論中所建議的那樣)可能更簡單甚至更有效

[編輯]然而,

atomic<shared_ptr<TheWholeStructContainigAll> > 

即使是非鎖定也可能有一定的意義:這種方法可以最大限度地減少需要多個相干值的讀者的碰撞概率,盡管編寫者每次更改內容時都應該制作整個“參數表”的新副本。

對於問題1 ,答案是“取決於,但可能不是”。 如果你真的只關心單個值沒有亂碼,那么是的,這沒關系,你也不關心內存順序。
但通常,這是一個錯誤的前提。

對於問題2,3,4 ,是的,這會工作,但它很可能會使用鎖定復雜的對象,如string (內部,每次訪問,而不讓你知道)。 通常可以以無鎖的方式原子地訪問/改變大致一個或兩個指針大小的相當小的對象。 這也取決於您的平台。

一個原子地成功更新一個或兩個值是一個很大的區別。 假設你有價值觀leftright ,其划定的,其中一個任務將在數組做一些處理的左右邊界。 假設它們分別為50和100,並且每個原子地將它們更改為101和150。 因此,另一個線程將更改從50提取到101並開始進行計算,看到101> 100,完成並將結果寫入文件。 之后,再次以原子方式更改輸出文件的名稱。
一切都是原子的(因此,比正常情況更昂貴),但沒有一個是有用的。 結果仍然是錯誤的,也被寫入了錯誤的文件。
這在您的特定情況下可能不是問題,但通常是(並且,您的要求將來可能會發生變化)。 通常你真的希望完整的變更是原子的。

也就是說,如果你有許多或復雜的(或許多復雜的)更新,你可能想要在整個配置中首先使用一個大的(讀寫器)鎖,因為那是比獲取和釋放20或30個鎖或執行50或100個原子操作更有效。 但請注意,在任何情況下,鎖定都會嚴重影響性能。

正如上面的評論中所指出的,我最好從修改配置的一個線程制作配置的深層副本,並將消費者使用的引用(共享指針)的更新安排為正常任務。 復制 - 修改 - 發布方法有點類似於MVCC數據庫的工作原理(這些也存在鎖定會導致其性能下降的問題)。

修改副本斷言只有讀者訪問任何共享狀態,因此對於讀者或單個編寫者不需要同步。 讀寫很快。 當保證集合處於完整,一致的狀態並保證線程不做其他事情時,交換配置集只發生在明確定義的點上,因此不會出現任何類型的丑陋意外。

一個典型的任務驅動的應用程序看起來有點像這樣(在C ++中 - 就像偽代碼一樣):

// consumer/worker thread(s)
for(;;)
{
    task = queue.pop();

    switch(task.code)
    {
        case EXIT:
            return;

        case SET_CONFIG:
            my_conf = task.data;
            break;

        default:
            task.func(task.data, &my_conf); // can read without sync
    }
}


// thread that interacts with user (also producer)
for(;;)
{
    input = get_input();

    if(input.action == QUIT)
    {
        queue.push(task(EXIT, 0, 0));
        for(auto threads : thread)
            thread.join();
        return 0;
    }
    else if(input.action == CHANGE_SETTINGS)
    {
        new_config = new config(config); // copy, readonly operation, no sync
        // assume we have operator[] overloaded
        new_config[...] = ...;           // I own this exclusively, no sync

        task t(SET_CONFIG, 0, shared_ptr<...>(input.data));
        queue.push(t);
    }
    else if(input.action() == ADD_TASK)
    {
        task t(RUN, input.func, input.data);
        queue.push(t);
    }
    ...
}

對於比指針更重要的東西,請使用互斥鎖。 tbb(opensource)庫支持讀寫器變體的概念,允許多個同時讀取器,請參閱文檔

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM