[Atomic] Rationale for preventing copy construction/assignment?
I've recently noticed that boost::atomic<T> prevents assignment from boost::atomic<T>, as well as copy construction. I'm having trouble understanding why this decision was made, as it makes boost::atomic<T> (and classes with a boost::atomic<T> member) very hard to store in containers, and I can't immediately see any safety issues that would be caused by allowing copy operations. What is the rationale for not allowing assignment or copy construction from another boost::atomic<T>?
On Wednesday 29 May 2013 13:22:09 Collin Dauphinee wrote:
I've recently noticed that boost::atomic<T> prevents assignment from boost::atomic<T>, as well as copy construction. I'm having trouble understanding why this decision was made, as it makes boost::atomic<T> (and classes with a boost::atomic<T> member) very hard to store in containers, and I can't immediately see any safety issues that would be caused by allowing copy operations.
What is the rationale for not allowing assignment or copy construction from another boost::atomic<T>?
Boost.Atomic tries to follow the standard interface closely, and it doesn't allow copying or assignment. Copying can be emulated if you explicitly cast or load the source atomic value and initialize the target with it. With containers, you can use emplace() to insert atomics.
AMDG On 05/29/2013 01:22 PM, Collin Dauphinee wrote:
I've recently noticed that boost::atomic<T> prevents assignment from boost::atomic<T>, as well as copy construction. I'm having trouble understanding why this decision was made, as it makes boost::atomic<T> (and classes with a boost::atomic<T> member) very hard to store in containers, and I can't immediately see any safety issues that would be caused by allowing copy operations.
What is the rationale for not allowing assignment or copy construction from another boost::atomic<T>?
How would you make it sequentially consistent without using a mutex? In Christ, Steven Watanabe
On 5/29/2013 8:49 PM, Steven Watanabe wrote:
AMDG
On 05/29/2013 01:22 PM, Collin Dauphinee wrote:
I've recently noticed that boost::atomic<T> prevents assignment from boost::atomic<T>, as well as copy construction. I'm having trouble understanding why this decision was made, as it makes boost::atomic<T> (and classes with a boost::atomic<T> member) very hard to store in containers, and I can't immediately see any safety issues that would be caused by allowing copy operations.
What is the rationale for not allowing assignment or copy construction from another boost::atomic<T>?
How would you make it sequentially consistent without using a mutex?
In Christ, Steven Watanabe
I don't view an assignment as a single operation, so I wouldn't expect both reading the source and storing it's value in the destination to be sequentially consistent. I would expect an assignment to be a 'convenience' function wrapping store and load, because as far as I'm aware, there is no way to do this sequentially without a mutex. My issue is stemming from wanting to store an object containing a few member variables, one being an atomic counter, in an std::map. The work around is to either implement the assignment operator just to accomodate the counter, or wrapping the counter in a class providing the same interface. Both result in bad code, and I think this is something an average user would run into fairly often. I imagine that the situation where a user actually cares about the sequential consistency of assignment would be very rare, and in the cases where the user does care, they would realize the cost of guaranteeing sequential consistency for an assignment and check the documentation. Again, this would be completely for convenience. I could definitely be wrong on all of this, though.
On May 30, 2013, at 12:59 AM, Collin Dauphinee
On 5/29/2013 8:49 PM, Steven Watanabe wrote:
On 05/29/2013 01:22 PM, Collin Dauphinee wrote:
I've recently noticed that boost::atomic<T> prevents assignment from boost::atomic<T>, as well as copy construction. I'm having trouble understanding why this decision was made, as it makes boost::atomic<T> (and classes with a boost::atomic<T> member) very hard to store in containers, and I can't immediately see any safety issues that would be caused by allowing copy operations.
What is the rationale for not allowing assignment or copy construction from another boost::atomic<T>?
How would you make it sequentially consistent without using a mutex?
I don't view an assignment as a single operation, so I wouldn't expect both reading the source and storing it's value in the destination to be sequentially consistent. I would expect an assignment to be a 'convenience' function wrapping store and load, because as far as I'm aware, there is no way to do this sequentially without a mutex.
Assignment is a load and a store, as you note, so it isn't atomic, which is the point of an atomic type, after all. Between the load and store, the source atomic's value can change, so the copy can be wrong, hence Steven's question.
My issue is stemming from wanting to store an object containing a few member variables, one being an atomic counter, in an std::map. The work around is to either implement the assignment operator just to accomodate the counter, or wrapping the counter in a class providing the same interface. Both result in bad code, and I think this is something an average user would run into fairly often.
By denying copying, atomics prevent such misguided ideas. When adding atomics to a container, or removing them later, their contents would be suspect at best. This is never a good thing to permit by default.
I imagine that the situation where a user actually cares about the sequential consistency of assignment would be very rare, and in the cases where the user does care, they would realize the cost of guaranteeing sequential consistency for an assignment and check the documentation. Again, this would be completely for convenience.
That's a very naive viewpoint. If one relies on complete understanding of documentation, which means the C++11 standard for std::atomics, few will be in the position you describe. Instead, the Right Thing (tm) is to deny unsafe usage at compile time. That forces one to abandon unsupported usage and may lead to reading more for better understanding. For your use case, you can design the copy constructor of your class to read-then-write the atomics' values to effect copying if you are certain such copying is synchronized or only done in one thread. ___ Rob (Sent from my portable computation engine)
Rob Stewart wrote:
Assignment is a load and a store, as you note, so it isn't atomic, which is the point of an atomic type, after all. Between the load and store, the source atomic's value can change, so the copy can be wrong, hence Steven's question.
I don't think that the copy can be wrong, although I may be missing something. From a cursory inspection, it seems to me that a1 = a2; and r1 = a2; a1 = r1; are equivalent. It's true that in the second case a2's value can change after the first line, but it can change in the first case after the only line as well (which corresponds to changing after the second line in the second case), and the observable effect is the same (except in the trivial case in which a1 and a2 are the same variable, but that's easily taken care of).
On May 30, 2013, at 5:34 AM, "Peter Dimov"
Rob Stewart wrote:
Assignment is a load and a store, as you note, so it isn't atomic, which is the point of an atomic type, after all. Between the load and store, the source atomic's value can change, so the copy can be wrong, hence Steven's question.
I don't think that the copy can be wrong, although I may be missing something. From a cursory inspection, it seems to me that
a1 = a2;
and
r1 = a2; a1 = r1;
are equivalent. It's true that in the second case a2's value can change after the first line, but it can change in the first case after the only line as well (which corresponds to changing after the second line in the second case), and the observable effect is the same (except in the trivial case in which a1 and a2 are the same variable, but that's easily taken care of).
While what you say is true, the two are different from a sequential consistency POV. Since the default for atomics is sequential consistency, such behavior would not be correct. ___ Rob (Sent from my portable computation engine)
On Thursday 30 May 2013 23:05:10 Rob Stewart wrote:
On May 30, 2013, at 5:34 AM, "Peter Dimov"
wrote: Rob Stewart wrote:
Assignment is a load and a store, as you note, so it isn't atomic, which is the point of an atomic type, after all. Between the load and store, the source atomic's value can change, so the copy can be wrong, hence Steven's question.> I don't think that the copy can be wrong, although I may be missing something. From a cursory inspection, it seems to me that> a1 = a2;
and
r1 = a2; a1 = r1;
are equivalent. It's true that in the second case a2's value can change after the first line, but it can change in the first case after the only line as well (which corresponds to changing after the second line in the second case), and the observable effect is the same (except in the trivial case in which a1 and a2 are the same variable, but that's easily taken care of). While what you say is true, the two are different from a sequential consistency POV. Since the default for atomics is sequential consistency, such behavior would not be correct.
Frankly, I don't quite understand this. The standard defines sequential consistency with regard to a single particular atomic value. I.e. both "a1 = a2" and "r1 = a2" must observe the effects of the last modification of a2 that happened before the operation. If you imply that some another side effect can happen in between "r1 = a2" and "a1 = r1" then that effect doesn't qualify that definition and need not be observed.
On 31/05/13 07:33, Andrey Semashev wrote:
On Thursday 30 May 2013 23:05:10 Rob Stewart wrote:
On May 30, 2013, at 5:34 AM, "Peter Dimov"
wrote: Rob Stewart wrote:
Assignment is a load and a store, as you note, so it isn't atomic, which is the point of an atomic type, after all. Between the load and store, the source atomic's value can change, so the copy can be wrong, hence Steven's question.> I don't think that the copy can be wrong, although I may be missing something. From a cursory inspection, it seems to me that> a1 = a2;
and
r1 = a2; a1 = r1;
are equivalent. It's true that in the second case a2's value can change after the first line, but it can change in the first case after the only line as well (which corresponds to changing after the second line in the second case), and the observable effect is the same (except in the trivial case in which a1 and a2 are the same variable, but that's easily taken care of). While what you say is true, the two are different from a sequential consistency POV. Since the default for atomics is sequential consistency, such behavior would not be correct.
Frankly, I don't quite understand this. The standard defines sequential consistency with regard to a single particular atomic value. I.e. both "a1 = a2" and "r1 = a2" must observe the effects of the last modification of a2 that happened before the operation. If you imply that some another side effect can happen in between "r1 = a2" and "a1 = r1" then that effect doesn't qualify that definition and need not be observed.
Suppose a1,a2 are initially zero Thread 1 then does: a2=1; //A ++a2; //B a1=3; //C r1=a1; //D Thread 2 does: a1=a2; //E If the assignment is atomic and SC then it must fit neatly into the list of SC ops. If the SC ordering is E>>A>>B>>C>>D then r1==3. If the SC ordering is A>>E>>B>>C>>D then r1==3. If the SC ordering is A>>B>>E>>C>>D then r1==3. If the SC ordering is A>>B>>C>>E>>D then r1==2. If the SC ordering is A>>B>>C>>D>>E then r1==3. If the assignment is split into a read and write then our assignment E is split into a read (F) and a write (G) which can occupy different places in our list. If the SC ordering is F>>A>>B>>C>>G>>D then r1==0. If the SC ordering is A>>F>>B>>C>>G>>D then r1==1. Neither of these are valid results if the assignment is a single atomic op. Since atomic ops covering multiple locations are seriously non-trivial without a mutex, it is unlikely that assignment would be done as an atomic op, and the results could therefore be surprising to people, since this would be inconsistent with the rest of the atomic interface. Anthony -- Author of C++ Concurrency in Action http://www.stdthread.co.uk/book/ just::thread C++11 thread library http://www.stdthread.co.uk Just Software Solutions Ltd http://www.justsoftwaresolutions.co.uk 15 Carrallack Mews, St Just, Cornwall, TR19 7UL, UK. Company No. 5478976
Anthony Williams wrote:
Suppose a1,a2 are initially zero
Thread 1 then does:
a2=1; //A ++a2; //B a1=3; //C
r1=a1; //D
Thread 2 does: a1=a2; //E
Yes, you are right. In fact, you can remove (B) from the example, it would still work.
Rob Stewart wrote:
On May 30, 2013, at 5:34 AM, "Peter Dimov"
I don't think that the copy can be wrong, although I may be missing something. From a cursory inspection, it seems to me that
a1 = a2;
and
r1 = a2; a1 = r1;
are equivalent. It's true that in the second case a2's value can change after the first line, but it can change in the first case after the only line as well (which corresponds to changing after the second line in the second case), and the observable effect is the same (except in the trivial case in which a1 and a2 are the same variable, but that's easily taken care of).
While what you say is true, the two are different from a sequential consistency POV.
I don't think so.
participants (6)
-
Andrey Semashev
-
Anthony Williams
-
Collin Dauphinee
-
Peter Dimov
-
Rob Stewart
-
Steven Watanabe