Guaranteed execution without the use of a volatile or protective barrier and memory locks

I have a question regarding a compiler changing the order of execution. I am trying to improve the performance of a multi-threaded program (C language) by replacing the critical section with a signaling mechanism (thorugh sketograph).

I need to guarantee the execution order here, and have done some research on this. I saw a lot of questions about the execution order inside the function, but did not discuss much about the function inside the function.

Based on https://en.wikipedia.org/wiki/Sequence_point of rule # 4, will the code snippet below guarantee that *p->a must be evaluated earlier than func2 , since func2 takes p as input (it is assumed that the compiler adheres to the rules of the schedule specified here)?

 func1 (struct *p) { p->a = x; func2 (p); } func2 (struct *p) { p->b = y; releaseSemaphore(s); } 

It is very important that p->b set only after p->a set, since another thread is in the processing cycle of a different request and identifies a valid request if p->b . The released semaphore starts the task only if it is inactive (and waiting for the semaphore), but if it is busy processing other requests, it will check p->b later, and we cannot guarantee that func1 is called only when this thread is idle .

+6
source share
2 answers

Not. A sequence of points in a sequence does not cross the boundaries of flows. This is the whole point of why we need guarantees of ordering memory in the first place.

The sequence order of the sequence is always guaranteed (modulo as-if-rule) for the thread that executes the code. Any other stream can observe the records of this stream in any order. This means that even if Thread # 1 can verify that it is writing in a specific order, Thread # 2 can still observe them in a different order. That is why volatility is also not enough here.

Technically, this can be explained, for example. on hiding places. Writing to Thread # 1 can first go to the write buffer, where they will still be invisible to Thread # 2. Only after the write buffer is flushed back to the main memory will they become visible, and the hardware can change the recording order before flushing .

Please note that just because the platform is allowed to reorder records does not mean that it will be. This is the dangerous part. Code that will work perfectly on one platform can break out from under blue when porting to another. Using the correct memory orders ensures that the code will work everywhere.

+2
source

An implementation can 1 reorder if this is not performed on function calls from other translation units.

This reordering is orthogonal to multithreaded, that is, it is performed both in single-threaded and in multithreaded programs.

If func2 is in the same translation block as func1, execution can be done like this:

 func1 (struct *p) { func2 (p); p->a = x; } 

Use volatile if you want to prevent 2 such reorders. (Note that this is done to prevent the reordering mentioned above, and not for other synchronization purposes. You will have to use atomic primitives for this.)


1 (Quoted from: ISO / IEC 9899: 201x 5.1.2.3 Program Execution 10)
Alternatively, an implementation can perform various optimizations within each translation unit, for example, that the actual semantics are consistent with abstract semantics only when making function calls across the boundaries of the translation unit.

2 (Quoted from: ISO / IEC 9899: 201x 6.7.3 Typical Classifiers 7)
An object that has a mutable type can be modified in ways that are unknown to the implementation or have other unknown side effects. Therefore, any expression that refers to such an object is evaluated strictly in accordance with the rules of an abstract machine, as described in 5.1.2.3. In addition, at each point in the sequence, the last value stored in the object agrees with what is prescribed by the abstract machine, with the exception of cases of unknown factors mentioned earlier.

+1
source

Source: https://habr.com/ru/post/1015645/


All Articles