对 std::atomic::load 的结果使用结构取消引用 (->) 运算符是否安全
Is it safe to use the Structure dereference(->) operator on the result of std::atomic::load
在尝试使用std原子指针时,我遇到了以下内容。假设我这样做:
std::atomic<std::string*> myString;
// <do fancy stuff with the string... also on other threads>
//A can I do this?
myString.load()->size()
//B can I do this?
char myFifthChar = *(myString.load()->c_str() + 5);
//C can I do this?
char myCharArray[255];
strcpy(myCharArray, myString.load()->c_str());
我很确定C是非法的,因为在此期间myString可能会被删除。
但是我不确定 A 和 B。我想它们是非法的,因为在执行读取操作时指针可能会被尊重。
但是,如果是这种情况,您怎么能从可能被删除的原子指针中读取。由于加载是 1 步,数据读取是 1 步。
// A can I do this?
myString.load()->size()
是的,您可以,但如果其他内容可能正在变异或破坏/解除分配您收到myString
快照指向的string
,则您确实存在竞争条件。 换句话说,以原子方式检索指针后的情况与多个线程可能具有指针的任何std::string
对象的情况相同,只是......
有一个问题是,原子load
是否保证对string
的某些特定构造/更改 - 可能由更新myString
以指向您load
指向的特定string
实例的任何线程执行 - 将对您可见。 默认设置是确保这一点,但您可能需要阅读此memory_order
参数的说明以load()
。 请注意,不显式要求内存同步并不能防止其他线程发生突变/破坏。
所以,假设myString()
依次指向string
的a
,b
然后c
,你的代码检索到&b
......只要string
b
在你打电话时没有变异或破坏/解除分配size()
,你就没问题。myString()
可能会更新为指向c
之前/期间/之后,这并不重要,因为您拨打b
.size()
。
退一步说,程序可能很难知道在你调用load()
后多久你可能会尝试取消引用指针,如果b
对象稍后要发生突变或破坏/解除分配,你提出的调用类型不会在以后的突变/破坏的任何同步中合作。 显然,您可以通过多种方式添加此类协调(例如,其他一些原子计数器/标志,使用条件变量通知潜在的修饰符/析构函数/删除器......),或者您可能决定有时接受这样的竞争条件(例如,如果已知b
是大尺寸 LRU 缓存中的最新条目之一)。
如果您正在做一些事情,例如在多个static const string
实例周围循环myString
,则不必担心上面的所有突变/破坏内容(好吧,除非您在main()
之前/之后访问它们)。
// B can I do this?
char myFifthChar = *(myString.load()->c_str() + 5);
是的,有上面的所有警告。
// C can I do this?
char myCharArray[255];
strcpy(myCharArray, myString.load()->c_str());
是的,如上所述(并且受提供的缓冲区足够大的限制)。
我很确定C是非法的,因为在此期间myString可能会被删除。
如上所述 - 这种担忧对您提到的所有 3 种用途同样有效,只是对于 C 来说可能性更大,因为复制需要更多的 CPU 周期才能完成,而不是恢复垃圾值,输掉比赛可能会导致缓冲区溢出。
我很确定C是非法的,因为myString可能会在 同时。
您的所有示例也是如此。由于原子负载,唯一安全的是负载本身 - 仅此而已。您有责任确保对加载的内容进行任何后续操作的安全性。在这种情况下,没有,所以它非常不安全。
从原子指针加载的唯一方法是确保您拥有结果 - 像std::shared_ptr<T>
一样,或者保证它存活更长的生命周期,并且您应该禁止所有写入。
有人提到你的方法是有风险的。以下是您可能需要考虑的事项:使用具有不可变值的std::shared_ptr<const std::string>
,以及shared_ptr
atomic_load和atomic_store。std::shared_ptr
将确保您不会访问悬空指针,而不变性(字符串在构造后不会更改)将保证对字符串本身的访问是线程安全的,因为标准定义的所有const
方法都是线程安全的。
编辑:按照要求解释我所说的"风险业务"的含义:如果您使用std::atomic<std::string *>
,那么很容易意外引入竞争条件,例如
// Data
std::atomic<std::string *> str(new std::string("foo"));
// Thread 1
std::cout << *str.load();
// Thread 2
*str.load() = "bar"; // race condition with read access in thread 1
// Thread 2 (another attempt using immutable instances)
auto newStr = new std::string("bar");
auto oldStr = str.exchange(newStr);
delete oldStr; /* race condition with read access in thread 1
because thread 1 may have performed load() before
the exchange became visible to it, and may not
be finished using the old object. */
请注意,这与operator <<
无关,即使只是在线程 1 中的字符串上调用size()
也会导致竞争条件。
在实践中,人们可能会看到"修复",例如使用不可变字符串在更新中的delete
之前添加sleep
,以便线程 1 有足够的时间使用旧指针完成其业务。尽管这可能在特定实现中大部分时间都有效,但它不会引入真正的排序(在标准语中是发生之前的关系C++因此不是正确的 rsp。便携式解决方案。
如果另一个线程可能会修改或删除string
对象,那么所有这些都是非法的。
atomic
会同步对指针的访问,但你不会同步对指针指向的对象的访问。
- 从不同线程使用int64的不同字节安全吗
- 将数组作为参数传递给函数安全吗?作为第三方职能部门,可以探索他们想要的之外的其他元素
- 虚拟决赛作为安全
- 获取日期异步信号安全吗?如果在信号处理程序中使用,它会导致死锁吗
- 如何将元素添加到数组的线程安全函数?
- C++中的线程安全删除
- 通过网络、跨平台传递std::变体是否安全
- 在std::thread中,joinable()然后join()线程安全吗
- 使用std::istream::peek()总是安全的吗
- EASTL矢量<向量<int>>连续的
- 从值小于256的uint16到uint8的Endian安全转换
- 在c++队列中使用pop和visit实现线程安全
- 在类型和包装器之间reinterpret_cast是否安全<Type>?
- 以线程安全的方式调用"QQuickPaintedItem::updateImage(const QImage&image)"(no QThread)
- 全局变量 多读取器 一个写入器多线程安全?
- 安全到标准:移动会员?
- AcquireCredentialsHandleA() 返回 PFX 文件的0x8009030e(安全包中没有可用的凭据
- 共享队列的线程安全
- boost::文件系统::recursive_directory_iterator多线程安全
- 跨 DLL 边界访问虚拟方法是否安全/可能?