【问题标题】:Is the C++ std::set thread-safe?C++ std::set 线程安全吗?
【发布时间】:2010-11-24 15:06:09
【问题描述】:

我有一个关于 std::set 线程安全的问题。

据我所知,我可以迭代一个集合并添加/删除成员,这不会使迭代器无效。

但请考虑以下情况:

  • 线程“A”迭代一组 shared_ptr
  • 线程“B”偶尔会将项目添加到此集合中。

我在程序运行时遇到了段错误,我不确定为什么会发生这种情况。是缺乏线程安全的原因吗?

【问题讨论】:

    标签: c++ stl std stdset


    【解决方案1】:

    没有一个 STL 容器是线程安全的,所以 std::set 尤其不是。

    在您的情况下,问题甚至不是真正的线程安全:您只需在多个线程之间共享一个对象(很好)并在一个线程中修改它(也很好)。但正如你已经说过的,修改容器会使它的迭代器失效。这是否发生在同一个线程或不同线程中都无关紧要,因为它仍然是同一个容器

    天啊! §23.1.2.8 声明插入不会使迭代器无效。

    【讨论】:

    • 实际上,标准规定在 std::set 中添加或删除不会使其任何迭代器无效(指向要删除的对象的迭代器是明显的例外)。跨度>
    • ...即使迭代器没有失效,我也不相信插入和删除会在两个线程使用时使集合处于可用状态。
    • 对我来说似乎并不奇怪:集合和映射(通常)是用链接树实现的,而迭代器用指针实现。插入和删除只会改变相关元素的本地“邻居”(在逻辑意义上),即使这样也不需要重新分配任何其他节点:因此,指向它们的迭代器无需任何额外努力就保持有效。
    • @Konrad: 23.1.2.8 仅适用于setmapmultisetmultimap。 TR1 针对unordered_*(我正在查看的版本中的第 6.3.1.12-13 节)在插入/删除和迭代器有效性方面指定了自己的规则。这些要求确实比订购的同行要弱。
    • @suszterpatt。插入可能不会使迭代器无效。但是如果线程A在插入的中途是集合的内部状态有效(插入完成时有效,但不要求在任何其他点有效)。现在,如果您在单独的线程中增加一个有效的迭代器,该迭代器会与集合交互吗?如果是这样,那么结果可能是不确定的。
    【解决方案2】:

    STL 没有内置线程支持,因此您必须扩展 STL 使用您自己的同步机制的代码来使用 STL 多线程环境。

    例如看这里:link text

    由于 set 是一个容器类,MSDN 有以下关于容器线程安全的说法。

    单个对象是线程安全的,可以从多个线程中读取。例如,给定一个对象 A,同时从线程 1 和线程 2 读取 A 是安全的。

    如果一个线程正在写入单个对象,则必须保护同一线程或其他线程上对该对象的所有读取和写入。例如,给定一个对象 A,如果线程 1 正在写入 A,则必须阻止线程 2 读取或写入 A。

    即使另一个线程正在读取或写入同一类型的不同实例,读取和写入一个类型的实例也是安全的。例如,给定相同类型的对象 A 和 B,如果在线程 1 中写入 A 而在线程 2 中读取 B 是安全的。

    【讨论】:

    • 实际上,您会发现创建一个包含 std::set 的线程安全类比扩展 set 本身更容易。工作量少了很多。
    • @Kieveli +1,但您必须注意不要在集合中提供后门(返回集合迭代器),这可能意味着必须在集合迭代器之上编写自己的迭代器集合,以便增量/减量是线程安全的。实现线程安全集和包装器都不是简单的任务......
    • 你能提供 MSDN 引用的链接吗?没关系:msdn.microsoft.com/en-us/library/c9ceah3b(v=VS.100).aspx
    【解决方案3】:

    Dinkumware STL 文档包含有关该主题的以下段落。它可能(如文中所示)对大多数实现都有效。

    对于定义在 标准 C++ 库,例如 STL 模板的容器和对象 类 basic_string,这个 实施遵循广泛 为 SGI 制定的采用的做法 STL:

    多个线程可以安全地读取同一个容器对象。 (有 nunprotected 内的可变子对象 一个容器对象。)

    两个线程可以安全地操作不同的容器对象 同类型。 (没有 未受保护的共享静态对象 在容器类型中。)

    您必须防止同时访问容器 如果至少有一个线程是对象 修改对象。 (明显的 同步原语,例如 Dinkum 线程库中的那些, 不会被容器颠覆 对象。)

    因此,没有尝试确保 容器上的原子操作 对象是线程安全的;但它是 很容易制作共享容器 线程安全的对象 适当的粒度级别。

    【讨论】:

      【解决方案4】:

      简单解释:如果线程 A 在容器中移动迭代器,它正在查看容器内部。如果线程 B 修改容器(即使是不会使 A 拥有的迭代器无效的操作),线程 A 可能会遇到麻烦,因为 B 正在欺骗容器内部,可能使它们处于(暂时)无效状态。这会导致线程 A 崩溃。

      问题不在于迭代器本身。当他们需要容器的数据结构以便找到您遇到麻烦的位置时。

      就这么简单。

      【讨论】:

        【解决方案5】:

        是的。处理这种情况的一种方法是让每个线程在访问同一个集合对象之前锁定一个共享互斥体。确保使用 RAII 技术来锁定和解锁互斥体。

        【讨论】:

          【解决方案6】:

          执行插入会导致向量重新分配其底层内存,而迭代器仍可能指向先前(但无效)的内存地址,从而导致段错误。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-12-23
            • 2017-05-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-03-31
            相关资源
            最近更新 更多