When it comes to atoms, code similar to the following may be seen by everyone.
#include <stdio.h>#include<pthread.h>intCNT =0;void* Mythread (void*Arg) { inti; for(i=0;i<500000000; i++) CNT++; returnNULL;}intMain () {pthread_t id, id2; Pthread_create (&ID, NULL, mythread, NULL); Pthread_create (&id2, NULL, mythread, NULL); Pthread_join (ID, NULL); Pthread_join (Id2, NULL); printf ("cnt =%d\n", CNT); return 0;}
I think most people know that the result may not be 1000000000.
Test it.
Linux-p94b:/tmp/testhere # gcc Test1.c-lpthreadlinux-p94b:/tmp/testhere # for ((i=0;i<10;i++));d O./a.out; donecnt = 1000000000cnt = 1000000000cnt = 1000000000cnt = 1000000000cnt = 1000000000cnt = 1000000000cnt = 958925625cnt = 1 000000000cnt = 1000000000cnt = 1000000000
But do you really know what's wrong? What if I compile it and optimize it?
Linux-p94b:/tmp/testhere # Gcc-o2 Test1.c-lpthreadlinux-p94b:/tmp/testhere # for ((i=0;i<10;i++));d O./a.out; donecnt = 1000000000cnt = 1000000000cnt = 1000000000cnt = 1000000000cnt = 1000000000cnt = 1000000000cnt = 1000000000cnt = 1000000000cnt = 1000000000cnt = 1000000000
The speed of the operation suddenly became fast, and seemed to have been 1 billion.
Here, the Mythread CNT has been optimized to CNT + = 500000000 Since adding 500 million times
Well, of course it is, but it seems that there are some differences in the way we tried to test the atom, the same code, not the same compilation, but brought different results.
In fact, the reason is that we do not write a good code here, we did not express our original meaning, we are hoping that CNT really added 500 million times. So what do we do? Actually very good, in the definition of CNT before adding a volatile, then here for CNT's self-addition will not be optimized. Many times, why we optimize before and after the results of the optimization is not the same, often because the person writing code does not understand the program optimization rules. At the time of the last company, I would like to leave the time to give you a training, talk about the optimization of C language, at the same time said that we usually write the so-called unintentional reliance on the compilation of the alleged garbage code, but until left, I still did not do this training.
We added the volatile test,
Linux-p94b:/tmp/testhere # Gcc-o2 Test1.c-lpthreadlinux-p94b:/tmp/testhere # for ((i=0;i<10;i++));d O./a.out; donecnt = 635981117cnt = 675792826cnt = 522700646cnt = 593410055cnt = 544306380cnt = 630888304cnt = 580539893cnt = 6293600 72cnt = 555570127
We add a volatile before the CNT definition, the effect is even more obvious, because it is really self-added 500 million times, causing problems more opportunities. Wouldn't it be possible to have a 1 billion chance of not adding volatile and optimizing the compilation before?
The first thing we need to understand is that the cnt++ here is not an atomic operation, there is a possibility of scheduling at any time.
500 million times too much, we take a self-add 1 times as an example to illustrate that two threads are only 1 times, and expected the result is 2.
Cnt++ has at least three instructions in the generic processor, which we write with a pseudo-assembly.
CNT-to-reg//To load CNT from memory to register Reg
Reg+1-reg//Register REG ADD 1
Reg, CNT//write Reg content to memory
So
(thread 1) CNT--Reg
(thread 1) reg+1-reg
(thread 1) reg-, CNT
(thread 2) CNT--Reg
(thread 2) reg+1-reg
(thread 2) reg-, CNT
Ideally, we think that the implementation of the processor is the above, the result CNT value is 2.
But assuming that the schedule has occurred, the order of instruction execution is not like this, if it becomes the following
(thread 1) CNT--Reg
(thread 1) reg+1-reg
(thread 2) CNT--Reg
(thread 2) reg+1-reg
(thread 2) reg-, CNT
(thread 1) reg-, CNT
We'll figure it out again.
CNT = 0, reg arbitrary
(thread 1) CNT--Reg
CNT = 0, reg = 0
(thread 1) reg+1-reg
CNT = 0, Reg = 1
Dispatched here, Reg = 1 is saved and is valid after re-dispatching, and CNT does not control
After dispatch
CNT = 0, reg arbitrary
(thread 2) CNT--Reg
CNT = 0, reg = 0
(thread 2) reg+1-reg
CNT = 0, Reg = 1
(thread 2) reg-, CNT
CNT = 1, reg = 1
The dispatch here again, Reg will restore the previous saved 1, and CNT will not have any changes
So before executing the next instruction,
CNT = 1, reg = 1
(thread 1) reg-, CNT
CNT = 1, reg = 1
We can see that the result is 1, not 2, this is the result of non-atomic operation, in fact, before optimization to CNT + = 500000000 itself still has this problem, but it is difficult to observe.
Although X + + is not an atom, we can use locks to artificially create "atoms," such as mutual exclusion.
#include <stdio.h>#include<pthread.h>volatile intCNT =0;p thread_mutex_t Mutex=Pthread_mutex_initializer;void* Mythread (void*Arg) { inti; for(i=0;i<500000000; i++) {Pthread_mutex_lock (&mutex); CNT++; Pthread_mutex_unlock (&mutex); } returnNULL;}intMain () {pthread_t id, id2; Pthread_create (&ID, NULL, mythread, NULL); Pthread_create (&id2, NULL, mythread, NULL); Pthread_join (ID, NULL); Pthread_join (Id2, NULL); printf ("cnt =%d\n", CNT); return 0;}
Test it.
Linux-p94b:/tmp/testhere # Gcc-o2 Test1.c-lpthreadlinux-p94b:/tmp/testhere # for ((i=0;i<10;i++));d O./a.out; donecnt = 1000000000cnt = 1000000000cnt = 1000000000cnt = 1000000000cnt = 1000000000cnt = 1000000000cnt = 1000000000cnt = 1000000000cnt = 1000000000cnt = 1000000000
C language/Atom/compile, do you really understand?