线程安全设置

时间:2013-11-01 13:05:24

标签: c++ multithreading boost synchronization atomic

我正在编写一些可以从多线程应用程序中的任何位置访问的设置类。我会经常阅读这些设置(因此读取访问速度应该很快),但它们不会经常写入。

对于原始数据类型,看起来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的列表。我认为所有这些设置值都是不可变的。因此,一旦我使用setValue()设置了值,值本身(std::string或端点列表本身)就不再发生变化。所以我再次确保我得到旧值或新值,但不是一些损坏的状态。

问题3:此方法是否适用于boost::atomic<std::string>?如果没有,有什么替代方案?

问题4 :更复杂的设置类型如端点列表如何?你会推荐像boost::atomic<boost::shared_ptr<std::vector<boost::asio::ip::tcp::endpoint>>>这样的东西吗?如果不是,那会更好一点?

3 个答案:

答案 0 :(得分:2)

Q1,如果在读取原子后没有尝试读取任何共享的非原子变量,请更正。内存障碍仅同步对原子操作之间可能发生的非原子变量的访问

Q2我不知道(但见下文)

Q3应该工作(如果编译)。但是,

 atomic<string> 

可能没有锁定

Q4应该可以工作,但是,实现不可能是无锁的(实现lockfree shared_ptr是具有挑战性和专利挖掘的领域)。

因此,如果您的配置包含大小超过1个机器字(CPU本机原子通常有效)的数据,那么读者 - 编写者锁定(如Damon在评论中所建议的那样)可能更简单甚至更有效。

[编辑]然而,

atomic<shared_ptr<TheWholeStructContainigAll> > 

可能有一些意义甚至是无锁定的:这种方法可以最大限度地减少需要多个相干值的读者的碰撞概率,尽管编写者每次更改时都应该制作整个“参数表”的新副本。

答案 1 :(得分:2)

对于问题 1 ,答案是“取决于,但可能不是”。如果你真的只关心单个值没有乱码,那么是的,这很好,你也不关心内存顺序。
但通常,这是一个错误的前提。

如果问题 2 3 4 是,这将有效,但它可能会使用锁定复杂对象,例如{ {1}}(内部,每次访问,在您不知情的情况下)。通常可以以无锁的方式原子地访问/改变大致一个或两个指针大小的相当小的对象。这也取决于您的平台。

一个原子地成功更新一个或两个值是一个很大的区别。假设您有值stringleft,它们分隔任务在数组中进行某些处理的左右边界。假设它们分别为50和100,并且每个原子地将它们更改为101和150。因此,另一个线程将从50变为101并开始进行计算,看到101&gt; 100,完成并将结果写入文件。之后,再次以原子方式更改输出文件的名称 一切都是原子的(因此,比正常情况更昂贵),但没有一个是有用的。结果仍然是错误的,也被写入了错误的文件 这在您的特定情况下可能不是问题,但通常是(并且,您的要求将来可能会发生变化)。通常你确实希望完整集的变化是原子的。

也就是说,如果你有许多或复杂的(或许多复杂的)更新,你可能想要在整个配置中使用一个大的(读写器)锁,无论如何,因为这比获取和释放20或30个锁或执行50或100个原子操作更有效。但请注意,在任何情况下,锁定都会严重影响性能。

正如上面的评论所指出的,我最好从修改配置的一个线程制作配置的深层副本,并安排消费者使用的引用(共享指针)的更新作为正常任务。复制 - 修改 - 发布方法有点类似于MVCC数据库的工作原理(这些也存在锁定会导致其性能下降的问题)。

修改副本断言只有读者才能访问任何共享状态,因此对于读者或单个编写者不需要同步。读写很快。 当保证集合处于完整,一致的状态并保证线程不做其他事情时,交换配置集只发生在明确定义的点上,因此不会出现任何类型的丑陋意外。

典型的任务驱动的应用程序看起来有点像这样(在C ++中 - 就像伪代码一样):

right

答案 2 :(得分:2)

对于比指针更重要的东西,请使用互斥锁。 tbb(opensource)库支持读写器变体的概念,允许多个同时读者,请参阅documentation