Concurrency 101 : From a Beginners POV

10th May, 2025

Concurrency has always been a difficult topic for me to wrap my head around, eiter be it in golang, javascript or python. Python and JavaScript approach concurrency in different ways because of their runtime environments. In this blog I want to deep dive into theory of concurrency because if we understand the engineering behind these topics we can easily code out it any language, thats what I believe.

Concurrency is not about doing many things at once, but about managing multiple tasks in a way that makes progress on all of them, even if they take turns, in todays time it helps to build faster, more reponsive, and scalable applications.

What is Concurrency ??

It is the ability of a program to manage multiple tasks at the same time. Concurrency dosen't necessarily mean these tasks are happening simultaneously ( like in parallel processing), it means they're interleaved or coordinated to make progress together. In programming, concurrency lets our app respond to user clicks while downloading data or processing a file.

it makes our program faster, more responsive, and efficient. Without it, our app might freeze while waiting for a slow database query which will make the users frustate and leave our app.

You might get a question if concurrency is about managing multiple tasks, how does a program actually switch between them without causing chaos ??
So moving to our next topic

Switching between tasks in Concurrency

Programs switch tasks using mechanisms like threads, processes, or event loops, managed by the operating system or the runtime. The CPU can only do one thing at a time, but by rapidly switching, it feels like multitasking

Here is note from my side, context switching for processes is costlier than for threads due to memory management (e.g., updating page tables). Event loops minimize switching overhead by staying single-threaded, but they require tasks to be non-blocking. Example: In C, pthread_create spawns a thread with low overhead (~10µs), while fork() for a process takes ~100µs due to memory copying.

Okkay now after all this you might have a question if threads share memory, how do we prevent chaos when they access the same data simultaneously ??

Preventing chaos when threads access shared data

Shared memory threads risks race conditions, where the outcome depends on unpredictable execution order. Lets take two threads incrementing a shared variable counter = counter + 1. The operation involves three steps : read counter, increment, write back. If both threads readd counter as 10, increment to 11, and write, the final value is 11 instead of 12 due to overwriting.

These primitives are implemente using OS-level constructs or hardware instructions. However, they introduce overhead and risks like priority inversion, where a low priority thread holding a lock delays a high-priority one.

So now what is overhead, and if locks add overhead and complexity, what to do if threads get stuck waiting for each other ?? But before this we need to know ..

What happens when threads get stuck waiting for locks ??

Lock, like mutexes or semaphores are essential for preventing race conditions when threads access shared data, but they introduce challenges like deadlocks, livelocks and performance overhead, but first what is overhead ?, it refers to the extra computational cost or resources consume by a system, process or operation which is not directly linked to the main task.

But now raises the question how do we prevent all these locks from happening ?

If using lock is soo much hectic, can we coordinate tasks without using them at all ??

Cordinating tasks without Locks

There are two methods by which we can avoid locks that are lock-free programming and message passing.

We saw the problem with Locks and also solved it with Lock-free programming, now the question is with scalability for thousands of tasks

Scaling our concurrent program for many tasks

Event loops scale via OS multiplexing (epoll handles ~1M events), while thread pools optimize via work-stealing. Processes leverage distributed logs (e.g., Spark’s RDDs) for massive scale. Performance depends on workload: Input/Output-bound favors event loops, CPU-bound favors processes/threads.

Hope I was able to add value to your today's learning goal !! Happy Learning ..