Consider the instructions that can be generated for an operator of type i++ . Of course, this will depend on your architecture / instruction set, but it will probably look like this:
LOAD @i, r0 ;load the value of 'i' into a register from memory ADD r0, 1 ;increment the value in the register STORE r0, @i ;write the updated value back to memory
Now consider how multi-threading will be implemented in the operating system, regardless of how many cores it has. At the most basic level of the OS, some means will be required to interrupt the execution of the current thread, save its state and perform a contextual transition to another thread. The OS does not have an automatic way to find out which instructions within a user thread should be considered an atomic operation, and has the ability to initiate a context switch between any two instructions.
So, what happens if the OS switches context from one thread to another between LOAD and ADD ? Let's say that i started with the value 0, so r0 will be set to 0 when the first thread is replaced. The OS will save this value as part of this thread state. Now the second thread executes and executes the same LOAD statement. The value in memory is still 0, so r0 is loaded again. 0. The thread increments the value and writes it back to the memory, setting the value of i to 1. Now the first thread resumes execution, and the operating system restores the value of r0 to 0 as part of its context switch. The first thread now performs its increment, setting r0 to 1, and the value 1 is again stored in i . Now the value of i is incorrect, because two increments were applied, but the value only increased by 1.
So in a nutshell, although i++ is a single statement in a high-level language, it generates several assembler instructions, and these instructions will not be treated as atomic in the operating system / runtime environment, unless you add additional synchronization logic around them.
source share