提问人:zalanshah64 提问时间:6/10/2023 更新时间:6/10/2023 访问量:165
为什么 C++ std 库本质上不是线程安全的?
Why is the C++ std library not inherently thread safe?
问:
我知道,从根本上说,在 C++ 中使用类不是线程安全的,因为它们的函数不是原子的,这可能会导致竞争条件。
在学习在类中编写多线程程序时,我们必须将对象读取/写入的关键部分包装在互斥锁中,以确保原子性。在花时间阅读了一些标准库之后,我很好奇为什么 C++ 不会自动为用户执行此操作。
我的理解是,互斥锁只包含一个表示锁状态的布尔值或整数,以及某种形式的队列,表示锁上等待的所有线程。既然这不是一个很大的开销,为什么标准库不自动提供这种抽象,并用一个仅对该类本地的互斥锁(即不是全局互斥锁)包装它的所有函数?
答:
即使忽略性能问题(这是不容忽视的),线程安全也绝不会像“所有对象都有一个互斥锁”那么简单。
假设您有一个线程安全容器。有一个容器对象被共享。那么,这个代码是“线程安全的”吗?
shared_container.push_back(value1);
shared_container.push_back(value2);
嗯,这取决于:如果此代码的意图是并且彼此相邻,则不能保证该行为。文本文本不会导致数据争用,但也不会具有所需的行为。value1
value2
代码的目的是按照它所说的去做。如果不这样做,无论是由于数据争用还是违反代码的预期行为,都是一个错误。
那这个呢:
shared_container.emplace_back(param1, param2);
切记:在函数调用中调用构造函数(容器的元素类型)。如果该构造函数尝试访问 ,会发生什么情况?不能将对象锁定在其他线程上。emplace_back
shared_container
或者更好的是,如果该构造函数需要访问其他一些共享对象,但保持该对象上的锁的线程在等待 的锁时被阻塞,会发生什么情况?您的线程拥有哪个并且无法释放,因为您处于 ?shared_container
emplace_back
您不能在不考虑线程安全性的情况下运行多线程。你可以划分线程间的通信,但你永远不能不去想它。默认情况下,将所有对象设置为“线程安全”只会提供安全的错觉。更糟糕的是,它使追踪错误变得更加困难,因为它们更加微妙。
我们甚至可以从标准库中看到一个更合理的线程安全思维的例子。 是一种将单个对象从一个线程转发到另一个线程的方法。但剥离到它的基本概念,你可以把它看作是你在线程之间共享的“线程安全”。promise/future
optional<T>
但它们不是一回事。
promise/future
具有非对称接口。一旦保存 的代码设置了该值,它就无法访问或修改该值。保存 的代码不能操作该值;他们只能查看是否已设置或访问它。接口说有一个值的提供者和一个值的接收者,并且该值只能单向。promise
future
共享没有对这种值转发的接口级支持。如果您有权访问该对象,则可以根据需要设置或获取该值。但这通常不是数据管理跨线程的工作方式,而且与 相比,它肯定不那么“安全”。optional<T>
promise/future
安全的线程间通信最好通过使用专门为此而设计的对象来完成。其接口支持特定线程间通信模式的对象,这些对象具有非常严格的线程间交互。
评论
if (!container.is_empty()) container.do_something_with_first_element();