简体   繁体   English

在std :: atomic :: load的结果上使用Structure dereference( - >)运算符是否安全

[英]Is it safe to use the Structure dereference(->) operator on the result of std::atomic::load

Whilst trying to work with std atomic pointer, I ran into the following. 在尝试使用std原子指针时,我碰到了以下内容。 Say I do this: 说我这样做:

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());

I'm pretty sure C is illegal because myString might be deleted in the meantime. 我很确定C是非法的,因为myString可能会在此期间被删除。

However I'm unsure about A and B. I suppose they are illegal since the pointer might be deferenced whilst performing the read operation. 但是我不确定A和B.我认为它们是非法的,因为在执行读操作时指针可能会被引用。

However if this is the case, how can you ever read from an atomic pointer that might be deleted. 但是,如果是这种情况,您怎么能从可能被删除的原子指针中读取。 Since the load is 1 step, and the reading of the data is 1 step. 由于负载是1步,并且数据的读取是1步。

// A can I do this?
myString.load()->size()

Yes you can , but you do have a race condition if something else might be mutating or destructing/deallocating the string to which the snapshot of myString you received points. 是的,你可以 ,但是你确实有一个竞争条件,如果其他东西可能正在变异或破坏/解除分配你收到的myString快照所指向的string In other words, the situation after atomically retrieving the pointer is the same as for any std::string object to which multiple threads might have pointers, except that... 换句话说,原子检索指针后的情况与多个线程可能有指针的任何std::string对象相同,除了......

There is the question of whether the atomic load guarantees some particular construction/change to the string - perhaps performed by whichever thread updated myString to point to the particular string instance you've load ed a pointer to - will be visible to you. 存在这样的问题:原子load是否保证对string某些特定构造/更改 - 可能由更新myString指向您load指针的特定string实例的任何线程执行 - 将对您可见。 The default is to ensure this, but you might want to read over this explanation of the memory_order parameter to load() . 默认设置是为了确保这一点,但您可能希望将memory_order参数的这种解释读load() Note that not explicitly asking for memory synchronisation does not keep you safe from mutating/destruction by other threads. 请注意, 没有明确要求内存同步并不能保证您不会被其他线程变更/破坏。

So, say myString() is pointed successively at string 's a , b then c , and your code retrieves &b ... as long as the string b isn't mutated or destructed/deallocated while you're calling size() , you're ok. 所以,假设myString()连续指向string ab然后是c ,并且你的代码检索&b ...只要你调用size() string b没有被变异或被破坏/解除分配,你没事。 It doesn't matter that myString() might be updated to point to c before/during/after your call to b 's .size() . 在调用b.size()之前/期间/之后, myString()可能会更新为指向c并不重要。

Taking a step back, it can be tricky for the program to know how long after you call load() you might try to dereference the pointer, and if the b object is to later be mutated or destructed/deallocated, the kind of call you propose doesn't cooperate in any synchronisation around that later mutation/destruction. 退一步,程序知道在调用load()后多长时间你可能会尝试取消引用指针,如果b对象稍后被变异或被破坏/解除分配,那么调用你可能会很棘手。建议不会围绕后来的突变/破坏进行任何同步。 You can obviously add such coordination in myriad ways (eg some other atomic counter/flag, notifying the would-be modifier/destructor/deleter using a condition variable...), or you might decide to accept such a race condition sometimes (eg perhaps if b is known to be one of the newest entries in a generously sized LRU cache). 显然,你可以用无数种方式添加这种协调(例如一些其他原子计数器/标志,使用条件变量通知可能的修改器/析构函数/删除器......),或者你可能有时决定接受这样的竞争条件(例如也许如果已知b是一个宽大的LRU缓存中的最新条目之一)。

