Several ways to implement thread synchronization:
Two-dollar semaphore: One of the simplest locks, only two state-occupied and non-occupied resources for exclusive access by only one thread multivariate Semaphore (semaphore): a semaphore initialized to n allows up to n threads to access resources simultaneously Mutex: &NB Sp is similar to a two-dollar semaphore, but it can only be used to implement independent access to a resource that is not available for synchronization because the thread that acquires the mutex must release the mutex and the semaphore can be freed by another thread for synchronization critical section: & nbsp a more restrictive mutex than mutex, because it is visible only in this process and the mutex is visible in all processes other than the same mutex Read-write lock: There are three states free to share and exclusive they are individually accessed by the thread different permissions for flexible to meet those read the number of times write less resources access control can modify its read-write lock state after the thread has access to it Access is divided into shared and exclusive ways Free: Two ways to get access share: &NB Sp Share only access rights exclusive only wait for the current use thread to complete and modify the read-write lock status to get &NBSP ; condition variables: Registering an event at the same time, letting the thread wait for an event to trigger a condition variable to wake the waiting thread the event is triggered by another thread at some point a condition variable can be simultaneously waited by multiple threads When the condition variable is valid, the thread resumes execution   reentrant function: A function can be re-entered, it means that the function has not been completed, due to external factors or internal calls, again into the function of the main there are two cases 1. Multiple threads execute this function at the same time 2. The function calls itself directly or indirectly a function that is called reentrant. Surface the function is re-entered without any adverse consequences. This requires the function to meet a few conditions
- Do not use any (local) static or global non-const variables
- does not return any (local) static or global pointers to non-const variables
- dependent on caller-supplied arguments only
- Locks that do not depend on any single resource (mutex, etc.)
Do not call any non-reentrant functions if a function satisfies the above criteria then it is reentrant and can be safely used in a multithreaded environment
Compiler over-optimization and CPU dynamic scheduling:
Due to the over-optimization of modern compilers, sometimes we may not be able to guarantee thread safety even if we use locks reasonably, eg1 x = 0; & nbsp thread1 thread2 lock (); Lock (); x++; x++ unlock (); unlock (); After the above two threads have been executed, the value of x is not necessarily 2 Imagine the following operating procedure 1. THREAD1 read x to register A at this time [a]=0; 2.thread1 modify x x++ at this time [A] = 1; 3.thread1 deferred write back cache (compiler Optimization)  4.THREAD2 read x to register B (note that in thread2 it is not known that the value of x is placed in register a with a different register environment for different threads) at this time [B] = 0; 5.thread2 Modify X + + at this point [B] = 1; 6. Next Thread1 thread, regardless of the order in which it was written back, X gets the final value 1 eg2 x = y = 0; thread1 thread2 x = 1; y = 1; r1 = y;  R2 =x; So it seems that no matter how the execution R1 and R2 must have at least one for 1 however not necessarily due to the dynamic scheduling of the CPU, the CPU may exchange instructions in order to improve efficiency in order to increase the performance of the instruction. In addition, when the compiler is optimized, it is possible to exchange irrelevant two adjacent instructions for efficiency, that is to say, the final execution sequence is as follows x = y = 0; thread1 & nbsp;thread2  R1 = y; y = 1; x = 1;  R2 = x; Eventually we will get R1 = r2 = 0 for the above two issues we can use the volatile keyword to implement block compilation The translator over-optimizes it can do: 1. Prevents the compiler from caching a variable to register without writing back 2 in order to increase speed. Prevents the compiler from adjusting the sequence of instructions for a volatile variable Visible volatile can solve the first problem, but volatile does not solve the second problem, because even if the compiler's dynamic scheduling is blocked, the dynamic scheduling of the CPU cannot be prevented This can be //here via barrier Directive #define BARRIER () _asm_volatile ("Lwsync") barrier () Implementing the instruction flow separating its sides does not allow out-of-bounds exchange directives x = y = 0; thread1 thread2 &N Bsp x = 1; y = 1; barrier ()   barrier (); r1 = y;  R2 = x;
Thread safety is a very tricky issue. Even if you use the lock properly, you may not have the desired effect.
Let's take a look at the seemingly reasonable code
Copy CodeThe code is as follows:
x=0;
Thread 1 Thread2
Lock (); Lock ();
x + +; x + +;
Unlock (); Unlock ();
Do you think that after these two threads, a certain value of x equals 2? Yes, X + + execution is not interrupted because of the protection of Lock () and unlock (). (Why does the + + operation get disrupted by multithreading?) The reason is that the + + operation corresponds to multiple assembly code after it has been compiled into a compilation. However, the compiler may be because of the clever optimization, put X into the register (because the register speed), that is, when the Thread1 after X + +, is interrupted by the Thread2, but the value of 1 is only saved in the Register X, not written in the memory of the x variable. After the Thread2 execution completes, the value of x in memory equals 1, at which point the Thread1 is executed again, and the x in memory is written to 1.
It turns out the compiler is a ghost!
Look at one more example.
Copy CodeThe code is as follows:
x=y=0;
Thread1 Thread2
Y=1; X=1;
R1=x; R2=y;
When you pat your chest to worship your mm guarantee said: R1 or R2 at least one is 1, unfortunately the compiler again to stand on your opposite.
The reason is that as early as more than 10 years ago or decades ago, the compiler had such an optimization mechanism, in order to improve efficiency and exchange the sequence of instructions. So the code above might turn out like this:
Copy CodeThe code is as follows:
x=y=0;
Thread1 Thread2
R1=x; R2=y;
Y=1; X=1;
Know that you are wrong ~ Fortunately we also have volatile:
1. Prevent the compiler from keeping the variable cache in registers without writing back to memory in order to increase speed.
2. Prevent the compiler from adjusting the sequence of operation instructions
Haha, pity however persuasive, outsmart. CPU dynamic scheduling function, the CPU can exchange instruction sequence. The volatile can't help you, but the great Zeus invented for us: the barrier instruction (which is a CPU instruction) can help us block the CPU tuning operation instruction sequence.
Well, I think we've solved the problem of site safety at the moment.
There is a famous problem with the sequencing related to the double-check of the singleton pattern. The code is probably like this:
Copy CodeThe code is as follows:
Volatile singleton* singleton::_instance = 0;
Copy CodeThe code is as follows:
Static singleton& Instance () {
if (0 = = _instance) {
Lock Lock (_mutex);
if (0 = = _instance) {
_instance = new Singleton ();
Atexit (Destroy);
}
}
return *_instance;
}
Simply put, the compiler may rearrange the order of execution of instructions (compiler-based reorderings) for efficiency.
Look at this line of code:
_instance = new Singleton ();
In the case where the compiler is not optimized, the order is as follows:
1.new operator allocate the appropriate memory;
2. Construct the Singleton object on the allocated memory;
3. The memory address is assigned to _instance.
However, when the compiler optimizes the execution order, it might look like this:
1.new operator allocate the appropriate memory;
2. Memory address assigned to _instance;
3. Construct the Singleton object on the allocated memory.
When the compiler is optimized, the thread is suspended after it is executed to 2. Line Cheng began to execute and found that 0 = = _instance is false, so direct return, and then the Singleton object may not be completed, the consequences ...
Thread Safety Related concepts