AIM:

To Demonstrate use of semaphores for deadlock avoidance.

Code:

import threading
import time
import random
 
# Initialization
NUM_PROCESSES = 5
NUM_RESOURCES = 3
available = [10, 5, 7]  # Available resources
maximum = [  # Maximum demand matrix
    [7, 5, 3],
    [3, 2, 2],
    [9, 0, 2],
    [2, 2, 2],
    [4, 3, 3]
]
allocation = [[0] * NUM_RESOURCES for _ in range(NUM_PROCESSES)]  # Allocated resources matrix
need = [[maximum[i][j] - allocation[i][j] for j in range(NUM_RESOURCES)] for i in range(NUM_PROCESSES)]  # Need matrix
mutex = threading.Semaphore(1)  # Mutex for synchronization
 
# Function to check if the system is in a safe state
def is_safe():
    work = available[:]
    finish = [False] * NUM_PROCESSES
    safe_sequence = []
    while len(safe_sequence) < NUM_PROCESSES:
        allocated = False
        for i in range(NUM_PROCESSES):
            if not finish[i] and all(need[i][j] <= work[j] for j in range(NUM_RESOURCES)):
                # Allocate resources
                for j in range(NUM_RESOURCES):
                    work[j] += allocation[i][j]
                safe_sequence.append(i)
                finish[i] = True
                allocated = True
                break
        if not allocated:  # If no allocation is possible, system is unsafe
            return False, []
    return True, safe_sequence
 
# Function to handle resource requests
def request_resources(process_id):
    global available, allocation, need
    mutex.acquire()  # Lock for critical section
    # Random resource request
    request = [random.randint(0, need[process_id][j]) for j in range(NUM_RESOURCES)]
    print(f"Process {process_id} requesting {request}")
    if all(request[j] <= available[j] for j in range(NUM_RESOURCES)):  # Check availability
        # Tentatively allocate resources
        for j in range(NUM_RESOURCES):
            available[j] -= request[j]
            allocation[process_id][j] += request[j]
            need[process_id][j] -= request[j]
        # Check if the system is still in a safe state
        safe, sequence = is_safe()
        if safe:
            print(f"Request granted. Safe sequence: {sequence}")
        else:  # Rollback allocation if unsafe
            for j in range(NUM_RESOURCES):
                available[j] += request[j]
                allocation[process_id][j] -= request[j]
                need[process_id][j] += request[j]
            print("Request denied to avoid deadlock.")
    else:
        print("Request exceeds available resources.")
    mutex.release()  # Release lock
 
# Function representing a process thread
def process_thread(process_id):
    time.sleep(random.randint(1, 3))  # Simulate process delay
    request_resources(process_id)
    time.sleep(random.randint(1, 3))  # Simulate process execution
 
# Main simulation
threads = []
for i in range(NUM_PROCESSES):
    t = threading.Thread(target=process_thread, args=(i,))
    threads.append(t)
    t.start()
 
# Wait for all threads to finish
for t in threads:
    t.join()
 
print("Simulation complete.")

Output:

Process 0 requesting [3, 2, 2]
Request granted. Safe sequence: [0, 1, 3, 4, 2]
Process 1 requesting [2, 1, 1]
Request granted. Safe sequence: [0, 1, 3, 4, 2]
Process 2 requesting [4, 0, 2]
Request denied to avoid deadlock.
Process 3 requesting [2, 2, 2]
Request granted. Safe sequence: [0, 1, 3, 4, 2]
Process 4 requesting [1, 1, 1]
Request granted. Safe sequence: [0, 1, 3, 4, 2]
Simulation complete.

Conclusion:

Successfully Demonstrated use of semaphores for deadlock avoidance.