Golang解决哲学家就餐变体问题
来源:stackoverflow
时间:2024-02-15 10:21:23 255浏览 收藏
今天golang学习网给大家带来了《Golang解决哲学家就餐变体问题》,其中涉及到的知识点包括等等,无论你是小白还是老手,都适合看一看哦~有好的建议也欢迎大家在评论留言,若是看完有所收获,也希望大家能多多点赞支持呀!一起加油学习~
我想实现经典哲学家就餐问题的一个变体,其定义为:
通过以下约束/修改来实现哲学家就餐问题。
- 应该有 5 个哲学家共用筷子,每对相邻的哲学家之间各插一根筷子。
- 每个哲学家只能吃 3 次(而不是像我们在讲座中那样无限循环)
- 哲学家以任意顺序拿起筷子,而不是从编号最小的开始(我们在讲座中这样做)。
- 为了吃饭,哲学家必须获得在自己的 goroutine 中执行的主机的许可。
- 主持人允许最多 2 名哲学家同时用餐。
- 每位哲学家都有编号,从 1 到 5。
- 当哲学家开始吃东西时(在获得必要的锁之后),它会在一行上打印“starting to eat”,其中是哲学家的编号。
- 当哲学家吃完时(在释放锁之前),它会在一行上打印“finishing eat”,其中是哲学家的编号。
我实现了以下代码:
package main import ( "fmt" "sync" ) // define variables var numphilo int = 5 var numcs int = 5 var eattimes int = 3 var numeatingphilo int = 2 type host struct { // channel for allowed philosopher for eating requestchannel chan *philo // channel for terminate signal for the daemon quitchannel chan int // bookkeeping of the current eating philosophers eatingphilos map[int]bool // mutex to lock the modification of the eatingphilos variable mu sync.mutex } // daemon function to manage the allowed philosophers func (phost *host) manage() { // daemon serving in the backend and only exits for terminate signal for { // if the channel is full, release the head of the queue if len(phost.requestchannel) == numeatingphilo { finished := <-phost.requestchannel curreating := make([]int, 0, numphilo) for tmpidx, eating := range phost.eatingphilos { if eating { curreating = append(curreating, tmpidx) } } fmt.printf("%v have been eating, clearing up %d\n", curreating, finished.idx) phost.eatingphilos[finished.idx] = false } select { case <-phost.quitchannel: fmt.println("stop hosting") return default: } } } type chops struct { mu sync.mutex } type philo struct { // index of the philosopher idx int // number of times the philosopher has eaten numeat int leftcs, rightcs *chops host *host } func (pphilo *philo) eat(wg *sync.waitgroup) { for pphilo.numeat < eattimes { // once the philosopher intends to eat, lock the corresponding chopsticks pphilo.leftcs.mu.lock() pphilo.rightcs.mu.lock() // reserve a slot in the channel for eating // if channel buffer is full, this is blocked until channel space is released pphilo.host.requestchannel <- pphilo pphilo.host.mu.lock() pphilo.host.eatingphilos[pphilo.idx] = true pphilo.host.mu.unlock() pphilo.numeat++ fmt.printf("starting to eat %d for %d time\n", pphilo.idx, pphilo.numeat) fmt.printf("finishing eating %d for %d time\n", pphilo.idx, pphilo.numeat) pphilo.rightcs.mu.unlock() pphilo.leftcs.mu.unlock() wg.done() } } func main() { var wg sync.waitgroup requestchannel := make(chan *philo, numeatingphilo) quitchannel := make(chan int, 1) host := host{requestchannel: requestchannel, quitchannel: quitchannel, eatingphilos: make(map[int]bool)} csticks := make([]*chops, numcs) for i := 0; i < numcs; i++ { csticks[i] = new(chops) } philos := make([]*philo, numphilo) for i := 0; i < numphilo; i++ { philos[i] = &philo{idx: i + 1, numeat: 0, leftcs: csticks[i], rightcs: csticks[(i+1)%5], host: &host} } go host.manage() wg.add(numphilo * eattimes) for i := 0; i < numphilo; i++ { go philos[i].eat(&wg) } wg.wait() host.quitchannel <- 1 }
但是,我注意到该程序实际上在某些情况下失败了,即
starting to eat 5 for 1 time finishing eating 5 for 1 time starting to eat 2 for 1 time finishing eating 2 for 1 time [5 2] have been eating, clearing up 5 [2] have been eating, clearing up 2 [] have been eating, clearing up 5 starting to eat 5 for 2 time finishing eating 5 for 2 time starting to eat 5 for 3 time finishing eating 5 for 3 time [5 2] have been eating, clearing up 2 [5] have been eating, clearing up 5 starting to eat 2 for 2 time finishing eating 2 for 2 time [4] have been eating, clearing up 4 starting to eat 4 for 1 time finishing eating 4 for 1 time [] have been eating, clearing up 2 starting to eat 4 for 2 time finishing eating 4 for 2 time [4] have been eating, clearing up 4 starting to eat 4 for 3 time finishing eating 4 for 3 time starting to eat 2 for 3 time finishing eating 2 for 3 time starting to eat 1 for 1 time finishing eating 1 for 1 time [2 4 1] have been eating, clearing up 4 [2 1] have been eating, clearing up 1 [2] have been eating, clearing up 3 starting to eat 1 for 2 time finishing eating 1 for 2 time [1 2] have been eating, clearing up 1 starting to eat 3 for 1 time finishing eating 3 for 1 time starting to eat 3 for 2 time finishing eating 3 for 2 time [3 2] have been eating, clearing up 1 [2 3] have been eating, clearing up 3 starting to eat 3 for 3 time finishing eating 3 for 3 time starting to eat 1 for 3 time finishing eating 1 for 3 time stop hosting
有时哲学家们似乎不能同时吃饭,而且根据日志,簿记地图表现得很奇怪。
有人可以指出实施的哪一部分不正确吗?有什么明显错误或不好的做法吗?
正确答案
您在此处发布的代码包含数据竞争(从多个 go 例程访问 host.eatingphilos
,没有任何保护)。您在将问题发布到 code review 之前解决了这些问题,这在一定程度上实现了可行的解决方案(但引入了其他问题)。
我在 code review 上完整回答了这个问题,并提供了一些反馈等。由于您在那里发布的代码与此处的代码不同,因此重复答案没有什么意义。但是,我将包括我建议的解决方案,因为它采用了与您所采用的方法截然不同的方法(为了解决一些逻辑问题)。请注意,此解决方案采用了一种简单的方法来解决“饥饿的哲学家”问题(每个哲学家都有一根筷子,因此没有人可以获得第二根筷子)。请参阅 Wikipedia page 了解其他一些更好的解决方案(但我认为您想使用 go 例程)。
警告 - 下面可能包含错误!
package main import ( "fmt" "math/rand" "sync" "time" "golang.org/x/exp/slices" ) const ( numPhilo = 5 eatTimes = 3 numEatingPhilo = 2 ) type eatRequest struct { who int // Who is making the request finishedFnChan chan func() // When approves a response will be sent on this channel with a function to call when done } // simulateHost - the host must provide permission before a philosopher can eat // Exits when channel closed func simulateHost(requestChannel <-chan eatRequest) { awaitRequest := requestChannel finishedChan := make(chan struct { who int done chan struct{} }) var whoEating []int // tracks who is currently eating for { select { case request, ok := <-awaitRequest: if !ok { return // Closed channel means that we are done (finishedChan is guaranteed to be empty) } // Sanity check - confirm that philosopher is not being greedy! (should never happen) if slices.Index(whoEating, request.who) != -1 { panic("Multiple requests from same philosopher") } whoEating = append(whoEating, request.who) // New request always goes at the end fmt.Printf("%d started eating (currently eating %v)\n", request.who, whoEating) // Let philosopher know and provide means for them to tell us when done request.finishedFnChan <- func() { d := make(chan struct{}) finishedChan <- struct { who int done chan struct{} }{who: request.who, done: d} <-d // Wait until request has been processed (ensure we should never have two active requests from one philosopher) } case fin := <-finishedChan: idx := slices.Index(whoEating, fin.who) if idx == -1 { panic("philosopher stopped eating multiple times!") } whoEating = append(whoEating[:idx], whoEating[idx+1:]...) // delete the element fmt.Printf("%d completed eating (currently eating %v)\n", fin.who, whoEating) close(fin.done) } // There has been a change in the number of philosopher's eating if len(whoEating) < numEatingPhilo { awaitRequest = requestChannel } else { awaitRequest = nil // Ignore new eat requests until a philosopher finishes (nil channel will never be selected) } } } // ChopS represents a single chopstick type ChopS struct { mu sync.Mutex idx int // Including the index can make debugging simpler } // philosopher simulates a Philosopher (brain in a vat!) func philosopher(philNum int, leftCS, rightCS *ChopS, requestToEat chan<- eatRequest) { for numEat := 0; numEat < eatTimes; numEat++ { // once the philosopher intends to eat, lock the corresponding chopsticks for { leftCS.mu.Lock() // Attempt to get the right Chopstick - if someone else has it we replace the left chopstick and try // again (in order to avoid deadlocks) if rightCS.mu.TryLock() { break } leftCS.mu.Unlock() } // We have the chopsticks but need the hosts permission ffc := make(chan func()) // when accepted we will receive a function to call when done eating requestToEat <- eatRequest{ who: philNum, finishedFnChan: ffc, } doneEating := <-ffc fmt.Printf("philosopher %d starting to eat (%d feed)\n", philNum, numEat) time.Sleep(time.Millisecond * time.Duration(rand.Intn(200))) // Eating takes a random amount of time fmt.Printf("philosopher %d finished eating (%d feed)\n", philNum, numEat) rightCS.mu.Unlock() leftCS.mu.Unlock() doneEating() // Tell host that we have finished eating } fmt.Printf("philosopher %d is full\n", philNum) } func main() { CSticks := make([]*ChopS, numPhilo) for i := 0; i < numPhilo; i++ { CSticks[i] = &ChopS{idx: i} } requestChannel := make(chan eatRequest) var wg sync.WaitGroup wg.Add(numPhilo) for i := 0; i < numPhilo; i++ { go func(philNo int) { philosopher(philNo, CSticks[philNo-1], CSticks[philNo%numPhilo], requestChannel) wg.Done() }(i + 1) } go func() { wg.Wait() close(requestChannel) }() simulateHost(requestChannel) }
今天带大家了解了的相关知识,希望对你有所帮助;关于Golang的技术知识我们会一点点深入介绍,欢迎大家关注golang学习网公众号,一起学习编程~
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
139 收藏
-
204 收藏
-
325 收藏
-
477 收藏
-
486 收藏
-
439 收藏
-
357 收藏
-
352 收藏
-
101 收藏
-
440 收藏
-
212 收藏
-
143 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习