【问题标题】:C++ STL vector iterator vs indexes access and thread safetyC++ STL 向量迭代器与索引访问和线程安全
【发布时间】:2012-05-13 02:52:25
【问题描述】:

我正在迭代一个 STL 向量并从中读取值。还有另一个线程可以更改此向量。现在,如果其他线程从向量中插入或删除元素,它会使迭代器无效。没有使用涉及的锁。我选择通过索引(方法 1)代替迭代器(方法 2)访问容器是否使其线程安全?性能怎么样?

struct A{int i; int j;};

方法一:

   size_t s = v.size();//v contains pointers to objects of type A
    for(size_t i = 0; i < s; ++i)
    {
         A* ptr = v[i];
         ptr->i++;
    }

方法二:

std::vector<A*>::iterator begin =  v.begin();
std::vector<A*>::iterator end =  v.end();
for(std::vector<A*>::iterator it = begin; it != end; ++it)
{
     A* ptr = *it;
     ptr->i++: 
}

【问题讨论】:

    标签: c++ multithreading stl thread-safety


    【解决方案1】:

    标准库容器的线程安全保证非常简单(这些规则是在 C++ 2011 中添加的,但基本上所有当前的库实现都符合这些要求并施加相应的限制):

    1. 可以有多个并发阅读器
    2. 如果有一个线程修改容器,则不应有其他线程访问(读取或写入)它
    3. 要求是每个容器对象

    实际上,这意味着您需要使用容器外部的某种机制来保证从多个线程访问的容器得到正确处理。例如,您可以使用互斥锁或读写器锁。当然,大多数时候容器只能从一个线程访问,并且没有任何锁定就可以正常工作。

    如果不使用显式锁,您将导致数据竞争并且行为未定义,与您使用索引还是迭代器无关。

    【讨论】:

    • 哪个更快?其中之一。它会随着平台、编译器、编译器版本、编译器标志以及可能的月相而改变。你需要配置文件。值得注意的是,与被访问对象的局部性相比,使用索引还是迭代器的选择可能是微不足道的。
    【解决方案2】:

    OP “我选择通过索引(方法 1)而不是迭代器(方法 2)访问容器是否使其线程安全?”

    不,一旦您开始写入数据结构,这两种方法都不是线程安全的。

    因此,您需要序列化对数据结构的访问。

    为了节省您大量的时间和挫败感,有很多现成的解决方案,例如

    英特尔线程构建模块 (TBB) 附带线程安全容器,例如 concurrent_vector

    http://threadingbuildingblocks.org/

    concurrent_vector 是一个具有以下特性的容器:

    • 按索引随机访问。第一个元素的索引为零。
    • 多个线程可以同时扩展容器并添加新元素。
    • 增大容器不会使现有迭代器或索引失效。*

    OP“性能怎么样?”

    不知道。使用不同编译器的不同系统上的不同性能,但不知道其大小足以影响您的选择。

    【讨论】:

      【解决方案3】:

      没有。 STL 容器不是线程安全的。

      当它们访问向量时,您应该为每个线程(删除的线程/添加的线程)提供独占访问权限。即使在使用索引时,您也可能会删除第 i 个元素,从而使您检索到的指针无效。

      【讨论】:

      • +1 C/C++ 标准库中的任何内容都不应被视为线程安全的。然而,POSIX 定义的函数被定义为线程安全的。
      • @seljuq70:你的说法是错误的。在最新的标准更新发布之前确实如此,但 C 和 C++ 都提供了特定的线程安全保证。它们可能不是您想要的,但这并不意味着库结构不是线程安全的。各种类确实没有监视器(在大多数情况下),这似乎是许多人误解为线程安全的原因。
      • @DietmarKühl 看完我的笔记后,您似乎完全正确。几乎所有系统和库函数都被认为是线程安全的。另外,关于所有 POSIX 函数都是线程安全的,我错了。例如,readdir 和 strerror 没有被定义为线程安全的(尽管我认为它们可以是)。
      【解决方案4】:

      您的算法可以使用固定大小的数组吗?

      我问的原因是,从逻辑上讲,让多个线程以线程安全、无锁的方式修改(大多数类型的)容器的唯一方法是使容器本身保持不变。这意味着 CONTAINER 在线程中永远不会改变,只会改变其中的元素。想一想在火车上弄乱棚车内部与在火车沿轨道移动时实际从火车上添加和移除整个棚车之间的区别。仅当您对该数据的操作遵守某些限制时,即使干预元素也是安全的。

      好消息是锁并不总是世界末日。如果多个执行上下文(线程、程序等)可以同时访问同一个对象,那么它们通常是唯一的解决方案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-03-19
        • 1970-01-01
        • 2016-08-25
        • 2010-10-18
        • 2012-01-05
        • 2012-10-07
        • 1970-01-01
        相关资源
        最近更新 更多