【问题标题】:Threadsafe Vector class for C++C++ 的线程安全向量类
【发布时间】:2009-07-08 17:39:11
【问题描述】:

有人知道用于 c++ 的快速而肮脏的线程安全向量类吗?我正在对一些代码进行多线程处理,我相信我遇到的问题与向量的使用方式有关。我计划重写代码,但在我疯狂地重做代码之前,我想用线程安全向量对其进行测试以确保它。我还想如果有这样的东西,它会比编写我自己的版本容易得多。

【问题讨论】:

    标签: c++ multithreading thread-safety


    【解决方案1】:

    由于算法,这很困难。

    假设您包装了 vector 以便它的所有成员函数都使用互斥锁进行序列化,就像 Java 同步方法一样。然后在该向量上并发调用 std::remove 仍然不安全,因为他们依赖于查看向量并根据他们看到的内容进行更改。

    因此,您的 LockingVector 需要在标准算法中专门化每个模板,以锁定整个事物。但是像std::remove_if 这样的其他算法将在锁定下调用用户定义的代码。一旦有人开始创建对象向量,这些对象本身在内部对所有方法进行锁定,就在幕后默默地执行此操作是锁定反转的秘诀。

    回答您的实际问题:对不起,不,我不知道。对于您需要的那种快速测试,我建议您从以下开始:

    template <typename T>
    class LockedVector {
        private:
        SomeKindOfLock lock;
        std::vector<T> vec;
    };
    

    然后将其作为替换容器放入,并开始实现成员函数(以及成员 typedef 和运算符),直到它编译为止。您会很快注意到,如果您的任何代码在向量上使用迭代器,而这种方式根本无法从内到外实现线程安全,并且如果需要,您可以在这些情况下临时更改调用代码以锁定向量通过公共方法。

    【讨论】:

    • 我希望不必自己编写,但看起来这可能是最快的方法。
    【解决方案2】:

    您可以查看TBB(如concurrent_vector)。虽然我从未使用过它,但老实说,我发现将范围保护对象放在访问周围更容易(特别是如果向量被正确封装)。

    【讨论】:

      【解决方案3】:

      我认为您会发现继续使用 std::vector 会容易得多,但使用某种互斥锁或其他操作系统同步对象来保护并发访问。如果您使用互斥体,您肯定也想使用 RAII。

      【讨论】:

      • 这是我最终打算做的,但是代码的编写方式,我在这个类中有10个向量,都是公共的(!),更不用说它们是直接访问和在其他代码中可能有几百个不同的地方发生了变异,所以当我用互斥锁重做代码时,这将是一个相当大的努力。现在我只想用一些工作的多线程版本(我可能必须编写)替换 std::vector 只是为了测试它并确保这是我的问题。
      • 我不羡慕你。如果您还不知道多个线程在哪里访问同一个向量,那么您不妨从一开始就考虑到多线程的设计重新开始。祝你好运。
      【解决方案4】:

      正如 Scott Meyers 在有效的 STL 书中所解释的,通过线程安全容器,您可以期望:

      • 多次读取是安全的
      • 对不同容器的多次写入是安全的。

      仅此而已。您不能指望许多其他事情,例如对同一个容器的多次写入是线程安全的。如果这就是您想要的,那么您可以查看STLPort。如果不是,那么我看到的唯一选择是将向量包含在一个同步访问向量的类中。

      【讨论】:

      • stlport 是一个公正的 STL 实现。它与并发无关。
      • STLPort 是一个提供(来自主页)“#异常安全和线程安全”的 STL 实现。 ALL STL 实现应该在设计上提供异常安全,但没有要求提供线程安全。 STLPort 可以。
      • 您可以通过“线程安全”查看 STLPort 的含义here. 基本上,它们的意思是 C++ 和/或硬件免费提供给您的东西——也就是说,Scott Meyers 的条件.库实现者搞砸这种“线程安全”的唯一方法是故意破坏。例如他可以添加一个全局变量x(不受锁保护)并在每次写入时触摸它,或者他可以添加一个被读取触摸的mutable成员,或类似的东西。
      【解决方案5】:

      我忘记了谁讨论过这个,但是制作线程安全容器的一种策略如下:

      1. 类的所有公共方法都必须锁定向量,并且如果成功(也可能不会成功!),必须返回一个布尔值。因此,不要使用f = myvec[i],而是使用if (myvec.tryGet(i, &amp;f)) {...} 并相应地实现该类。
      2. 不提供计数方法。用户必须使用迭代器来遍历向量。

      注意:小心迭代。您必须聪明地使用边界检查迭代器维护永不收缩的向量,否则您的代码可能会出现缓冲区溢出类型错误。

      提供“线程安全”向量的蹩脚而简单的方法是只采用标准向量并将向量锁定在每个方法上。但如果你这样做,你仍然可能会得到损坏的代码(例如,从 0 迭代到 vec.count 的循环在迭代时可能会改变计数)。

      提供“线程安全”容器的第二种方法是创建不可变容器(每个方法都返回一个新容器。这绝对是discussed by Eric Lippert。它是 C#,但大多数情况下很容易转换为 C++ 代码。你仍然会使用时需要锁定容器,但是当迭代器中断时所有涉及缓冲区溢出的可怕问题都会消失。实现不可变容器可能是函数式编程经验丰富的人的第二本能。

      【讨论】:

      • 锁定收藏的问题不是我。您可能会想到我的文章,关于我们如何重新调整 lock 语句在 C# 4 中的代码生成方式。
      【解决方案6】:

      如果您还没有,请考虑使用 tbb 库中的 concurrent_vector。 C++ STL 向量不是线程安全的,因此如果您打算从多个线程修改向量资源,我发现最简单的解决方案是使用 concurrent_vector。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-06-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多