Рассмотрим следующую попытку реализовать Обеденные философы с подпрограммами и каналами Go.
package main
import "fmt"
func philos(id int, left, right, plate chan bool) {
fmt.Printf("Philosopher # %d wants to eat\n", id)
<-left
<-right
plate <- true
left <- true
right <- true
fmt.Printf("Philosopher # %d finished eating\n", id)
}
func main() {
const numPhilos = 5
var forks [numPhilos]chan bool
for i := 0; i < numPhilos; i++ {
forks[i] = make(chan bool, 1)
forks[i] <- true
}
plates := make(chan bool)
for i := 0; i < numPhilos; i++ {
go philos(i, forks[(i-1+numPhilos)%numPhilos], forks[(i+numPhilos)%numPhilos], plates)
}
for i := 0; i < numPhilos; i++ {
<-plates
}
}
Иногда это работает так, как ожидалось, т.е. все философы едят, например:
Philosopher # 4 wants to eat
Philosopher # 3 wants to eat
Philosopher # 2 wants to eat
Philosopher # 1 wants to eat
Philosopher # 4 finished eating
Philosopher # 3 finished eating
Philosopher # 2 finished eating
Philosopher # 1 finished eating
Philosopher # 0 wants to eat
Philosopher # 0 finished eating
Однако иногда пропускается один (или несколько) философов (например, Философ № 0 не ел в следующем случае):
Philosopher # 4 wants to eat
Philosopher # 1 wants to eat
Philosopher # 3 wants to eat
Philosopher # 2 wants to eat
Philosopher # 4 finished eating
Philosopher # 0 wants to eat
Philosopher # 2 finished eating
Philosopher # 1 finished eating
Philosopher # 3 finished eating
Вопрос: Зачем такое бывает?
Что я уже знаю:
Программа завершится, если процедура main
go завершена (даже если некоторые другие процедуры все еще выполняются).
Подпрограмма go заблокируется, если она попытается прочитать из канала, а этот канал пуст (т. е. в него ранее никто не писал).
Теперь main
пытается прочитать 5 раз из канала plates
, поэтому он не должен завершаться, пока подпрограмма philos
не будет выполнена пять раз. Но кажется, что каким-то образом ему все же удается завершить работу, прежде чем это сделать. Я что-то пропустил? (Кажется, plates
было прочитано всего 4 раза.)
РЕДАКТИРОВАТЬ: Хорошо, подумав еще немного, я пришел к выводу, что, может быть, подпрограмма philos
всегда выполняется 5 раз, однако ее можно прервать до, успев напечатать, что философ съел. Действительно, если я изменю порядок следующим образом, он всегда будет работать:
func philos(id int, left, right, plate chan bool) {
fmt.Printf("Philosopher # %d wants to eat\n", id)
<-left
<-right
left <- true
right <- true
fmt.Printf("Philosopher # %d finished eating\n", id)
plate <- true
}
Тем не менее, было бы здорово, если бы кто-то мог подтвердить это объяснение :)
То, что вы видите в стандартном выводе, не совпадает с тем, что происходит. Иногда main
получает от plates
, а затем возвращается до того, как произойдет оператор печати. Так:
plate <- true
left <- true // On this line or on
right <- true // this line, main receives from plate and then returns before
fmt.Printf("Philosopher # %d finished eating\n", id) // this line executes
Поскольку параллелизм не является детерминированным, это происходит не каждый раз. Иногда печать происходит до возвращения main
, иногда нет. Это не означает, что чтение канала не происходит.
Спасибо, я тоже только что пришел к этому выводу, еще раз взглянув на код :)
На самом деле канал читается 5 раз, но так как функция main ждет только 5-го чтения канала, то она завершается до того, как функция philos доберется до этой строки:
fmt.Printf("Philosopher # %d finished eating\n", id)`
Чтобы он печатал это правильно, вам нужно будет запустить эту строку перед записью в канал пластины.
@downvoter: не хотите уточнить, в чем проблема с этим вопросом? Может быть, я могу попытаться улучшить его.