Question 14
A popular online shopping platform, ”QuickBuy”, has a limited-time offer on a
bestselling product. The offer allows only 10 customers to buy the product at a
discounted price. The platform’s developer, Alex, wants to ensure that only 10
customers can take advantage of the offer. Alex decides to use multithreading
to handle the simultaneous requests from customers.

  • Write a program using Pthreads to simulate the scenario where more than
    10 customers (threads) try to buy the product, but only 10 can succeed.

This is yet another classic problem in multi-threaded programming as we did in one of the previous case. Multiple entities are trying to perform the same action simultaneously. This leads to contention. In this case, the high-demand activity is securing a discounted product. Every customer wants to be among the lucky 10 who get the deal. As soon as the seller announces the limited-time offer, a flood of customers rush in. They all try to make their purchase before the stock runs out.

Now, let’s map this real-world scenario to multi-threaded programming. Think of customers as pthreads—each acting independently, competing to grab the discounted product. The main program represents the seller. It ensures that only a limited number of items are sold at the discounted price. In this case, that number is 10. The products themselves act as the shared resource. We must carefully manage them. This careful management avoids inconsistencies when multiple customers (threads) try to access them at the same time.

How Do We Prevent Chaos?

In programming, multiple threads often access a shared resource. We need a synchronization mechanism to ensure only one thread modifies the resource at a time. This is exactly where mutex locks come into play. A mutex (mutual exclusion) ensures that only one thread can access the resource at any given moment. It prevents race conditions. It also ensures fairness.

Let’s extend our analogy—think of the discounted products being locked inside a room. Customers (threads) are waiting outside the room to get their hands on the deal. Whenever a customer gets the chance, they lock the door from inside, preventing others from entering at the same time. They then proceed with their purchase, and once they are done, they unlock the door for the next customer. This is exactly how mutex locks work in multi-threaded programming. They ensure that only one thread accesses the critical section at a time. It refers to the shared resource.

Now that we understand the fundamentals of locking in multi-threaded programming, let’s move forward. We will develop our program using Pthreads and mutex locks in the next section.

Download the Full Source Code
The complete source code for this implementation is available on GitHub. You can download it from the following link:
pthread_question_14.c on GitHub

We now move to the core implementation of our multi-threaded solution. It is designed to simulate a flash sale. Only 10 lucky customers can purchase a discounted product. If more than 10 customers attempt to buy, the system ensures that only the first 10 succeed. Let’s go through the program step by step to understand how we achieve this.

Mutex and Condition Variable

At the heart of our program, we use two key synchronization primitives:

pthread_mutex_t customer_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t available = PTHREAD_COND_INITIALIZER;

Mutex (customer_lock): Ensures that only one thread (customer) can modify the stock at a time. This prevents multiple customers from “grabbing” the same product at once.

Condition Variable (available): Helps customers wait until the sale starts (i.e., when stock is available). This ensures that customers don’t try to buy before the offer is announced.

We also define two global variables:

static int stock = 0; 
static int offer_ends = 0;

The stock variable keeps track of how many discounted items are left. Each time a customer (thread) successfully purchases an item, stock is decremented. The offer_ends variable, on the other hand, is used to indicate whether the sale has officially ended. Even if customers are still waiting, once offer_ends is set to 1, it signals the end of discounts. No more discounts will be given. Any remaining customers will exit without making a purchase.

The Customer (Thread) Function

Each customer is represented as a thread that tries to buy the discounted product. Let’s analyze how this works.

