Golang: How are Data Races, Context Switching, and Multi-Core Computing interlinked
Pre-Requisites -> Functional Knowledge on Go Programming
In the age of the internet controlling our lives, the B2C companies started flourishing across the world. Be it anything from groceries to nappies everything was made online. There might be a thousand factors that make a product successful out of which Customer Experience is one.
When it comes to customer experience, a couple of things that make the customer love the product are un-interrupted service and faster response time.
Take an example where you opened GoIbibo for searching a flight and it takes a minute and finally says there are no flights running in the requested route, what would be the reaction of the customer ?? For me, I will never try to use the app again.
So, as a developer we do hell lot of things be it concurrent computing, parallel computing, multi-threading to reduce the response time.
In this blog post, I want to cover a couple of key things that are critical while we develop the back-end applications to cater to the ever-increasing load.
We often do most of the development in our local computer which doesn’t have production scale workload nor production scale infrastructure. So when we test the code in the local environment everything starts working as expected but once it goes live we see a ton of un-identified issues popping up. Out of all the issues that occur in production, Data Races are the toughest and the most time-consuming thing to debug.
So basically what’s a data race and how it defers from single core computing to multi core computing ?
From the above picture, when we test our code most probably we test it on a single-core CPU where one instruction is executed at a time. But when it goes to production we run it on multi-core CPU’s where all the parallelism comes into the picture. In picture-1 and picture-2, there is one instruction executing at a time, but in Picture-3 when both the instructions are executed in parallel, there is an explosion.
Enough with the theory, so let’s dive into some code and check what’s an instruction and what’s a data race and how it differs b/w single-core and multi-core machines.
Consider the above-mentioned piece of code wherein all it does to go through a loop and increment the count and print the count. We expect the output to be 10000 as the loop is being iterated for 10000 times. Let’s run the programme.
Hurray! I’ve got the response that I intended and pushed the code to production. ( ** My mac book is of 12 CPU core so I had to set the MAXPROCS to 1 CPU core for demonstration or else Go will use all the available CPU cores by default). It works in local perfectly fine but let’s give it a try in PROD .
Let’s try checking the same code again leaving the CPU to default which is 12 in my case.
I’ve executed the code thrice and we have to observe 2 things from the output.
- The output is corrupted.
- The output is in-consistent
This behavior is called a race condition where one can’t predict the output moreover at the same time it’s corrupted.
Interesting ain’t it? So let’s dig into some insights on this anomalous behaviour.
Code Running on Single Core Processor
When we run a loop for 10000 times and spun up 10000 go routines we believe that they are working in parallel but we are wrong, they work concurrently.
This is how it goes. All the goroutines will be pushed into a task queue kind of thing and the CPU serves one goroutine at a time. Whenever a goroutine had some synchronous piece of code, it then pushes that goroutine to the wait queue and starts serving the next goroutine and keep serving the task queue instructions until an instruction in wait queue is done executing. Once the wait queue instruction is done, it then starts serving that instruction and repeats the same cycle until there is no synchronous code and finally exit the goroutine.
If you see the above process, be it 1 lakh goroutines in the task queue with only one CPU to serve, there is no way where two instructions(goroutines) can access the memory location of count in parallel because CPU can take only one at a time. The process of switching execution of instructions multiple times to use the resources to fullest is called Context Switching.
Now lets try examine the same with multi core processor
If you look at the above example, multiple cores are working on multiple instructions(goroutines) at the same time and try to access the memory location of the counter.
Here when we say increment the counter, it gets the counter and update. So from the time where one core gets and updates the counter, there might be the other core that updates the counter which means Core-1 gets the value as 500, and by the time it updates the counter to 501, there is core-2 which already did the Job. So instead of the result being 502, Core-1 again tries to update it as 501.
This is where a race condition occurs as multiple cores trying to access and modify the same memory location.
Now that you got to know what data races are, how they differ from single-core to multi-core the next task would be to find a way to debug the programme and detect for any race conditions before the code goes live in production.
Stay Tuned for the next article on identifying the data races at compile time.