Why I receive an intermittent deadlock error?

The main reason I can see is the missing concurrency planning.

The reason the codes got deadlock is because your timing of locking for each sticks is unclear. Topping up the fact that you randomizes the sticks and assign to 5 philosophers, there is a chance where a stick is getting locked multiple rounds at the same time, repeatedly. That’s why you occasionally get deadlock.

The way you’re currently doing is like picking 1 stick from a pair chopstick and combine with another stick from another pair and tell the philosopher to use that pair of chopstick to eat.


Aside from @Christophe_Meessen mentioning about not understanding the problem clearly, you always need to plan out your concurrency. This is important because you’re dealing with multiple processes. If one process is giving you problem(s), it will be magnified. Concurrency planning also helps you prioritizes what to control and simplify the processes to the minimum viable codes. Only then, you can have full power to ensure all processes will not get into deadlock and race conditions problems.

Planning Example

Here is an example of the planning (I refactored the plan 2 times before testing it on code) based on your listed requirements.

Plan 1


If you notice, in Plan 1, it serves as an overview of the program, testing whether I’m understanding the problem correctly or otherwise. It is cleared that we have at least 2 main process structures (main as “host”, p as philosopher process). The arrows (with some errors) indicate data exchanges between main and any p processes.

However, it is still unclear how would I approach the synchronization, so we need to improve the plan.

Plan 2


In Plan 2, a more improved version, we can now clearly see that host is actually controlling who can eat, offers a request function to either return nil to deny eating or a chopstick to allow eating.

Also, p will check his/her fullness and inform back to host that he/she will no longer eats. However, we did not take that into account in both Plan 1 and Plan 2, so we need to improve it again.

Plan 3


In Plan 3, it is very clear now with roles and synchronization. The table is synchronized using mutex locking in order to offer request function. It has clarity on when to lock and unlock during checking.

Also, main setup a channel approach (c1) after table for synchronizing with all philosophers regarding of their fullness before serving them. Hence, we understood that step 6 in main is a for...select mechanism for their waiting. Hence, at step 5, we are clear that each philosopher will take in c1 as a parameter in order to inform host of his/her fullness.

On p side, it is clear that philosopher knows whether he/she can eat by checking the return value of the request function. Without chopstick, repeat the request again. Since we’re using c1 channel, it made it easy to infrom the host before completing the process.

With Plan 3, you can now know how-to and what-to code. If you notice:

  1. we do not need to manage chopsticks down to sticks level.
  2. chopsticks ownership is not needed.

However, we are still unclear with

  1. contact switching between main, table, and p. (WHO is doing WHAT at WHEN)
  2. After eating, who is freeing the chopsticks.

Hence, we refactor the plan again.

Plan 4


This time, it has very clear contact switching, and explanation on WHO is doing WHAT at WHEN.

We can now clearly see that table is responsible to setup and managing chopsticks, offering contacting switching functions like Request and Return to p. The mutex locking happens only at table.

p only needs to setup his/her fullness limit, use Request to get a chopstick from table, start eating, stop eating etc and Return to free the chopstick back to table and check for its fullness. If full, then he/she informs host by sending his/her name to the fullness c1 channel.

main (host) only needs to know the headcount, setup table, the fullness channel c1, serve the philosophers (p1, p2, …), then process the waiting (for..select) by checking the fullness report is the same has headcount.

At any point, any time, you can use this map to explain WHO is doing WHAT at WHEN. This is the best indicator that you’re ready to code. You can continue to refactor your plan. However, Plan 4 is good enough for coding.


The codes differs from individual to individuals. However, all of them should agree to your concurrency plan. Please try it out yourself.

I’m hiding my tested source codes just in case. Please be self-disciplined. Welcome to Golang-Bridge by the way. :rocket::fireworks:

One coding Example
package main

import (
	"fmt"
	"sync"
)

const (
	maxEating = 2
	fullnessLimit = 2
)

// Chopsticks is an object structure that serves as a tool to eat
type Chopsticks struct {
	mutex  sync.RWMutex
	ID     int
	isUsed bool
}