void * thread_function(void *arg)
{
    int ret = 0;
    while(1)
    {
        ret = pthread_mutex_lock(&customer_lock);

Customers Try to Enter the Sale

Each customer thread acquires the mutex (customer_lock). This ensures that no two customers access the stock at the same time.

Customers Wait for the Sale to Start

while(stock == 0 && offer_ends != 1)
{
    printf("Customer [%ld] joining the waiting queue\n", pthread_self());
    ret = pthread_cond_wait(&available, &customer_lock);
}

If stock is 0 and the sale hasn’t ended, customers must wait until the sale starts. pthread_cond_wait()releases the mutex and puts the thread in a waiting state. Once the main thread announces the sale, it will wake up all waiting customers using pthread_cond_broadcast().

Customers Check If the Offer Has Ended

if (offer_ends) {
    printf("Customer ID: %ld: Discounted price offer already closed\n", pthread_self());
    pthread_mutex_unlock(&customer_lock);
    break;
}

If offer_ends == 1, the customer realises that the sale is over and exits gracefully. The mutex is unlocked before the thread exits.

Successful Purchase

If the sale is still active and stock is available:

if(stock) 
{
    --stock;
    printf("Customer ID: %ld: Hurray!! Won discounted offer\n", pthread_self());
} 

The first 10 customers who reach this point successfully purchase the product. The stock decreases by 1 every time a purchase is made.

Customer Leaves After Purchase

pthread_mutex_unlock(&customer_lock);
break; // won the offer, exit now
  • The mutex is released, allowing other customers to enter.
  • The customer exits the loop, indicating that they have either:
    • Successfully bought the product, or
    • The offer has ended.

The Main Function: Announcing and Managing the Sale

Now let’s look at the main function, which acts as the seller handling the flash sale.

Handling Command-Line Arguments

if (argc < 2)
{
    printf("Invalid usage\n;");
    printf("Launch the program as below:\n");
    printf("\t./pthread_question_14 <num customers>\n");
    exit(1);
}

This ensures the user provides the number of customers trying to participate in the sale.

Creating Customer Threads

int num_customers = atoi(argv[1]);
pthread_t threads[num_customers];

num_customers represents the number of people trying to buy the product. We create one thread per customer. Then, we create each threads:

for(int i=0; i < num_customers; ++i)
{
    ret = pthread_create(&threads[i], NULL, thread_function, NULL);
}

Each thread represents a customer trying to buy the product.

Announcing the Sale (Making Products Available)

The main thread (seller) announces the sale after 2 seconds, simulating a delay.

sleep(2);
pthread_mutex_lock(&customer_lock);
stock = 10; // 10 discounted price offer
printf("Main :: Limited discounted sale on offer\n");
pthread_cond_broadcast(&available);
pthread_mutex_unlock(&customer_lock);

The stock is set to 10, meaning only 10 customers can buy the product. pthread_cond_broadcast(&available) – wakes up all waiting customer threads. The mutex is unlocked, allowing customers to compete for the stock.

Checking If the Sale Has Ended

sleep(5); // Main thread waits for some time before checking
pthread_mutex_lock(&customer_lock);

After 5 seconds, the seller checks if all 10 products are sold.

if(stock == 0) {
    printf("Main :: Looks like all 10 offers gone pretty quickly!\n");
    offer_ends = 1;
    pthread_cond_broadcast(&available);
}

If stock is 0, the seller ends the sale (offer_ends = 1). This wakes up any remaining customers still waiting, so they can exit gracefully.

Waiting for Customers to Finish

for(i=0; i < num_customers; ++i)
{
    pthread_join(threads[i], NULL);
}

The main thread waits for all customer threads to complete before exiting the program.

Compiling and Running the Program

You can compile the program using the following command:

gcc pthread_question_14.c -o pthread_question_14 -lpthread

After compilation, you can run the program by specifying the number of customers as a command-line argument:

./pthread_question_14 12
NUmber of customers chosen by the user = 12
Customer [137984154273344] joining the waiting queue
Customer [137984164759104] joining the waiting queue
Customer [137984143787584] joining the waiting queue
Customer [137984133301824] joining the waiting queue
Customer [137984101844544] joining the waiting queue
Customer [137984112330304] joining the waiting queue
Customer [137984091358784] joining the waiting queue
Customer [137984122816064] joining the waiting queue
Customer [137984070387264] joining the waiting queue
Customer [137984080873024] joining the waiting queue
Customer [137984049415744] joining the waiting queue
Customer [137984059901504] joining the waiting queue
Main :: Limited discounted sale on offer
Customer ID: 137984133301824: Hurray!! Won discounted offer
Customer [137984133301824] returning main() home
Customer ID: 137984154273344: Hurray!! Won discounted offer
Customer ID: 137984059901504: Hurray!! Won discounted offer
Customer [137984059901504] returning main() home
Customer [137984154273344] returning main() home
Customer ID: 137984164759104: Hurray!! Won discounted offer
Customer [137984164759104] returning main() home
Customer ID: 137984070387264: Hurray!! Won discounted offer
Customer [137984070387264] returning main() home
Customer ID: 137984049415744: Hurray!! Won discounted offer
Customer [137984049415744] returning main() home
Customer ID: 137984080873024: Hurray!! Won discounted offer
Customer [137984080873024] returning main() home
Customer ID: 137984091358784: Hurray!! Won discounted offer
Customer [137984091358784] returning main() home
Customer ID: 137984143787584: Hurray!! Won discounted offer
Customer [137984143787584] returning main() home
Customer ID: 137984122816064: Hurray!! Won discounted offer
Customer [137984122816064] returning main() home
Customer [137984112330304] joining the waiting queue
Customer [137984101844544] joining the waiting queue
Main :: Looks like all 10 offers gone pretty quickly!
Customer ID: 137984112330304: Discounted price offer already closed
Customer [137984112330304] returning main() home
Customer ID: 137984101844544: Discounted price offer already closed
Customer [137984101844544] returning main() home

Understanding the Output

Customers Start Arriving (Threads Start Execution)

NUmber of customers chosen by the user = 12
Customer [137984154273344] joining the waiting queue
Customer [137984164759104] joining the waiting queue
Customer [137984143787584] joining the waiting queue
...

The Seller Announces the Sale (Main Thread Updates Stock)

Main :: Limited discounted sale on offer

Customers Start Grabbing the Discounted Products

Customer ID: 137984133301824: Hurray!! Won discounted offer
Customer [137984133301824] returning main() home
Customer ID: 137984154273344: Hurray!! Won discounted offer
Customer ID: 137984059901504: Hurray!! Won discounted offer
Customer [137984059901504] returning main() home
Customer [137984154273344] returning main() home
...

Each customer thread competes for stock. Only 10 threads successfully decrement stock and print the success message. After successfully purchasing, each customer returns home (exits the thread).

Two Late Customers Find the Sale is Over

Customer [137984112330304] joining the waiting queue
Customer [137984101844544] joining the waiting queue
Main :: Looks like all 10 offers gone pretty quickly!
Customer ID: 137984112330304: Discounted price offer already closed
Customer [137984112330304] returning main() home
Customer ID: 137984101844544: Discounted price offer already closed
Customer [137984101844544] returning main() home

Two customers (137984112330304 and 137984101844544) were too late. They woke up after stock was already empty. The offer_ends flag was set, which prevented them from making a purchase. These customers receive a message that the discounted offer is closed and exit.

In this post, we took a real-world problem—a flash sale with limited stock. We translated it into a multi-threaded program using Pthreads. We explored how multiple customers (threads) compete for a shared resource (discounted products). We discussed how we can ensure fairness and synchronization using mutex locks and condition variables.

We saw how the pthread_cond_wait() function helps customers wait for the sale to start. It also demonstrates how pthread_cond_broadcast() wakes them up as soon as stock is available. We also implemented a clean exit strategy for late customers. We used the offer_ends flag to ensure no one is left waiting indefinitely.

Most importantly, we prevented race conditions by using a mutex lock. This ensured that no two customers could modify the stock at the same time. This is exactly how real-world multi-threaded applications manage concurrent access to shared resources.

Some of the concepts we touched on—such as critical sections, mutex locks, and condition variables—might not be entirely clear yet. This is especially true if you’re new to multi-threaded programming. And that’s completely fine! These are foundational concepts in concurrent programming, and they take time to fully grasp.

To make things easier, I’ll be coming up with follow-up posts where I’ll break down these concepts further. We’ll explore what exactly a critical section is. We will discuss why mutex locks are needed. We will also examine how condition variables help threads synchronize efficiently. I’ll introduce some common pitfalls in multi-threaded programming. These include deadlocks, race conditions, and thread starvation. I will show you how to avoid them.

So, if you found this post useful but still have questions, stick around! In the upcoming posts, we’ll take a more detailed and hands-on approach to mastering thread synchronisation and concurrent programming.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.