【问题标题】:Standard way to make STL objects threadsafe?使 STL 对象线程安全的标准方法?
【发布时间】:2011-06-05 18:51:18
【问题描述】:

我需要几个 STL 容器,线程安全。

基本上我在想我只需要向每个 STL 容器对象添加 2 个方法,

.lock()

.unlock()

我也可以分解成

.lockForReading()
.unlockForReading()
.lockForWriting()
.unlockForWriting()

可行的方式是任意数量的并行读取锁都是可以接受的,但如果有一个写入锁,则读取和写入将被阻止。

锁定写入的尝试会等待,直到 lockForReading 信号量降至 0。

有标准的方法吗?

我打算这样做是错误的还是短视的?

【问题讨论】:

    标签: c++ multithreading stl thread-safety


    【解决方案1】:

    这真的有点糟糕。外部代码将无法识别或理解您的线程语义,而且容器中对象的别名很容易获得,这使得它们的线程安全接口很差。

    线程安全发生在设计时间。你不能通过对问题加锁来解决线程安全问题。您可以通过不让两个线程同时写入相同数据来解决线程安全问题——当然,在一般情况下。但是, 特定对象不负责处理线程安全,直接线程同步原语除外。

    可以拥有并发容器,旨在允许并发使用。但是,它们的接口与标准容器提供的接口非常不同。例如,容器中对象的别名更少,并且每个单独的操作都被封装。

    【讨论】:

    • 是的,听起来不错。如果对这些共享队列的读/写相对不频繁/对性能不重要,那么避免错误对我来说比其他任何事情都重要——你说的这些并发容器在哪里?
    • @bobobobo:您可以在 Intel 的 Thread Building Blocks 和 Microsoft 的 Parallel Patterns Library(两者都是专有的)中找到它们。如果你有勇气的话,你也许可以找到各种使用 Google 的实现,或者根据他们的公开文档推出自己的实现。
    • 现在你可以download TBB,开源
    • @bobobobo:哦,我以为是商业广告。如果可以的话,那就这样做吧。
    【解决方案2】:

    执行此操作的标准方法是在构造函数中获取锁,并在析构函数中释放它。这通常称为资源获取即初始化,或 RAII。我强烈建议您使用这种方法,而不是

    .lock()
    
    .unlock()
    

    这不是异常安全的。您很容易忘记在抛出之前解锁互斥锁,从而在下次尝试锁定时导致死锁。

    Boost.Thread 库中有几种对您有用的同步类型,notablyboost::mutex::scoped_lock. 而不是将lock()unlock() 方法添加到您希望从多个线程访问的任何容器中,我建议您在访问容器时使用boost:mutex 或等效项并实例化boost::mutex::scoped_lock

    【讨论】:

    • 我认为这与问题无关。
    • @Diego 建议一些更改或提供您自己的答案。使用 RAII 锁定互斥锁与 OP 的问题完全相关。
    • +1。 RAII 惯用语很好地服务于任何具有获取/释放语义的东西。 @bobobobo:你想要的不是信号量,而是R/W lock
    • @Sam:不,同样,RAII 与此无关。 RAII 谈到何时以及如何锁定和释放互斥锁。他或她正在询问为标准容器提供标准线程安全的方法是什么。添加锁定/解锁(可撤销)或其他方式。
    • @bobobobo:是的,有 - 互斥锁的锁定和解锁。米勒是对的——如果你选择这条路线,在这种情况下应该使用 RAII 来确保互斥体的解锁。也就是说,您将拥有某种LockObject Lock(),当LockObject 被销毁时会自动解锁,并且不会提供明确的Unlock() 调用。
    【解决方案3】:

    有标准的方法吗?

    不,这是有原因的。

    我打算怎么做 错误或短视?

    想要同步访问单个容器对象不一定是错的,但是容器类的接口经常是放错地方了同步(like DeadMG says:对象别名,等等)。

    我个人认为TBBconcurrent_vector 之类的东西可能是矫枉过正,或者仍然是“简单”同步问题的错误工具。

    我发现通常只添加一个(私有)Lock 对象(到持有容器的类)并将 2 或 3 个访问模式包装到一个容器对象就足够了,并且对于其他人来说更容易掌握和维护在路上。

    【讨论】:

    • 好吧,concurrent_vector 的重点是不必担心push_back 和读取操作的同步。它并不过分。但是concurrent_vector 没有.erase() 方法,如果你要使用.clear(),你必须知道它不是线程安全的。所以它并不像人们希望的那样无忧无虑。
    • 另外,即使你使用concurrent_vector,你也很有可能仍然会遇到更高级别的并发问题,因为不同线程中的函数仍然会相互踩踏,因为它们修改数据。所以 Martin 的设计建议相当不错。
    【解决方案4】:

    Sam:你不想要.lock() 方法,因为可能会出错,阻止在块末尾调用.unlock() 方法,但是如果.unlock() 被调用作为对象破坏的结果堆栈分配的变量,然后从调用.lock() 的函数的任何类型的提前返回都将保证释放锁。

    DeadMG: 英特尔的Threading Building Blocks (open source) 可能就是您要找的。​​p>

    还有 Microsoft's concurrent_vector and concurrent_queue,它已经随 Visual Studio 2010 提供。

    【讨论】:

    • 这是评论吗?
    • 这是公认的答案!!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-24
    • 1970-01-01
    • 2011-08-20
    相关资源
    最近更新 更多