Golang实现哲学家就餐问题
Golang实现哲学家就餐问题 我正在完成加州大学尔湾分校Coursera Go语言课程中的哲学家就餐问题程序。
我遇到了一些死锁问题(尽管使用了互斥锁)。同步机制似乎存在某些问题。题目要求如下:
实现哲学家就餐问题,需遵循以下约束/修改条件:
- 应有5位哲学家共享筷子,每对相邻哲学家之间放置一根筷子
- 每位哲学家只应进餐3次(不同于课堂演示中的无限循环)
- 哲学家以任意顺序拿起筷子,而非按编号从小到大顺序(课堂演示中使用的方式)
- 哲学家开始进餐前,必须获得独立协程中运行的主持人的许可
- 主持人最多允许2位哲学家同时进餐
- 每位哲学家都有编号,从1到5
- 当哲学家开始进餐(获得必要锁之后),需单独打印一行"开始进餐 <编号>",其中<编号>是哲学家编号
- 当哲学家结束进餐(释放锁之前),需单独打印一行"结束进餐 <编号>",其中<编号>是哲学家编号
以下是我目前的代码:
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
var ch = make(chan int, 3)
type fork struct{ sync.Mutex }
type philosopher struct {
id int
leftFork, rightFork *fork
}
// Goes from thinking to hungry to eating and done eating then starts over.
// Adapt the pause values to increased or decrease contentions
// around the forks.
func (p philosopher) eat(n int) {
say("thinking", p.id)
randomPause(2)
say("hungry", p.id)
p.leftFork.Lock()
p.rightFork.Lock()
randomPause(5)
say("eating", p.id)
p.rightFork.Unlock()
p.leftFork.Unlock()
say("done eating", p.id)
ch <- n
}
func randomPause(max int) {
time.Sleep(time.Millisecond * time.Duration(rand.Intn(max*1000)))
}
func say(action string, id int) {
fmt.Printf("Philosopher #%d is %s\n", id+1, action)
}
func init() {
// Random seed
rand.Seed(time.Now().UTC().UnixNano())
}
func main() {
// How many philosophers and forks
count := 5
// Create forks
forks := make([]*fork, count)
for i := 0; i < count; i++ {
forks[i] = new(fork)
}
// Create philospoher, assign them 2 forks and send them to the dining table
philosophers := make([]*philosopher, count)
for i := 0; i < count; i++ {
philosophers[i] = &philosopher{
id: i, leftFork: forks[i], rightFork: forks[(i+1)%count]}
for j := 0; j < 3; j++ {
go philosophers[i].eat(j)
}
}
philoEat := make(chan int, 3)
<-philoEat
}
期待获得任何指导。谢谢!
更多关于Golang实现哲学家就餐问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html
FistOfJustice:
os.Exit(0)
这个退出操作会在第一位哲学家吃完三次后立即执行。无论其他哲学家吃了多少次,程序都会直接退出。
os.Exit(0)
更多关于Golang实现哲学家就餐问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
必须将 Done 放在 go 协程内部,否则它会在协程启动后立即被调用。因此 waitgroup 必须是全局变量、作为参数传递,或者使用匿名函数先调用 eat() 再调用 Done()
package main
import (
"fmt"
"sync"
"time"
)
type fork struct{ sync.Mutex }
type philosopher struct {
id int
leftFork, rightFork *fork
}
// 从思考到饥饿到进食,完成进食后重新开始
// 调整暂停时间值来增加或减少对叉子的争用
func (p philosopher) eat() {
for j := 0; j < 3; j++ {
p.leftFork.Lock()
p.rightFork.Lock()
say("eating", p.id)
time.Sleep(time.Second)
p.rightFork.Unlock()
p.leftFork.Unlock()
say("finished eating", p.id)
time.Sleep(time.Second)
}
eatWgroup.Done()
}
func say(action string, id int) {
fmt.Printf("Philosopher #%d is %s\n", id+1, action)
}
var eatWgroup sync.WaitGroup
func main() {
// 哲学家和叉子的数量
count := 5
// 创建叉子
forks := make([]*fork, count)
for i := 0; i < count; i++ {
forks[i] = new(fork)
}
// 创建哲学家,分配两个叉子并送到餐桌
philosophers := make([]*philosopher, count)
for i := 0; i < count; i++ {
philosophers[i] = &philosopher{
id: i, leftFork: forks[i], rightFork: forks[(i+1)%count]}
eatWgroup.Add(1)
go philosophers[i].eat()
}
eatWgroup.Wait()
}
你的版本无法正常工作的原因是,go 协程会一直运行直到遇到阻塞调用,比如等待磁盘、网络等类似操作。因此在紧密循环中运行 go 协程不会给其他协程任何时间,除非你有多于一个核心让它们可以并行运行。我将 waitgroup 移到了全局变量,这样在函数内部更容易访问,并加入了一些睡眠时间(因为进食和思考都需要时间),这样其他 go 协程就有机会运行。
更多阅读请参考 Medium
不要打断!
go 运行时调度器采用协作式调度,这意味着只有当当前协程阻塞或完成时,才会调度另一个协程。这些情况包括:
- 通道发送和接收操作(如果这些操作会阻塞)
- Go 语句(虽然不能保证新协程会立即被调度)
- 阻塞性系统调用(如文件和网络操作)
- 垃圾回收周期结束后被停止
感谢您的跟进。当我移除 os.Exit(0) 后,程序在协程运行后出现死锁,请看:
$ go run diningphilosophers.go
Philosopher #1 is eating
Philosopher #1 is finished eating
Philosopher #3 is eating
Philosopher #3 is finished eating
Philosopher #5 is eating
Philosopher #5 is finished eating
Philosopher #2 is eating
Philosopher #2 is finished eating
Philosopher #4 is eating
Philosopher #4 is finished eating
Philosopher #1 is eating
Philosopher #1 is finished eating
Philosopher #3 is eating
Philosopher #3 is finished eating
Philosopher #5 is eating
Philosopher #5 is finished eating
Philosopher #2 is eating
Philosopher #2 is finished eating
Philosopher #4 is eating
Philosopher #4 is finished eating
Philosopher #1 is eating
Philosopher #1 is finished eating
Philosopher #3 is eating
Philosopher #3 is finished eating
Philosopher #5 is eating
Philosopher #5 is finished eating
Philosopher #2 is eating
Philosopher #2 is finished eating
Philosopher #4 is eating
Philosopher #4 is finished eating
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
C:/Users/iokhamaf/Documents/Coursera/Course 3/Module 4/diningphilosophers.go:85 +0x2d4
exit status 2
以下是修改后的代码(新代码),仍然有2位哲学家无法同时进餐:
package main
import (
"fmt"
"sync"
)
type fork struct{ sync.Mutex }
type philosopher struct {
id int
leftFork, rightFork *fork
}
// Goes from thinking to hungry to eating and done eating then starts over.
// Adapt the pause values to increased or decrease contentions
// around the forks.
func (p philosopher) eat() {
for j := 0; j < 3; j++ {
p.leftFork.Lock()
p.rightFork.Lock()
say("eating", p.id)
p.rightFork.Unlock()
p.leftFork.Unlock()
say("finished eating", p.id)
}
}
func say(action string, id int) {
fmt.Printf("Philosopher #%d is %s\n", id+1, action)
}
func main() {
// How many philosophers and forks
var eatWgroup sync.WaitGroup
count := 5
// Create forks
forks := make([]*fork, count)
for i := 0; i < count; i++ {
forks[i] = new(fork)
}
// Create philospoher, assign them 2 forks and send them to the dining table
philosophers := make([]*philosopher, count)
for i := 0; i < count; i++ {
philosophers[i] = &philosopher{
id: i, leftFork: forks[i], rightFork: forks[(i+1)%count]}
eatWgroup.Add(1)
go philosophers[i].eat()
eatWgroup.Done()
}
eatWgroup.Wait()
philoEat := make(chan int)
<-philoEat
}
更新:
我对代码进行了一些修改。但是,它并没有并发运行。看起来每次只运行一个goroutine或线程。在这个问题中,通常可以有两个哲学家同时进食,而其他哲学家则在等待进食。以下是修改后的代码:
package main
import (
"fmt"
"math/rand"
"os"
"sync"
"time"
)
type fork struct{ sync.Mutex }
type philosopher struct {
id int
leftFork, rightFork *fork
}
// 从思考到饥饿,再到进食和完成进食,然后重新开始。
// 调整暂停值以增加或减少对叉子的争用
func (p philosopher) eat() {
for j := 0; j < 3; j++ {
p.leftFork.Lock()
p.rightFork.Lock()
randomPause(5)
say("eating", p.id)
p.rightFork.Unlock()
p.leftFork.Unlock()
say("finished eating", p.id)
}
os.Exit(0)
}
func randomPause(max int) {
time.Sleep(time.Millisecond * time.Duration(rand.Intn(max*1000)))
}
func say(action string, id int) {
fmt.Printf("Philosopher #%d is %s\n", id+1, action)
}
func init() {
// 随机种子
rand.Seed(time.Now().UTC().UnixNano())
}
func main() {
// 哲学家和叉子的数量
count := 5
// 创建叉子
forks := make([]*fork, count)
for i := 0; i < count; i++ {
forks[i] = new(fork)
}
// 创建哲学家,分配两把叉子并送他们到餐桌
philosophers := make([]*philosopher, count)
for i := 0; i < count; i++ {
philosophers[i] = &philosopher{
id: i, leftFork: forks[i], rightFork: forks[(i+1)%count]}
go philosophers[i].eat()
}
philoEat := make(chan int)
<-philoEat
}
以下是输出:
$ go run diningphilosophers.go
Philosopher #5 is eating
Philosopher #5 is finished eating
Philosopher #4 is eating
Philosopher #4 is finished eating
Philosopher #2 is eating
Philosopher #2 is finished eating
Philosopher #1 is eating
Philosopher #1 is finished eating
Philosopher #5 is eating
Philosopher #5 is finished eating
Philosopher #4 is eating
Philosopher #4 is finished eating
Philosopher #3 is eating
Philosopher #3 is finished eating
Philosopher #2 is eating
Philosopher #2 is finished eating
Philosopher #1 is eating
Philosopher #1 is finished eating
Philosopher #5 is eating
Philosopher #5 is finished eating
$ go run diningphilosophers.go
Philosopher #3 is eating
Philosopher #3 is finished eating
Philosopher #1 is eating
Philosopher #1 is finished eating
Philosopher #5 is eating
Philosopher #5 is finished eating
Philosopher #4 is eating
Philosopher #4 is finished eating
Philosopher #3 is eating
Philosopher #3 is finished eating
Philosopher #2 is eating
Philosopher #2 is finished eating
Philosopher #1 is eating
Philosopher #1 is finished eating
Philosopher #5 is eating
Philosopher #5 is finished eating
Philosopher #4 is eating
Philosopher #4 is finished eating
Philosopher #3 is eating
Philosopher #3 is finished eating
这显然没有正常工作,因为并非所有哲学家都进食了3次。
您的代码存在几个关键问题,导致死锁和未满足题目要求。以下是修复后的完整实现:
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
type fork struct{ sync.Mutex }
type philosopher struct {
id int
leftFork, rightFork *fork
eatCount int
}
type host struct {
permission chan struct{}
}
func (h *host) grantPermission() {
h.permission <- struct{}{}
}
func (h *host) releasePermission() {
<-h.permission
}
func newHost(maxConcurrent int) *host {
h := &host{
permission: make(chan struct{}, maxConcurrent),
}
// 预先填充许可
for i := 0; i < maxConcurrent; i++ {
h.permission <- struct{}{}
}
return h
}
func (p *philosopher) eat(wg *sync.WaitGroup, host *host) {
defer wg.Done()
for p.eatCount < 3 {
// 思考
randomPause(2)
// 从主持人获取许可
host.grantPermission()
// 随机顺序拿起筷子
first, second := p.getForkOrder()
first.Lock()
second.Lock()
// 开始进餐
fmt.Printf("开始进餐 %d\n", p.id+1)
randomPause(3)
// 结束进餐
fmt.Printf("结束进餐 %d\n", p.id+1)
second.Unlock()
first.Unlock()
// 释放主持人许可
host.releasePermission()
p.eatCount++
}
}
func (p *philosopher) getForkOrder() (*fork, *fork) {
if rand.Intn(2) == 0 {
return p.leftFork, p.rightFork
}
return p.rightFork, p.leftFork
}
func randomPause(max int) {
time.Sleep(time.Millisecond * time.Duration(rand.Intn(max*1000)))
}
func main() {
rand.Seed(time.Now().UnixNano())
count := 5
forks := make([]*fork, count)
for i := 0; i < count; i++ {
forks[i] = new(fork)
}
// 创建主持人,最多允许2位哲学家同时进餐
host := newHost(2)
philosophers := make([]*philosopher, count)
for i := 0; i < count; i++ {
philosophers[i] = &philosopher{
id: i,
leftFork: forks[i],
rightFork: forks[(i+1)%count],
eatCount: 0,
}
}
var wg sync.WaitGroup
for i := 0; i < count; i++ {
wg.Add(1)
go philosophers[i].eat(&wg, host)
}
wg.Wait()
fmt.Println("所有哲学家已完成进餐")
}
主要修复和改进:
- 主持人机制:实现了
host结构体,使用带缓冲的通道控制最多2位哲学家同时进餐 - 随机筷子顺序:
getForkOrder()方法随机选择先拿哪边的筷子 - 进餐次数限制:每个哲学家通过
eatCount计数器确保只进餐3次 - 正确的同步:使用
sync.WaitGroup等待所有哲学家完成,而不是未使用的通道 - 输出格式:严格按照题目要求输出"开始进餐 <编号>“和"结束进餐 <编号>”
- 死锁预防:通过主持人限制并发数,结合随机筷子顺序,有效预防死锁
这个实现满足了所有题目要求:5位哲学家、每人进餐3次、随机筷子顺序、主持人许可机制、最多2人同时进餐,以及正确的输出格式。

