"""
Dining Philosophers, one of the best-known examples of race condions with
1. a circular table
2. `count` seats, each with a philosopher in it
3. a single chopstick between each pair of philosophers
4. philosophers need two chopsticks to eat, and put down their chopsticks after each mouthful

           Ph2
        ┌───────┐
        │ \ ○ / │
    Ph1 │○     ○│ Ph3
        │ / ○ \ │
        └───────┘
           Ph4

"""

from threading import Thread, Lock
from time import sleep
from random import uniform

class Chopstick:
    """Lock cannot be a base class, so we use a wrapper instead.  The __enter__ and __exit__ methods are used by `with` blocks"""
    def __init__(self, name):
        self.lock = Lock()
        self.name = name
    def __enter__(self, *args, **kargs): self.lock.__enter__(*args, **kargs)
    def __exit__(self, *args, **kargs): self.lock.__exit__(*args, **kargs)

class Philosopher(Thread):
    def __init__(self, name, left, right):
        super().__init__()
        self.left = left
        self.right = right
        self.name = name
        self.round = 0
    def run(self):
        while True:
            with self.left:
                print(self.name,'has', self.left.name)
                with self.right:
                    print(self.name,'has', self.right.name)
                    sleep(uniform(0,0.001)) # eat
                    self.round += 1
            print(self.name,f'put down the chopsticks ({self.round})')
            sleep(uniform(0,0.001)) # think

count = 3
utensil = [Chopstick(f'chopstick {i+1}') for i in range(count)]
seat = [Philosopher(f'Philosopher {i+1}', utensil[i-1], utensil[i]) for i in range(count)]
for p in seat: p.start()