Dining philosopher's problem with semaphore

"The Dining Philosophers Problem is a classic concurrency problem that illustrates synchronization issues and the challenges of resource allocation in concurrent systems. It was formulated by Edsger Dijkstra in 1965. The problem is typically described in terms of five philosophers sitting around a table, each philosopher alternately thinking and eating. However, there are only five chopsticks available for them to share, with each philosopher requiring two chopsticks to eat.

Here's the problem statement:

                    - There are five philosophers sitting around a circular table.
                    - Each philosopher alternates between thinking and eating.
                    To eat, a philosopher needs two chopsticks, one from their left and one from their right.
                    - Chopsticks can only be held by one philosopher at a time.
                    A philosopher must be holding both chopsticks simultaneously to eat.
                    - Once a philosopher finishes eating, they put down both chopsticks and start thinking again."

import threading
import time
import random
import signal

class Chopstick(threading.Semaphore):
    def __init__(self, name):
        threading.Semaphore.__init__(self)
        self.name = name

class Philosopher(threading.Thread):
    def __init__(self, name, left_chopstick, right_chopstick, color, time_limit):
        threading.Thread.__init__(self)
        self.stopped = False
        self.name = color + name + '\033[00m'
        self.left_chopstick = left_chopstick
        self.right_chopstick = right_chopstick
        self.time_limit = time_limit
        self.time_limit_reached = threading.Event()

    def pickup(self):
        if not self.stopped:
            self.left_chopstick.acquire()
            self.right_chopstick.acquire()

    def putdown(self):
        self.left_chopstick.release()
        self.right_chopstick.release()

    def stop(self):
        self.stopped = True

    def run(self):
        start_time = time.time()
        while not self.stopped and not self.time_limit_reached.is_set():
            print(f'{self.name} is waiting.')
            self.pickup()
            if self.stopped or self.time_limit_reached.is_set():
                return
            print(f'{self.name} is eating.')
            time.sleep(random.randint(3, 10))
            self.putdown()
            print(f'{self.name} is thinking.')
            if not self.stopped:
                time.sleep(random.randint(3, 15))
            if time.time() - start_time >= self.time_limit:
                self.time_limit_reached.set()

if __name__ == '__main__':
    c1 = Chopstick('c1')
    c2 = Chopstick('c2')
    c3 = Chopstick('c3')
    c4 = Chopstick('c4')
    c5 = Chopstick('c5')
    chopsticks = [c1, c2, c3, c4, c5]

    colors = {
        'red': '\033[0;31m',
        'yellow': '\033[33m',
        'green': '\033[32m',
        'blue': '\033[34m',
        'purple': '\033[35m'
    }

    names = ['Kashyap', 'Deep', 'Jenil', 'Darshil', 'Dhairya']
    random.shuffle(names)

    time_limit = int(input("Enter the time limit for the dinner in seconds: "))
    
    philosophers = [
        Philosopher(names[0], c1, c2, colors['red'], time_limit),
        Philosopher(names[1], c2, c3, colors['yellow'], time_limit),
        Philosopher(names[2], c3, c4, colors['green'], time_limit),
        Philosopher(names[3], c4, c5, colors['blue'], time_limit),
        Philosopher(names[4], c5, c1, colors['purple'], time_limit)
    ]

    def init():
        print('Philosophers:')
        for p in philosophers:
            print(f'{p.name} ', end='')
        print('\n\nDinner is served!\n')

    def start():
        for p in philosophers:
            p.start()
        for p in philosophers:
            p.join()

    def exit(signum, frame):
        for p in philosophers:
            p.stop()
        print('\nDinner has ended. Waiting for everyone to finish up!\n')

    signal.signal(signal.SIGINT, exit)
    init()
    start()
    print("\nDone! 👌")                      
                            

Dining Philosopher Problem