【问题标题】:STL vector and thread-safetySTL 向量和线程安全
【发布时间】:2010-12-03 14:56:56
【问题描述】:

假设我有一个包含 N 个元素的向量,但该向量的最多 n 个元素具有有意义的数据。一个更新线程更新第 n 个或第 n+1 个元素(然后设置 n = n+1),还检查 n 是否太接近 N 并在必要时调用 vector::resize(N+M)。更新后,线程调用多个子线程最多读取第n个数据并进行一些计算。

保证子线程永远不会更改或删除数据,(实际上没有任何数据被删除)并且更新程序在完成更新后立即调用子线程。

到目前为止还没有出现问题,但是我想问一下,在将vector重新分配到更大的内存块的过程中是否会出现问题,如果上次更新留下了一些子工作线程。
或者在这种多线程情况下使用向量是否安全,因为它不是线程安全的?

编辑: 由于只有在更新程序调用 vector::resize(N+M,0) 时才会发生插入,所以我的问题有什么可能的解决方案吗?由于 STL 向量的出色性能,我不愿意用可锁定的向量替换它,或者在这种情况下是否有任何高性能、已知和无锁的向量?

【问题讨论】:

    标签: c++ multithreading stl vector thread-safety


    【解决方案1】:

    我想问一下,在将vector重新分配到更大的内存块时是否会出现问题,如果上次更新留下了一些子工作线程。

    是的,这样会很糟糕。

    如果您使用来自多个线程的容器,并且至少一个线程可能会执行一些可能会修改容器状态的操作,则必须同步对容器的访问。

    std::vector 的情况下,任何改变其大小(特别是插入和擦除)的东西都会改变其状态,即使不需要重新分配(任何插入或擦除都需要std::vector 的内部大小簿记数据更新)。


    您的问题的一个解决方案是让生产者动态分配std::vector 并使用std::shared_ptr<std::vector<T> > 拥有它并将std::shared_ptr 提供给每个消费者。

    当生产者需要添加更多数据时,它可以动态分配一个新的std::vector,它具有新的更大的大小以及旧std::vector的元素副本。然后,当您剥离新的消费者或使用新数据更新消费者时,您只需将std::shared_ptr 分配给新的std::vector

    【讨论】:

    • @James McNellis:是的。这是一个很好的建议。我可以自己重新分配。实际上向量被包装在一个类中,该类包含一个指向向量的指针。它不是 shared_ptr 但我可以轻松地构造一个新的更大的向量,从旧向量中复制元素,然后将其删除。那么复制大内存块的最快方法是什么。 CopyMemory()?
    • 使用std::deque 代替向量不是更简单的解决方案吗?这完全避免了重新分配,同时仍然提供几乎与向量相当的性能。
    • @jalf:我不认为使用std::deque 是安全的,因为重新分配不是唯一的问题。不能保证std::deque::operator[] 不会检查deque 内部的大小或任何其他簿记,因此有可能出现竞争条件,即消费者调用operator[],它读取内部状态,而生产者正在添加数据,这会修改内部状态。
    • 实际上,如果std::deque 被实现为数组的数组,这肯定是一个问题,因为保存指向数据数组的指针的数组需要偶尔重新分配。
    • @James:我认为这是不允许的,std::deque 被禁止在 pushpop 任一端的操作上随机播放数据。
    【解决方案2】:

    您的员工如何决定使用数据线程安全?工人完成和生产者之间是否有任何信号?如果不是,那么肯定存在一个问题,即生产者可能会导致向量在仍在处理时移动。虽然这可以通过移动到 std::deque 来轻松解决。(请注意,std::deque 使 push_back 上的迭代器无效,但对元素的引用不受影响)。

    【讨论】:

    • @stonemetal:工人和生产者之间没有信号。我将如何使用双端队列?
    • @stonemetal:我不使用 push_back() 插入数据。我调整它的大小然后打电话给vec[n] = X; 这有关系吗?
    • 这取决于您如何将工作分配给工人。你说你只追加到最后,所以你可以很好地使用 push_back 或根据需要调整大小,然后通过索引将工作分配给工作人员,因为你不推或弹出前面或擦除,你也可以分配工作通过一个指针块,因为元素被保证不会移动,尽管它们不能保证是连续的,所以你需要一个指向工作人员需要的每个元素的指针,而不是数组样式的开始和长度设置。迭代器在调整大小时失效,因此它们不是一个选项。
    • 仍然不安全。无法保证 operator[] 重载不会检查容器的大小或容器的其他内部状态簿记,因此存在潜在的竞争条件。
    • 天气存在竞争条件,工作人员是否将双端队列的长度视为 n 或 n+x,但由于他明确限制对
    【解决方案3】:

    我制作了自己的 GrowVector。它对我有用,而且速度非常快。

    链接:QList, QVector or std::vector multi-threaded usage

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-07
      相关资源
      最近更新 更多