Your proposal is worse!
shouldn't the two threads access the memory slot at the exact same time down to the nano second, every time
What you are describing here is something much, much worse than a race condition - you are describing contention. Contention is when two different circuits are trying to drive the same output at the same time.
Why contention is bad
Imagine the two cores are trying to write to a bit. Think of that bit as a wire. The first core is trying to write a 1
and the second core is trying to write a 0
. For the sake of simplicity let's assume a positive logic convention and a CPU core running at 1.2 volts. What happens to that wire when one of the cores is trying to drive that wire to 1.2v and the other core is trying to drive that wire to 0v?
Well, in terms of hardware design we say the value of that bit is unpredictable and in simulation software it will be marked with a value of "x" instead of "1", "0", "z" or "?" (true, false, high-impedence, unknown). But let's say we ignore a hardware designer's sensibilities and the warnings of circuit simulators. What is physically happening to that wire?
Practically anything can happen. The details depend on the exact design of the circuit and what type of transistor you are using to drive the output and even the position of stars in the sky (not literally but kind of true - everything from cosmic rays to the earth's magnetic field can have a tiny effect).
But let's say we design the circuit in such a way as to be stable in this case and not affected by silly things like the state of the universe. For simplicity let's make the circuit behave like a perfect voltage divider if a part of it is equally being sourced some voltage and is sinking voltage. So the value of the wire is 0.6v. If 1.2v has a value of 1
and 0v has a value of 0
what is the digital (binary) value of 0.6v?
What happens in the real world is that the memory cell will have a cut-off value of what it considers 1
and what it considers 0
. Let's say for the sake of simplicity our memory cell interprets anything lower than 0.6v as the value 0 and 0.6v and higher as the value of 1. So in theory if both cores is trying to write different values to memory the result should be 1
right? So is it that simple to solve?
Unfortunately no. Due to manufacturing defects that 0.6v cut-off is not exactly 0.6v. Some cells will have a cut-off at 0.6000001. Some will have a cut-off at 0.59999999 some will have a cut-off at 0.602311 etc. So in effect you cannot predict what the value will be in memory when two cores are trying to write different values to the same variable.
Can't we allow both to write?
But let's say we can design a circuit to safely solve the unpredictability. It's actually quite simple. We can combine the outputs of both circuits using something like an OR gate. So it is solved? In terms of hardware yes. In terms of how a programmer expects the CPU to behave no.
Consider the two cores are trying to write two different values to the same variable:
Thread 1 is trying to write the character "P"
Thread 2 is trying to write the character "F"
The binary value for "P" is 01010000
and the binary value of "F" is 01000110
. This results in the value in memory becoming "V":
01010000 = P
or 01000110 = F
-----------
01010110 = V
I assume the programmer who wrote the program does not expect the value "V" at all. I'd personally expect the variable to contain the value "P" or "F" and no other value. Isn't this a bug? I'd consider this a form of data corruption!
Real hardware avoid contention
Because of this very weirdly unexpected behavior, no real hardware will allow two source to have write access to the same memory at the same time (read access is no problem - we do this all the time with dual-ported memory in GPUs allowing the GPU to read the memory in parallel with the CPU). Generally the CPU will be designed so that only one source will have write access to the same area of memory.
There are many different techniques a hardware designer can use to select which source can access the memory. These techniques (algorithms) are generally called "arbitration". The simplest is to simply have a default priority: eg. core0 will always go first, core1 will go next etc.
If contention is solved, why do we have race condition?
While the root cause of a race condition is real-world timing and thus is a kind of hardware problem, the reason we experience it is not a hardware issue. If we know for a fact that there is a priority of which core gets to write a value to the same variable first and which one gets to write next we can design our logic to take this into account.
The problem is we don't write our software to run on CPUs. We write them to run on Operating Systems and the operating system we use will automatically allocate our threads to run on separate cores. So when we write our code, we don't know which thread gets to write to the variable first - and that fact of not knowing is the crux of the problem with race conditions.
Race condition is often not even an issue with CPU design!
When you asked this question you talk about CPU design and why they cause race conditions. The fact is, in the real world the timing problem does not involve the CPU at all.
Let's look at an example: say you are trying to download some data from two servers: SERVER_A and SERVER_B. For the sake of simplicity let's assume we are trying to get a list of IP addresses from a Bittorrent cloud.
SERVER_A has very good low latency. Pings to it respond almost immediately. So we connect to SERVER_A first and get a response from it first.
SERVER_B has bad high latency and takes a longer to respond to the connection. However, SERVER_B has a very high speed connection so even though SERVER_A answers first SERVER_B completes sending all the data before SERVER_A.
Assume SERVER_A sends:
22.44.1.2
128.77.0.43
Assume SERVER_B sends:
34.2.66.7
34.2.131.17
Assume we write our software without thinking about race conditions and write each response directly to file as they arrive. Because SERVER_A sends the response first the file will probably begin with 22.
but because SERVER_A is slow the file will also end with .43
instead of .17
!
So potentially the file could look something like:
22.434.24..61.62
7
34.21.28..131.107
.43
Instead of:
22.44.1.2
128.77.0.43
34.2.66.7
34.2.131.17
This is because of the race condition where SERVER_B's response is received while we are processing SERVER_A's response.
You can see that in this case the race condition has nothing to do with the CPU design or even your motherboard's design but is related to real-world timing. Quite literally a race condition bug is when a programmer forgets that his software runs in the real world where things are not as predictable as expected.