What is concurrency?

Concurrency is a methodology that we implement to enable a server to simultaneously handle/manage multiple requests and create an illusion that all the requests are processing simultaneously. Concurrency can help achieve Parallel processing. But what is parallel processing? Parallel processing means that a server is able to process many requests at the same time. To understand this better, let’s backtrack to revisit the basics.

After you write your code instructions, you save it, and the code compiles itself into a language that your computer can understand. When you run the compiled code, your computer takes the compiled code and loads it into memory. The act of the application code running is called a process. The process takes up some space in your computer and controls/uses a thread to run the application code’s logic on the CPU’s core. A thread has the following:

  • A stack (for your methods in your code)
  • Heap (allocated memory to run your code)
  • Data (for your variables in code)
  • Text (for program counters)

Single-threaded application

A single-threaded application has one process with the control of one thread. There is one program counter, one stack, one heap, and the single-threaded application can carry out one single instruction at a time. Regardless of whether a CPU has one or multiple cores, a single-threaded application can run only one core.

Single-threaded Programming Languages

  • Javascript – achieves concurrency using event loops and event queues.

Multi-threaded application

A multi-threaded application has one process with the control of multiple threads. Each thread has a program counter, stack, heap, and data. When a process has multiple tasks to perform, it can spin off threads to independently carry out instructions.

Multi-threaded Programming Languages

  • Java
  • C#
  • GoLang

Threads on Single-Core vs Multi-Core CPU

In a CPU with a single-core, the threads in a process would take turns running on the core. Example:

  • Core 1: Thread 1, Thread 2, Thread 1, Thread 4, Thread 1

In a CPU with multiple-core, the threads would take turns running on the core in different cores simultaneously, allowing parallelism. Example:

  • Core 1: Thread 1, Thread 5, Thread 1, Thread 5
  • Core 2: Thread 2, Thread 4, Thread 4, Thread 2

Multi-threaded programming languages use threads (like user-level threads or kernel-level threads) to achieve parallelism in a multi-core machine.

User-Level Threads

User-level threads are managed by the users/application/language runtime and reside inside a process. The kernel is not aware of these user-level threads, and it only sees one thread and processes one. Thus, it cannot use the multi-core and can’t achieve parallelism. The user-level threads are faster than kernel-level threads because the application manages them, not the kernel. The entire process is blocked if the thread performs a blocking operation.

Kernel-Level Threads

The kernel manages the kernel-level threads. Process threads are managed by the kernel as well; process threads are threads that belong to a process. Threads, in general, can exist by themselves. The advantage of a kernel-level thread is that it can be managed by the kernel and scheduled to run in parallel amongst different cores. If a kernel level-thread of a process is blocked, another thread from the same process can be scheduled. Kernel-level threads are slower than user-level threads because context switching between threads is time expensive, and hardware support is needed. Kernel-level threads are designed as independent threads. These threads have a fixed stack size, so the more recursions you have, the larger the stack size becomes, which may cause a stack overflow.

Other names for kernel-threads: system threads, OS threads

Ways Multi-threaded Programming Languages achieve Parallelism

Java

  • JVM manages Native-threads with the help of OS. Native-threads belong to JAVA and are implemented via JAVA using an OS API library. Multiple threads can co-exist at the same time and can run on different cores, allowing parallelism. Resources (data and code) are shared amongst threads, but not the stack and program counters. The kernel manages these threads.

GoLang

  • GoLang Runtime uses its own schedule to manage the goroutines and maps the goroutines to the threads.