C++ memory_order_consume, kill_dependency, depende

2019-04-10 07:02发布

问题:

I am reading C++ Concurrency in Action by Anthony Williams. Currently I at point where he desribes memory_order_consume.

After that block there is:

Now that I’ve covered the basics of the memory orderings, it’s time to look at the more complex parts

It scares me a little bit, because I don't fully understand several things:


How dependency-ordered-before differs from synchronizes-with? They both create happens-before relationship. What is exact difference?


I am confused about following example:

int global_data[]={ … };
std::atomic<int> index;
void f()
{
    int i=index.load(std::memory_order_consume);
    do_something_with(global_data[std::kill_dependency(i)]);
}

What does kill_dependency exactly do? Which dependency it kills? Between which entities? And how compiler can exploit that knowladge?


Can all occurancies of memory_order_consume be safely replaced with memory_order_acquire? I.e. is it stricter in all senses?


At Listing 5.9, can I safely replace

std::atomic<int> data[5]; // all accesses are relaxed

with

int data[5]

? I.e. can acquire and release be used to synchronize access to non-atomic data?


He describes relaxed, acquire and release by some examples with mans in cubicles. Are there some similar simple descriptions of seq_cst and consume?

回答1:

As to the next to last question, the answer takes a little more explanation. There are three things that can go wrong when multiple threads access the same data:

  1. the system might switch threads in the middle of a read or write, producing a result that's half one value and half another.

  2. the compiler might move code around, on the assumption that there is no other thread looking at the data that's involved.

  3. the processor may be keeping a value in its local cache, without updating main memory after changing the value or re-reading it after another thread changed the value in main memory.

Memory order addresses only number 3. The atomic functions address 1 and 2, and, depending on the memory order argument, maybe 3 as well. So memory_order_relaxed means "don't bother with number 3. The code still handles 1 and 2. In that case, you'd use acquire and release to ensure proper memory ordering.



回答2:

How dependency-ordered-before differs from synchronizes-with?

From 1.10/10: "[ Note: The relation “is dependency-ordered before” is analogous to “synchronizes with”, but uses release/consume in place of release/acquire. — end note ]".

What does kill_dependency exactly do?

Some compilers do data-dependency analysis. That is, they trace changes to values in variables in order to better figure out what has to be synchronized. kill_dependency tells such compilers not to trace any further because there's something going on in the code that the compiler wouldn't understand.

Can all occurancies of memory_order_consume be safely replaced with memory_order_acquire? I.e. is it stricter in all senses?

I think so, but I'm not certain.



回答3:

memory_order_consume requires that the atomic operation happens-before all non-atomic operations that are data dependent on it. A data dependency is any dependency where you cannot evaluate an expression without using that data. For example, in x->y, there is no way to evaluate x->y without first evaluating x.

kill_dependency is a unique function. All other functions have a data dependency on their arguments. Kill_dependency explicitly does not. It shows up when you know that the data itself is already synchronized, but the expression you need to get to the data may not be synchronized. In your example, do_something_with is allowed to assume any cached value of globalldata[i] is safe to use, but i itself must actually be the correct atomic value.

memory_order_acquire is strictly stronger if all changes to the data are properly released with a matching memory_order_release.