A Deep Dive into Multithreading Challenges

Thiago Bomfim
5 min readFeb 19, 2024

--

Let’s start by understanding what multithreading is and why it’s important for many applications.

Multithreading is a concurrent execution model in which multiple threads operate independently within the same process.
A thread is the smallest unit of execution within a process, and multithreading allows multiple threads to run concurrently, enabling parallel execution of tasks.

Great, now that we understand the definition of multithreading, let’s understand what is the difference between concurrency and parallelism.

Concurrency vs Parallelism

Concurrency refers to the ability of multiple threads to execute tasks simultaneously.

Parallelism involves executing multiple tasks in parallel to improve overall efficiency.

Let’s think in 3 scenarios, single-thread, multi-thread, and parallelism.

Scenario 1: Single-threaded Coffee Machine

In this scenario, only one person can use the coffee machine at a time. Each customer has to wait for the previous one to finish before making their order.

Scenario 2: Multi-threaded Coffee Machine

The coffee machine now has multiple brewing stations (threads) that can handle different orders simultaneously. Customers can place orders independently, and the machine can process multiple orders concurrently, improving throughput.

Scenario 3: Parallel Coffee Machine

In this scenario, imagine having multiple separate coffee machines, each with its own line of customers. Each machine operates independently and can serve customers concurrently, significantly improving overall throughput.

This is the best scenario if you want to increase throughput and decrease latency, but be careful with the challenges this scenario brings. We are going to see these challenges soon.

Benefits:

Multithreading applications allow asynchronous tasks, which can be executed in the background while another thread keeps running the main application.

The throughput and latency can be increased with multithreading the application.

Coffee Machine example: Let’s use our last example, the coffee store.

Scenario: Single-threaded Coffee Machine

In this scenario, the coffee machine can execute just a single task.

If we have two orders, let’s say one for a latte and another for espresso, we will need to wait until one is completed to start the other.

Scenario: Multithreaded Coffee Machine

In this case, the coffee machine can execute many tasks at the same time.

If we have two orders in this case, let’s say one for a latte and another for espresso, we will be able to make both simultaneously, increasing the throughput and latency.

  • Throughput Improvement: With multithreading, the coffee shop can serve more customers in the same amount of time because different threads handle different orders simultaneously.
  • Latency Reduction: Customers experience lower wait times (lower latency) because multiple threads work concurrently, reducing the time it takes for each customer to get their coffee.

Challenges:

Using multithreading can bring some challenges that we should be aware of. These challenges can bring serious problems to our application.

As we’ve already talked, multithreading allows concurrency issues. Let’s see some of them:

Race Conditions

Race Conditions occur when two or more threads access shared data simultaneously, and at least one of them modifies the data, it can lead to unpredictable behaviour.

Example: Imagine a shared bank account that two people, Alice and Bob, are trying to access simultaneously.

Issue: Both Alice and Bob want to deposit money at the same time.

Race Condition: If the account balance is not properly protected, both transactions might read the current balance, add their deposit, and then write the updated balance. This can lead to unexpected results, as they might overwrite each other’s changes.

Possible solution: Use synchronization mechanisms like locks or atomic operations to ensure that only one thread can update the account balance at a time. This prevents race conditions by serializing access to the critical section of code that modifies shared data.

Deadlocks

Deadlock is a situation where two or more threads are unable to proceed because each is waiting for the other to release a resource.

Example: Two friends, Carol and Dave, are sharing resources while cooking in a kitchen.

Issue: Carol is using the cutting board and needs the knife that Dave is holding, while Dave needs the cutting board that Carol is using.

Deadlock: Carol won’t give up the cutting board until she gets the knife, and Dave won’t give up the knife until he gets the cutting board. They’re both waiting for the other to release the resources they require, resulting in a deadlock. No one can continue cooking.

Solution: Introduce a strategy to avoid circular waiting. For example, Carol could release the cutting board if she can’t get the knife and use another one, allowing Dave to proceed. Alternatively, assign a fixed order for resource acquisition to avoid circular dependencies.

Starvation

Starvation occurs when the current thread is unable to gain necessary resources (such as CPU time, memory, or access to a critical section) for an extended period.

Example: Imagine a printer shared by multiple people in an office.

Issue: Several employees, Emily, Frank, and George, are sending print jobs to the printer.

Starvation: If the printer always prioritizes Emily’s print jobs over others, Frank and George might wait indefinitely for their print jobs to be processed. They are starved of printing resources because Emily’s jobs always take precedence.

Solution: Implement a fair scheduling policy for the printer. Use techniques like a first-come-first-served queue or priority levels to ensure that all users get a chance to use the printer. This prevents one user from consistently taking precedence over others.

Conclusion:

To summarize, multithreading will provide more power to your application, it will be able to handle more requests at the same time.

However, if your application is competing to access the same resource with many threads, you can have a Race condition problem. If you decide to implement a Lock solution to avoid simultaneous access to the resource, you should be careful with Deadlock, if this Lock can take a while to execute, you should be careful with Starvation.

Therefore, the best solution for multithreading applications is to avoid sharing resources, and if this is not possible, investigate the best blocking solution 😊

If you want to keep updated, I invite you to subscribe to my newsletter, where I share many key concepts related to Software Engineer https://devjava.substack.com/p/a-deep-dive-into-multithreading-challenges

References:

--

--

Thiago Bomfim
Thiago Bomfim

Written by Thiago Bomfim

I'm a happy developer, trying to help the world and peoples with technologic

No responses yet