Copy constructor for classes with atomic member

匿名 (未验证) 提交于 2019-12-03 01:12:01

问题:

I have a class with an atomic member and i want to write a copy constructor:

struct Foo {     std::atomic mInt;      Foo() {}     Foo(const Foo& pOther)     {         std::atomic_store(mInt, std::atomic_load(pOther.mInt, memory_order_relaxed), memory_order_relaxed);     } }; 

But i don't know which ordering i must use, because i don't know where and when this copy constructor will be called.

Can i use relaxed ordering for copy constructor and assignment operator?

回答1:

No, if you don't know how it will be used, you should use memory_order_seq_cst to be safe. If you use memory_order_relaxed, you could run into issues with instructions being reordered.



回答2:

You only need a stronger memory ordering than memory_order_relaxed, if your copy operation is supposed to synchronize with other operations on a different thread.
However, this is almost never the case, as a thread safe copy constructor will almost always require some external synchronization or an extra mutex anyway.



回答3:

The std::atomic template deletes its copy-constructor, because atomics are for shared state, so copying them to another atomic is usually not what you want.

Deleting the copy-constructor forces users of your class to think about what they're doing, and document that they're doing an atomic-load of one value and then passing that copy to somewhere else. (e.g. atomic var1 (var2.load())). See C++11: write move constructor with atomic member?


The constructor for std::atomic is not itself atomic, so it makes little sense to worry about the ordering of a store to it in your constructor (unless your constructor called a bunch of other functions and put the address of mInt somewhere that another thread could get it...)

Even better, use the copied value as an initializer, instead of doing an atomic store at all. (See also Nonlocking Way to Copy Atomics in Copy Constructor).

I think the only way this could be a problem is if you were doing something that's already undefined-behaviour, like using placement-new to construct a new Foo object in an already-shared location that could be read/written by other threads as you did this. This is obviously insane, so don't do that.

Having your class's memory-ordering behaviour match std::atomic's constructor (i.e. none for storing the initializer) seems like a good idea.


Only the caller knows whether sequential-consistency is required for the load from the source operand. Thus, you should let the caller choose by accepting a memory-order argument, with default=seq_cst (for consistency with std::atomic, not because that's what anyone is likely to want for this case). And yes, this is legal C++: copy constructor with default arguments

#include   struct Foo {     std::atomic mInt;      Foo() {}     Foo(const Foo& pOther, std::memory_order order = std::memory_order_seq_cst)         : mInt(pOther.mInt.load(order))     {} }; 

This compiles the way I expected: with ordering for the load but no ordering for the store. (e.g. looking at the asm output for ARM64 shows that the load uses ldar to do an acquire-load, but the store is just a simple str).

I tested it with this caller (Godbolt compiler explorer) which constructs one on the stack and then passes its address to a non-inline function that might make that address available to other threads. So it can't optimize away.

void extf(Foo &);    // non-inline function  void test(const Foo *p) {     Foo tmp(*p);     extf(tmp); } 

Whatever extf() does to make the address available to other threads should use a release-store, which ensures that any other thread which sees that address will see a properly-constructed Foo. This is a normal requirement, and is why it's totally fine that the initializer isn't even atomic.


Note that it's impossible to do a move between two different memory locations as a single atomic operation (in C++11 or on any hardware I'm aware of), so it's unlikely that a strong ordering will be useful.

Even defining whether such a move is atomic or not is problematic, because atomicity only exists in the eyes of an observer. Since it's impossible to observe two memory locations at the same time it's a meaningless concept. (Unless they're adjacent and you can get them both with a single atomic load).



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