Основная функция не ждет чтения канала перед выходом?

Рассмотрим следующую попытку реализовать Обеденные философы с подпрограммами и каналами 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

Вопрос: Зачем такое бывает?

Что я уже знаю:

  1. Программа завершится, если процедура main go завершена (даже если некоторые другие процедуры все еще выполняются).

  2. Подпрограмма 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
}

Тем не менее, было бы здорово, если бы кто-то мог подтвердить это объяснение :)

@downvoter: не хотите уточнить, в чем проблема с этим вопросом? Может быть, я могу попытаться улучшить его.

Attilio 25.04.2019 13:41
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
1
1
94
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

То, что вы видите в стандартном выводе, не совпадает с тем, что происходит. Иногда 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, иногда нет. Это не означает, что чтение канала не происходит.

Спасибо, я тоже только что пришел к этому выводу, еще раз взглянув на код :)

Attilio 24.04.2019 19:28

На самом деле канал читается 5 раз, но так как функция main ждет только 5-го чтения канала, то она завершается до того, как функция philos доберется до этой строки:

fmt.Printf("Philosopher # %d finished eating\n", id)`

Чтобы он печатал это правильно, вам нужно будет запустить эту строку перед записью в канал пластины.

Другие вопросы по теме