【问题标题】:Threading-Safe std:list C++线程安全的 std:list C++
【发布时间】:2016-12-08 02:57:05
【问题描述】:

我是多线程的新手,我正在尝试简单地使一些 std:lists 线程安全。每当将项目添加或删除到列表中时,执行 mutex.lock() 和 mutex.unlock() 就足够了吗?同样,我只是想让它们成为线程安全的。

谢谢

【问题讨论】:

    标签: c++ multithreading thread-safety mutex


    【解决方案1】:

    请注意,C++ 没有定义线程安全,而是定义了数据竞争,这是在多个线程访问同一个对象并且至少其中一个对象时发生的情况是作家。

    您可以使用互斥锁使std::list<> 的成员函数免于数据竞争。您甚至可以使用Wrapping C++ Member Function Calls technique by Bjarne Stroustrup 对任意对象执行此操作。这称为内部锁定,意味着对象维护自己的互斥体。

    此方法不会使数据竞争泄露对对象内部状态的引用,例如引用、指针和迭代器。例如,当您迭代列表时,您不希望另一个线程通过删除元素来使您的迭代器无效,因此您必须保持互斥锁锁定直到迭代完成。

    此外,在许多有用的场景中,需要原子更改的不仅仅是一个对象。在这种情况下,您需要一个可以序列化对多个对象的访问的互斥锁。

    【讨论】:

    • @Slava 显然,泄漏指针、引用和迭代器的成员函数不能成为线程安全的。但是您甚至懒得说明原因,所以您的评论几乎没有用。
    • OP 询问他是否可以使std::list<> 线程安全,几乎所有对列表中数据的访问都需要迭代器(除了前面和后面这样的琐碎情况),所以不,你不能只使std::list 线程安全在列表方法中锁定互斥锁。
    • @Slava 这取决于使用模式。例如,如果std::list<>用作队列或堆栈,则仅使用push_backpop_frontpop_back
    • 您不能说某些东西是线程安全的,具体取决于使用模式。否则一切都是线程安全的,就像你只从std::map 读取数据一样,那么std::map 是线程安全的。
    • @Slava C++ 标准没有提供线程安全的定义。虽然存在数据竞争,但特定的使用可能会导致数据竞争。
    【解决方案2】:

    一般来说,是的。如果修改或读取列表的每个线程/路径在这样做之前都锁定了同一个互斥锁,则可以认为对列表的访问是线程安全的。

    请注意,如果有人持有迭代器、引用或指向锁定范围之外的列表项的指针,则需要注意。在这种情况下,你就不再安全了。

    【讨论】:

    • 问题是 OP 想要将互斥锁保存在列表本身并使其成为线程安全的。
    • @Slava 对我来说是/不清楚问题的措辞方式。
    【解决方案3】:

    为了安全起见,您必须保护对列表的所有访问权限。虽然从没有锁的列表中读取不会损坏列表,但如果在另一个线程正在读取列表时修改了列表,则任何一个线程都可能损坏(即崩溃或产生不正确的结果)。

    您必须为您希望内容稳定的整个代码段持有锁定。如果另一个线程可以随时擦除或重新排序任何元素,那么这包括任何时候您对其内容有实时迭代器。如果对哪些线程可以操作哪些元素存在限制,则可以放宽对保持活动迭代器的锁定要求。

    使用std::lock_guard 可以帮助确保您正确管理锁。只需在将操作您的列表的任何作用域的开头创建它的一个实例,并且在作用域的末尾它会自动解锁,即使作用域通过异常退出也是如此。

    【讨论】:

    • 其实第二段是错的。列表迭代器实际上是稳定的。不过,我没有看到一个微不足道的编辑来使该段落正确。最好重写它。
    • @MSalters 在什么意义上?如果其他线程可能正在修改列表,则迭代器可能无效,或者这些迭代器引用的内容可能会以导致代码中断的方式发生变化。也许 stable 不是正确的词,但我认为这种说法大体上是正确的。
    • @MSalters 在我的第一段中,只有读取线程会损坏,因为示例没有说明涉及多个未受保护的写入器。这来自 OP,他说他只会锁定修改,而不是读取。
    • @nate 迭代器被认为是有效的,直到 发生并且“在另一个线程中发生 X,Y,Z 以外的事情”不计算在内。出于多线程的原因,您不需要保护迭代器的生命周期。这对于列表迭代器特别有用,列表 X,Y,Z 非常短。可能只有列表对 tbh 有好处
    • 如果您知道另一个线程不会写入您正在阅读的列表的子集,那么它是安全的
    【解决方案4】:

    只要在列表中添加或删除项目时执行 mutex.lock() 和 mutex.unlock() 就足够了吗?

    不,您必须同步所有访问,包括读取数据。您可以使用读/写锁来优化多个读取器的情况,但是您为更复杂的锁定付出的代价可能会吃掉您获得的所有好处,这取决于特定情况。

    如果问题是“我可以将互斥锁放在列表中并使其成为线程安全的吗?”那么答案是否定的,你不能。如果您查看std::list 接口,您可以看到几乎所有对列表保存的数据的访问都是通过迭代器完成的,因此使std::list 线程安全将需要使这些迭代器线程安全,这是不可行的(即你会做什么如果指向已删除数据的迭代器被取消引用)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-08-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多