please I have a question

Hi guys please I have this question

// ga is a global variable

int func_a() {
int a = ga;
return a>2 ? a-2 : 2-a;
}

int func_b() {
return ga>2 ? ga-2 : 2-ga;
}

// ga is a global variable

int func_a() {
int a = ga;
return a>2 ? a-2 : 2-a;
}

int func_b() {
return ga>2 ? ga-2 : 2-ga;
}
Are these two code snippets functionally identical in a multi-threaded or real-time operating system (RTOS) environment? Specifically, is the first code guaranteed to safely read the global variable ga only once, ensuring consistent behavior even if ga changes? This is not about data protection mechanisms like locks, but rather compiler behavior regarding register assignment and variable reading. @Middleware & OS
15 Replies
ygramoel
ygramoel9mo ago
These functions are equivalent. The compiler is allowed to optimize them as if your code is single threaded. In both cases, it is allowed to read once or multiple times. In a multicore environment, it can read from a local cache. There is no guarantee in either case that ga will only be read once. Also no guarantee that the value used will be the last value written to ga. The only (simple) way to make this code work predictably in a multi-threaded context is to use a mutex, and lock it before accessing ga
ygramoel
ygramoel9mo ago
Most programmers have a very simplistic model in their mind about of memory and registers. This is how we are taught to think, and the model is correct as long as you only reason about single threaded code. The actual hardware that executes the code is much weirder than you think, and includes multiple levels of caching. The hardware designers did a lot of effort to make sure that the result is as if the simple model is correct, but only for single-threaded code.
ygramoel
ygramoel9mo ago
If you really want to know what the compiler does, have a look at the assembly code it generates. The generated code will be different for different optimization levels, and also for different compilers. Do you want to write code that is only correct for some compilers?
boualleg sabrina
@ygramoel what is the mutex?
mad.maxii
mad.maxii9mo ago
Hey, Mutex is used in rtos , when one task is accessing the resources then we can prevent other task to access that resource by Mutex, when one task enter into a function it acquire mutex and do their work in that function and when that task is done it releases Mutex, then other task acquire mutex and enter into that same function, in this way we can prevent rtos issues
mad.maxii
mad.maxii9mo ago
Hey if ga is of same datatype i.e int , then yes the first code guaranteed safely read global variable ga as it is in a.
ygramoel
ygramoel9mo ago
Not true
techielew
techielew9mo ago
Why is it not true? Can you explain?
ygramoel
ygramoel9mo ago
Sure. Sorry I did not explain immediately, I was too busy. First, let's discuss what "safe" means. The OP did not specify this. I assume that "safe" in this context means that the behavior is the same regardless of which compiler or optimization level you use, and also when another thread is running that can access ga at any time. I am not denying that in most cases, func_a will read ga exactly once. I am only saying that this is not guaranteed. If we cannot assume a specific compiler or optimization level, we can only rely on a C standard specification. No C standard that I know defines read instructions; this is left to the compiler implementation. The code generated by a compiler for a call to func_a or func_b might contain between 0 and 3 read instructions for the memory address containing ga. For func_b, it might decide to read ga once or three times. Even for func_a, it might decide to read ga multiple times (although this is not probable, it would be allowed by the C standard). Moreover, for both functions, it might decide to inline the function at a call site. (Both functions are global, so it will also have to keep a non-inline copy). If ga is already available in a register at the call site, the compiler might decide not to read it again, so there will be zero reads at the call site. It could also be the case that multiple read instructions are required to read ga. For example, if int is a 4-byte value, the code could be compiled for a microcontroller that cannot read 4 bytes with a single instruction. Admittedly, this is not a common situation with modern processors.
ygramoel
ygramoel9mo ago
Even if the read is done in a single instruction, there is no guarantee that either function will read the latest value written to ga, if the write was done in another thread. The other thread might run on a different core on a multicore processor, and might be stuck in a cache of that core, so it is not available to the core running func_a or func_b. (If you do not understand what I am saying here, you might want to do some reading about modern processor architectures). This is why in a mutithreaded program, before accessing any data that is also accessible to other threads, you must lock a mutex (the same mutex for all threads accessing the same data). "Mutex" is short for "mutual exclusion"; it is an object, often provided by an RTOS, that you can lock and unlock. The implementation of the lock and unlock functions makes sure that cached data is synced. My conclusion is: the above code is not safe in a multithreaded environment.
ygramoel
ygramoel9mo ago
(I tried to write all the above in a separate thread, but that didn't work: I got "Your message could not be delivered. This is usually because you don't share a server with the recipient or the recipient is only accepting direct messages from friends.")
techielew
techielew9mo ago
That’s fantastic. Thank you! There’s a good beginner explanation of multicore processor architectures here: https://medium.com/@adityasinghz/multi-core-processor-architecture-7580bc347042
Marvee Amasi
Marvee Amasi9mo ago
As in if the compiler guarantees that the value of ga is read only once and used consistently throughout the execution of the function, regardless of any changes to ga made by other threads or processes.
ygramoel
ygramoel9mo ago
Thanks. For completeness, I'd like to add a note on 'volatile', as this directive is often misunderstood. If the global variable ga is declared volatile, as in: volatile int ga; then the compiler will actually generate a read instruction (or read instructions) for each use of ga. So func_a wil have one read instruction, and func_b wil have three. Also, each write to ga will generate a write instruction (or instructions). However, no code will be emitted to sync any cache, so this is not a solution for a multithreaded program. volatile is intended to be used (amongst others) for special function registers, where a read or write on the bus may actually trigger some hardware action in a peripheral.
Marvee Amasi
Marvee Amasi8mo ago
Thanks a lot on this
Want results from more Discord servers?
Add your server