4 ms·
Not tearing, but it could reorder the assignment so that it happens someplace else than you think it will. For this kind of thread synchronization memory barrie
by biokoda 10y ago
Not tearing, but it could reorder the assignment so that it happens someplace else than you think it will. For this kind of thread synchronization memory barriers should be used at least.
- exDM69 10y agoExactly. To give a concrete example (but artificial) what might happen, consider this case: global_flag = 0; for(this loop takes a long time) { ... } global_flag = 1; for(this loop takes a long time) { ... } global_flag = 0; If it is trying to communicate something to another thread (or audio callback) with global_flag, it might fail miserably because the compiler is free to drop two of the assignments and place `global_flag = 0` wherever in the code. Using `volatile` should prevent this compiler optimization but it doesn't give any guarantees about cache coherency, etc. As far as I know, using atomic load and store is the only way (apart from locks) to guarantee correctness and portability.
- Bromskloss 10y agoI thought _tearing_ referred to a value being read while in the midst of being updated, so that, for example, some of the read bits comes from the old value and some from the new value.
- barrkel 10y agoIt does. It's a distinct issue and still very relevant if you're using shared memory without locks to communicate between threads.
- cesarb 10y agoElaborating on your example. my_buffer->something = 42; global_pointer = my_buffer; global_flag = 1; Even if everything is declared `volatile`, in C or C++, the compiler or the processor can reorder the stores and reads, so that the reading thread can see the "global_pointer = my_buffer" before the "->something = 42". The only safe way to do it is to add the appropriate memory barriers on both the writing and reading sides, which force the compiler and the processor to not reorder the writes/reads. my_buffer->something = 42; write_barrier(); // Not the actual function; will vary depending on your environment. global_pointer = my_buffer; write_barrier(); // Not the actual function; will vary depending on your environment. global_flag = 1; And on the reading side, the corresponding read barriers. It's simpler to use atomic loads and stores or locks, since their implementation already has the required barriers with all the details (quick: what's the difference between an acquire and a release atomic access?). (Note that this is different in Java, where `volatile` always implies a memory barrier.)
- exDM69 10y ago> what's the difference between an acquire and a release atomic access ACQUIRE prevents reordering any loads and stores from after the barrier to before. RELEASE is the opposite, no load or store before it may happen after the barrier. ACQUIRE is what you use when locking a spinlock, RELEASE is when you unlock. Put these incorrectly and you're not guaranteed to have all loads and stores happening when the spinlock is locked, and your program is no longer guaranteed to behave as expected.