// NewChopsticks creates a Chopsticks object with the given id label.
func NewChopsticks(id int) *Chopsticks {
	return &Chopsticks{
		mutex:  sync.RWMutex{},
		ID:     id,
		isUsed: false,
	}
}

// Use is to set the Chopsticks status to "In-Use"
func (c *Chopsticks) Use() {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	c.isUsed = true
}

// Free is to set the Chopsticks status to "Free"
func (c *Chopsticks) Free() {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	c.isUsed = false
}

// IsInUse is to check the Chopsticks status, currently "In-Use" or "Free".
func (c *Chopsticks) IsInUse() bool {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	return c.isUsed
}

// Table is the structure serving foods and chopsticks
type Table struct {
	mutex      sync.RWMutex
	isEating   uint
	chopsticks []*Chopsticks
}

// NewTable creates the table object with person quantity.
func NewTable(quantity uint) *Table {
	t := &Table{
		mutex:      sync.RWMutex{},
		isEating:   0,
		chopsticks: []*Chopsticks{},
	}
	for i := 0; i < int(quantity); i++ {
		t.chopsticks = append(t.chopsticks, NewChopsticks(i))
	}

	return t
}

// RequestChopsticks is to allows a customer to eat using an available
// Chopsticks.
func (t *Table) RequestChopsticks() *Chopsticks {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	// table is full
	if t.isEating >= maxEating {
		return nil
	}

	// permit to eat. Scan for available chopsticks
	c := t.seekChopsticks()
	c.Use()
	t.isEating++
	return c
}

func (t *Table) seekChopsticks() *Chopsticks {
	// NOTE: here, you can use random. I will use FIFO instead.
	for _, c := range t.chopsticks {
		if !c.IsInUse() {
			return c
		}
	}
	return nil
}

// ReturnChopsticks is to allow a customer to place back chopsticks when
// he/she is done eating
func (t *Table) ReturnChopsticks(c *Chopsticks) {
	t.mutex.Lock()
	defer t.mutex.Unlock()
	t.isEating--
	c.Free()
}

func philosopher(id int, fullness chan int, table *Table) {
	eatCount := fullnessLimit

	for {
		chopsticks := table.RequestChopsticks()
		if chopsticks == nil {
			continue
		}

		// start eating
		fmt.Printf("P%d: START eating with chopstick %d.\n",
			id,
			chopsticks.ID)

		// eating
		eatCount--

		// stop eating
		fmt.Printf("P%d: FINISH eating with chopstick %d.\n",
			id,
			chopsticks.ID)
		table.ReturnChopsticks(chopsticks)

		// check fullness
		if eatCount == 0 {
			fullness <- id
			return
		}

	}
}

func main() {
	headcount := 5

	t := NewTable(uint(headcount))
	c1 := make(chan int)
	fullness := 0

	for i := 0; i < headcount; i++ {
		go philosopher(i, c1, t)
	}

	// Wait for fullness
	for {
		select {
		case person := <-c1:
			fullness++
			fmt.Printf("Philosopher %d is full\n", person)
			if fullness == headcount {
				fmt.Printf("All are full.\n[ ENDED ]\n")
				return
			}
		}
	}
}

// Output:
// P4: START eating with chopstick 0.
// P4: FINISH eating with chopstick 0.
// P4: START eating with chopstick 0.
// P4: FINISH eating with chopstick 0.
// Philosopher 4 is full
// P0: START eating with chopstick 0.
// P0: FINISH eating with chopstick 0.
// P0: START eating with chopstick 0.
// P0: FINISH eating with chopstick 0.
// Philosopher 0 is full
// P3: START eating with chopstick 0.
// P2: START eating with chopstick 1.
// P2: FINISH eating with chopstick 1.
// P3: FINISH eating with chopstick 0.
// P3: START eating with chopstick 0.
// P3: FINISH eating with chopstick 0.
// Philosopher 3 is full
// P2: START eating with chopstick 1.
// P2: FINISH eating with chopstick 1.
// Philosopher 2 is full
// P1: START eating with chopstick 0.
// P1: FINISH eating with chopstick 0.
// P1: START eating with chopstick 0.
// P1: FINISH eating with chopstick 0.
// Philosopher 1 is full
// All are full.
// [ ENDED ]

2 Likes