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

别说谁变了你拦得住时间么 提交于 2020-08-04 10:20:37

问题


Whilst trying to work with std atomic pointer, I ran into the following. 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.

However I'm unsure about A and B. I suppose they are illegal since the pointer might be deferenced whilst performing the read operation.

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:


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 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.

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, e.g.

// 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.

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. 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. portable solution.




回答2:


// 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. 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...

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 loaded a pointer to - will be visible to you. The default is to ensure this, but you might want to read over this explanation of the memory_order parameter to 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. It doesn't matter that myString() might be updated to point to c before/during/after your call to b's .size().

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. You can obviously add such coordination in myriad ways (e.g. 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 (e.g. perhaps if b is known to be one of the newest entries in a generously sized LRU cache).

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

// 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.

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:


I'm pretty sure C is illegal because myString might be deleted in the meantime.

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.




回答4:


If another thread might modify or delete the string object, then all of these are illegal.

The use of atomic synchronises access to the pointer, but you're doing nothing to synchronise access to the object that it points to.



来源:https://stackoverflow.com/questions/30121194/is-it-safe-to-use-the-structure-dereference-operator-on-the-result-of-stda

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!