【发布时间】:2016-12-08 02:57:05
【问题描述】:
我是多线程的新手,我正在尝试简单地使一些 std:lists 线程安全。每当将项目添加或删除到列表中时,执行 mutex.lock() 和 mutex.unlock() 就足够了吗?同样,我只是想让它们成为线程安全的。
谢谢
【问题讨论】:
标签: c++ multithreading thread-safety mutex
我是多线程的新手,我正在尝试简单地使一些 std:lists 线程安全。每当将项目添加或删除到列表中时,执行 mutex.lock() 和 mutex.unlock() 就足够了吗?同样,我只是想让它们成为线程安全的。
谢谢
【问题讨论】:
标签: c++ multithreading thread-safety mutex
请注意,C++ 没有定义线程安全,而是定义了数据竞争,这是在多个线程访问同一个对象并且至少其中一个对象时发生的情况是作家。
您可以使用互斥锁使std::list<> 的成员函数免于数据竞争。您甚至可以使用Wrapping C++ Member Function Calls technique by Bjarne Stroustrup 对任意对象执行此操作。这称为内部锁定,意味着对象维护自己的互斥体。
此方法不会使数据竞争泄露对对象内部状态的引用,例如引用、指针和迭代器。例如,当您迭代列表时,您不希望另一个线程通过删除元素来使您的迭代器无效,因此您必须保持互斥锁锁定直到迭代完成。
此外,在许多有用的场景中,需要原子更改的不仅仅是一个对象。在这种情况下,您需要一个可以序列化对多个对象的访问的互斥锁。
【讨论】:
std::list<> 线程安全,几乎所有对列表中数据的访问都需要迭代器(除了前面和后面这样的琐碎情况),所以不,你不能只使std::list 线程安全在列表方法中锁定互斥锁。
std::list<>用作队列或堆栈,则仅使用push_back、pop_front、pop_back。
std::map 读取数据一样,那么std::map 是线程安全的。
一般来说,是的。如果修改或读取列表的每个线程/路径在这样做之前都锁定了同一个互斥锁,则可以认为对列表的访问是线程安全的。
请注意,如果有人持有迭代器、引用或指向锁定范围之外的列表项的指针,则需要注意。在这种情况下,你就不再安全了。
【讨论】:
为了安全起见,您必须保护对列表的所有访问权限。虽然从没有锁的列表中读取不会损坏列表,但如果在另一个线程正在读取列表时修改了列表,则任何一个线程都可能损坏(即崩溃或产生不正确的结果)。
您必须为您希望内容稳定的整个代码段持有锁定。如果另一个线程可以随时擦除或重新排序任何元素,那么这包括任何时候您对其内容有实时迭代器。如果对哪些线程可以操作哪些元素存在限制,则可以放宽对保持活动迭代器的锁定要求。
使用std::lock_guard 可以帮助确保您正确管理锁。只需在将操作您的列表的任何作用域的开头创建它的一个实例,并且在作用域的末尾它会自动解锁,即使作用域通过异常退出也是如此。
【讨论】:
只要在列表中添加或删除项目时执行 mutex.lock() 和 mutex.unlock() 就足够了吗?
不,您必须同步所有访问,包括读取数据。您可以使用读/写锁来优化多个读取器的情况,但是您为更复杂的锁定付出的代价可能会吃掉您获得的所有好处,这取决于特定情况。
如果问题是“我可以将互斥锁放在列表中并使其成为线程安全的吗?”那么答案是否定的,你不能。如果您查看std::list 接口,您可以看到几乎所有对列表保存的数据的访问都是通过迭代器完成的,因此使std::list 线程安全将需要使这些迭代器线程安全,这是不可行的(即你会做什么如果指向已删除数据的迭代器被取消引用)。
【讨论】: