Open In App

Bakery Algorithm in Process Synchronization

Prerequisite - Critical Section, Process Synchronization, Inter Process Communication The Bakery algorithm is one of the simplest known solutions to the mutual exclusion problem for the general case of N process. Bakery Algorithm is a critical section solution for N processes. The algorithm preserves the first come first serve property.

How does the Bakery Algorithm work?
In the Bakery Algorithm, each process is assigned a number (a ticket) in a lexicographical order. Before entering the critical section, a process receives a ticket number, and the process with the smallest ticket number enters the critical section. If two processes receive the same ticket number, the process with the lower process ID is given priority.

How does the Bakery Algorithm ensure fairness?
The Bakery Algorithm ensures fairness by assigning a unique ticket number to each process based on a lexicographical order. This ensures that processes are served in the order they arrive, which guarantees that all processes will eventually enter the critical section.

if i < j 
Pi is served first;
else
Pj is served first.

Notation - lexicographical order (ticket #, process id #) - Firstly the ticket number is compared. If same then the process ID is compared next, i.e.-

– (a, b) < (c, d) if a < c or if a = c and b < d
– max(a [0], . . ., a [n-1]) is a number, k, such that k >= a[i] for i = 0, . . ., n - 1

Shared data - choosing is an array [0..n - 1] of boolean values; & number is an array [0..n - 1] of integer values. Both are initialized to False & Zero respectively. Algorithm Pseudocode -

repeat
choosing[i] := true;
number[i] := max(number[0], number[1], ..., number[n - 1])+1;
choosing[i] := false;
for j := 0 to n - 1
do begin
while choosing[j] do no-op;
while number[j] != 0
and (number[j], j) < (number[i], i) do no-op;
end;
critical section

number[i] := 0;

remainder section
until false;

Explanation - Firstly the process sets its "choosing" variable to be TRUE indicating its intent to enter critical section. Then it gets assigned the highest ticket number corresponding to other processes. Then the "choosing" variable is set to FALSE indicating that it now has a new ticket number. This is in-fact the most important and confusing part of the algorithm. It is actually a small critical section in itself ! The very purpose of the first three lines is that if a process is modifying its TICKET value then at that time some other process should not be allowed to check its old ticket value which is now obsolete. This is why inside the for loop before checking ticket value we first make sure that all other processes have the "choosing" variable as FALSE. After that we proceed to check the ticket values of processes where process with least ticket number/process id gets inside the critical section. The exit section just resets the ticket value to zero. Code - Here's the C code implementation of the Bakery Algorithm. Run the following in a UNIX environment

C
// Importing the thread library
#include "pthread.h"

#include "stdio.h"

// Importing POSIX Operating System API library
#include "unistd.h"

#include "string.h"

// This is a memory barrier instruction.
// Causes compiler to enforce an ordering
// constraint on memory operations.
// This means that operations issued prior
// to the barrier will be performed
// before operations issued after the barrier.
#define MEMBAR __sync_synchronize()
#define THREAD_COUNT 8

volatile int tickets[THREAD_COUNT];
volatile int choosing[THREAD_COUNT];

// VOLATILE used to prevent the compiler
// from applying any optimizations.
volatile int resource;

void lock(int thread)
{

    // Before getting the ticket number
    //"choosing" variable is set to be true
    choosing[thread] = 1;

    MEMBAR;
    // Memory barrier applied

    int max_ticket = 0;

    // Finding Maximum ticket value among current threads
    for (int i = 0; i < THREAD_COUNT; ++i) {

        int ticket = tickets[i];
        max_ticket = ticket > max_ticket ? ticket : max_ticket;
    }

    // Allotting a new ticket value as MAXIMUM + 1
    tickets[thread] = max_ticket + 1;

    MEMBAR;
    choosing[thread] = 0;
    MEMBAR;

    // The ENTRY Section starts from here
    for (int other = 0; other < THREAD_COUNT; ++other) {

        // Applying the bakery algorithm conditions
        while (choosing[other]) {
        }

        MEMBAR;

        while (tickets[other] != 0 && (tickets[other]
                                        < tickets[thread]
                                    || (tickets[other]
                                            == tickets[thread]
                                        && other < thread))) {
        }
    }
}

// EXIT Section
void unlock(int thread)
{

    MEMBAR;
    tickets[thread] = 0;
}

// The CRITICAL Section
void use_resource(int thread)
{

    if (resource != 0) {
        printf("Resource was acquired by %d, but is still in-use by %d!\n",
            thread, resource);
    }

    resource = thread;
    printf("%d using resource...\n", thread);

    MEMBAR;
    sleep(2);
    resource = 0;
}

// A simplified function to show the implementation
void* thread_body(void* arg)
{

    long thread = (long)arg;
    lock(thread);
    use_resource(thread);
    unlock(thread);
    return NULL;
}

int main(int argc, char** argv)
{

    memset((void*)tickets, 0, sizeof(tickets));
    memset((void*)choosing, 0, sizeof(choosing));
    resource = 0;

    // Declaring the thread variables
    pthread_t threads[THREAD_COUNT];

    for (int i = 0; i < THREAD_COUNT; ++i) {

        // Creating a new thread with the function
        //"thread_body" as its thread routine
        pthread_create(&threads[i], NULL, &thread_body, (void*)((long)i));
    }

    for (int i = 0; i < THREAD_COUNT; ++i) {

        // Reaping the resources used by
        // all threads once their task is completed !
        pthread_join(threads[i], NULL);
    }

    return 0;
}
C#
using System;
using System.Threading;

public class Solution
{
    private const int THREAD_COUNT = 8;
    private static int[] tickets = new int[THREAD_COUNT]; // Ticket array for each thread
    private static int[] choosing = new int[THREAD_COUNT]; // Array to indicate if a thread is choosing
    private static volatile int resource = 0; // Volatile resource variable
    private static object lockObj = new object(); // Lock object for synchronization

    // Memory barrier instruction.
    private static void Membar()
    {
        Thread.MemoryBarrier();
    }

    // Function to acquire the lock
    private static void Lock(int thread)
    {
        choosing[thread] = 1; // Indicate that this thread is choosing
        Membar(); // Memory barrier

        int maxTicket = 0;

        // Find the maximum ticket value
        for (int i = 0; i < THREAD_COUNT; i++)
        {
            int ticket = tickets[i];
            maxTicket = Math.Max(ticket, maxTicket);
        }

        // Assign ticket to this thread
        tickets[thread] = maxTicket + 1;

        Membar(); // Memory barrier
        choosing[thread] = 0; // Done choosing
        Membar(); // Memory barrier

        // The ENTRY Section starts from here
        for (int other = 0; other < THREAD_COUNT; ++other)
        {
            // Applying the bakery algorithm conditions
            while (choosing[other] != 0) { }

            Membar();

            while (tickets[other] != 0 && (tickets[other] < tickets[thread] || (tickets[other] == tickets[thread] && other < thread))) { }
        }
    }

    // EXIT Section
    private static void Unlock(int thread)
    {
        Membar();
        tickets[thread] = 0;
    }

    // The CRITICAL Section
    private static void UseResource(int thread)
    {
        lock (lockObj) // Lock to ensure thread-safe access to resource
        {
            // Check if resource is already in use
            if (resource != 0)
            {
                Console.WriteLine($"Resource was acquired by {thread}, but is still in-use by {resource}!");
            }

            // Acquire resource
            resource = thread;
            Console.WriteLine($"{thread} using resource...");
        }

        // Simulate resource usage
        Thread.Sleep(TimeSpan.FromSeconds(2));

        // Release resource
        lock (lockObj)
        {
            resource = 0;
        }
    }

    // A simplified function to show the implementation
    private static void ThreadBody(object arg)
    {
        long thread = (long)arg;
        Lock((int)thread); // Acquire lock
        UseResource((int)thread); // Use resource
        Unlock((int)thread); // Release lock
    }

    public static void Main(string[] args)
    {
        Array.Clear(tickets, 0, THREAD_COUNT); // Initialize ticket array
        Array.Clear(choosing, 0, THREAD_COUNT); // Initialize choosing array
        resource = 0; // Initialize resource

        Thread[] threads = new Thread[THREAD_COUNT];

        for (int i = 0; i < THREAD_COUNT; ++i)
        {
            threads[i] = new Thread(ThreadBody);
            threads[i].Start(i);
        }

        for (int i = 0; i < THREAD_COUNT; ++i)
        {
            threads[i].Join();
        }
    }
}
Javascript
const THREAD_COUNT = 8;
const tickets = new Array(THREAD_COUNT).fill(0);
const choosing = new Array(THREAD_COUNT).fill(0);
let resource = 0;
const lockObj = {};

// Memory barrier instruction.
function membar() {
  // JavaScript doesn't have explicit memory barrier instructions.
  // In most cases, JavaScript's single-threaded nature makes explicit memory barriers unnecessary.
  // If working with Web Workers or other asynchronous operations, additional synchronization may be required.
}

// Function to acquire the lock
function lock(thread) {
  choosing[thread] = 1;
  membar();

  let maxTicket = 0;

  // Find the maximum ticket value
  for (let i = 0; i < THREAD_COUNT; i++) {
    const ticket = tickets[i];
    maxTicket = Math.max(ticket, maxTicket);
  }

  // Assign ticket to this thread
  tickets[thread] = maxTicket + 1;

  membar();
  choosing[thread] = 0;
  membar();

  // The ENTRY Section starts from here
  for (let other = 0; other < THREAD_COUNT; ++other) {
    while (choosing[other] !== 0) {}

    membar();

    while (tickets[other] !== 0 && (tickets[other] < tickets[thread] || (tickets[other] === tickets[thread] && other < thread))) {}
  }
}

// EXIT Section
function unlock(thread) {
  membar();
  tickets[thread] = 0;
}

// The CRITICAL Section
function useResource(thread) {
  lockObj.lock = true;

  // Check if resource is already in use
  if (resource !== 0) {
    console.log(`Resource was acquired by ${thread}, but is still in-use by ${resource}!`);
  }

  // Acquire resource
  resource = thread;
  console.log(`${thread} using resource...`);

  // Simulate resource usage
  setTimeout(() => {
    // Release resource
    resource = 0;
    lockObj.lock = false;
  }, 2000);
}

// A simplified function to show the implementation
function threadBody(thread) {
  lock(thread);
  useResource(thread);
  unlock(thread);
}

const threads = [];

for (let i = 0; i < THREAD_COUNT; ++i) {
  threads[i] = setTimeout(threadBody, 0, i);
}
//This code is contributed by Aman.

// Note: This code uses setTimeout to simulate threads, but for actual multithreading in JavaScript,
// you would need to use a library or environment that supports it, such as Node.js with Worker Threads
// or a web browser environment with Web Workers.
//use this node multithreading.js




C++14
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <chrono>

#define THREAD_COUNT 8

std::vector<int> tickets(THREAD_COUNT);
std::vector<int> choosing(THREAD_COUNT);
volatile int resource = 0;

std::mutex mtx;  // Mutex for resource access

void lock(int thread) {
    choosing[thread] = 1;

    std::atomic_thread_fence(std::memory_order_seq_cst);

    int max_ticket = 0;

    for (int i = 0; i < THREAD_COUNT; ++i) {
        int ticket = tickets[i];
        max_ticket = ticket > max_ticket ? ticket : max_ticket;
    }

    tickets[thread] = max_ticket + 1;

    std::atomic_thread_fence(std::memory_order_seq_cst);
    choosing[thread] = 0;

    for (int other = 0; other < THREAD_COUNT; ++other) {
        while (choosing[other]) {
        }

        std::atomic_thread_fence(std::memory_order_seq_cst);

        while (tickets[other] != 0 && (tickets[other] < tickets[thread] || (tickets[other] == tickets[thread] && other < thread))) {
        }
    }
}

void unlock(int thread) {
    std::atomic_thread_fence(std::memory_order_seq_cst);
    tickets[thread] = 0;
}

void use_resource(int thread) {
    std::lock_guard<std::mutex> lock(mtx);

    if (resource != 0) {
        std::cout << "Resource was acquired by " << thread << ", but is still in-use by " << resource << "!\n";
    }

    resource = thread;
    std::cout << thread << " using resource...\n";

    std::atomic_thread_fence(std::memory_order_seq_cst);
    std::this_thread::sleep_for(std::chrono::seconds(2));
    resource = 0;
}

void thread_body(int thread) {
    lock(thread);
    use_resource(thread);
    unlock(thread);
}

int main() {
    std::fill(tickets.begin(), tickets.end(), 0);
    std::fill(choosing.begin(), choosing.end(), 0);
    resource = 0;

    std::vector<std::thread> threads;

    for (int i = 0; i < THREAD_COUNT; ++i) {
        threads.emplace_back(thread_body, i);
    }

    for (auto& thread : threads) {
        thread.join();
    }

    return 0;
}
// Compile this code using the following command to link against the pthread library:
// g++ -std=c++11 -pthread Solution.cpp -o Solution

// Note: Ensure that you have the '-pthread' option to properly link against the pthread library.

Output: Output

Advantages of Bakery Algorithm:


Disadvantages Bakery Algorithm:

Article Tags :