If you're doing something like cycling myString around a number of static const string instances, you don't have to worry about all the mutation/destruction stuff above (well, not unless you're accessing them before/after main() ). 如果你正在做一些像围绕许多static const string实例循环myString事情,你不必担心上面的所有变异/破坏(除非你在main()之前/之后访问它们) 。

// B can I do this?
char myFifthChar = *(myString.load()->c_str() + 5);

Yes, with all the caveats above. 是的,上述所有警告。

// C can I do this?
char myCharArray[255];
strcpy(myCharArray, myString.load()->c_str());

Yes, as above (and subject to the buffer provided being large enough). 是的,如上所述(并且提供的缓冲区足够大)。

I'm pretty sure C is illegal because myString might be deleted in the meantime. 我很确定C是非法的,因为myString可能会在此期间被删除。

As above - that concern's equally valid for all the 3 uses you've mentioned, just somewhat more likely for C because copying takes more CPU cycles to complete, and rather than get garbage values back losing a race could cause a buffer overrun. 如上所述 - 该问题对于您提到的所有3种用途同样有效,对C来说更有可能,因为复制需要更多的CPU周期来完成,而不是让垃圾值失去竞争可能导致缓冲区溢出。

I'm pretty sure C is illegal because myString might be deleted in the meantime. 我很确定C是非法的,因为myString可能会在此期间被删除。

The same is true of all of your examples. 所有例子都是如此。 The only thing that's safe because of an atomic load is the load itself- nothing more. 由于原子负载而唯一安全的是负载本身 - 仅此而已。 You are responsible for ensuring the safety of any subsequent operations on what was loaded. 您有责任确保任何后续操作的安全性。 And in this case, there is none, so it's terribly unsafe. 在这种情况下,没有,所以它非常不安全。

The only way to load from an atomic pointer is to ensure that you own the result- like std::shared_ptr<T> , or have it guaranteed to live for some longer lifetime and that you should ban all writing. 从原子指针加载的唯一方法是确保你拥有结果 - 如std::shared_ptr<T> ,或者保证它可以存活一段更长的寿命并且你应该禁止所有写入。

If another thread might modify or delete the string object, then all of these are illegal. 如果另一个线程可能修改或删除string对象,则所有这些都是非法的。

The use of atomic synchronises access to the pointer, but you're doing nothing to synchronise access to the object that it points to. 使用atomic同步访问指针,但是你没有做任何事情来同步对它所指向的对象的访问。

It has been mentioned that your approach is risky business. 有人提到你的方法是有风险的业务。 Here is what you may want to consider instead: Use std::shared_ptr<const std::string> with immutable values, and the shared_ptr atomic_load and atomic_store . 您可能需要考虑以下内容:将std::shared_ptr<const std::string>与不可变值以及shared_ptr atomic_load和atomic_store一起使用 std::shared_ptr will ensure you do not access a dangling pointer, while the immutability (string does not change after construction) will guarantee that accesses to the string itself are thread-safe, as all const methods defined by the standard are thread-safe. std::shared_ptr将确保您不访问悬空指针,而不变性(字符串在构造后不会更改)将保证对字符串本身的访问是线程安全的,因为标准定义的所有const方法都是线程安全的。

EDIT: As requested an explanation of what I mean by "risky business": If you use std::atomic<std::string *> , then it's easy to accidentally introduce race conditions, eg 编辑:根据要求解释“风险业务”的含义:如果使用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. */

Note that this has nothing to do with operator << , even just calling size() on the string in thread 1 would lead to the race condition. 请注意,这与operator <<无关,即使只是在线程1中的字符串上调用size()也会导致竞争条件。

In practice, one may see "fixes" like adding a sleep before the delete in the update with immutable strings, so that thread 1 has enough time to finish its business with the old pointer. 在实践中,人们可能会看到“修复”,例如在更新中使用不可变字符串在delete之前添加sleep ,以便线程1有足够的时间用旧指针完成其业务。 Although this may work most of the time in a particular implementation, it does not introduce a true ordering (a happens-before relation, in C++ standardese) and therefore is not a correct rsp. 虽然这在大多数情况下可能在特定实现中起作用,但它不会引入真正的排序(在C ++标准中发生之前的关系),因此不是正确的rsp。 portable solution. 便携式方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM