Golang: оператор select завершает работу, когда не должен

Я пытаюсь создать программу, которая печатает "Eat", "Work", "Sleep" каждые 3, 8 и 24 секунды соответственно. Вот мой код:

package main

import (
"fmt"
"time"
)

func Remind(text string, delay time.Duration) <-chan string { //channel only for receiving strings
    ch := make(chan string) // buffered/unbuffered?
    go func() {
        for {
            msg := "The time is " + time.Now().Format("2006-01-02 15:04:05 ") + text
            ch <- msg
            time.Sleep(delay) // waits according to specification
        }
    }()
    return ch
}

func main() {
    ch1 := Remind("Eat", 1000*1000*1000*3) // every third second    
    ch2 := Remind("Work", 1000*1000*1000*8) // every eighth second
    ch3 := Remind("Sleep", 1000*1000*1000*24) // every 24th second
    select { // chooses one channel that is not empty. Should run forever (?)
        case rem1 := <-ch1:
            fmt.Println(rem1)
        case rem2 := <-ch2:
            fmt.Println(rem2)
        case rem3 := <-ch3:
            fmt.Println(rem3)
    }
}

Проблема с ним в том, что он перестает работать сразу после печати времени, за которым следует "Eat". В других примерах, которые я читал, оператор select продолжается вечно. Почему сейчас нет?


person Sahand    schedule 31.03.2016    source источник


Ответы (2)


Не знаю, где вы прочитали, что select продолжается вечно, но это не так.

После выполнения оператора case оператор select считается выполненным. Если ни одна из коммуникационных операций, указанных в cases, не может быть выполнена, и отсутствует default ветвь, select будет заблокирована до тех пор, пока какая-либо из com. операции могут продолжаться. Но как только выполняется case, select не повторяется.

Прочтите соответствующий раздел спецификации: Выбор операторов.

Поместите его в бесконечную for, чтобы он повторялся вечно:

for {
    select { // chooses one channel that is not empty. Should run forever (?)
    case rem1 := <-ch1:
        fmt.Println(rem1)
    case rem2 := <-ch2:
        fmt.Println(rem2)
    case rem3 := <-ch3:
        fmt.Println(rem3)
    }
}

В качестве примечания:

Вы можете создавать значения time.Duration намного проще, используя константы из time:

ch1 := Remind("Eat", 3*time.Second)    // every third second
ch2 := Remind("Work", 8*time.Second)   // every eighth second
ch3 := Remind("Sleep", 24*time.Second) // every 24th second

Вы также можете проверить тип time.Ticker, который предназначен для задач, аналогичных вашей функции Remind().

person icza    schedule 31.03.2016
comment
Наверное, тогда что-то не так понял. Спасибо! - person Sahand; 31.03.2016
comment
@Sandi Скорее всего, select ждет, если операции связи не могут быть продолжены, если это необходимо, навсегда. Но не повторяется. - person icza; 31.03.2016
comment
То есть если, например, все каналы пусты, select будет ждать? - person Sahand; 31.03.2016
comment
@Сэнди Да, что-то в этом роде. Например, select{} блокирует навсегда. - person icza; 31.03.2016

select в Go больше всего напоминает оператор управления переключателем и иногда называется коммуникационным переключателем. select прослушивает входящие данные по каналам, но также могут быть случаи, когда значение отправляется по каналу. Одним словом select используется для получения или отправки значений при одновременном выполнении горутин.

В вашем примере, поскольку вы выполняете текущее время в основной горутине, оно всегда выполняется. Но поскольку другие горутины выполняются в операторе select, у них не всегда есть возможность выполниться, потому что после выполнения case канал блокируется.

Что делает выбор:

  • если все заблокированы, он ждет, пока один из них не сможет продолжить
  • если несколько могут продолжиться, он выбирает один случайным образом.
  • когда ни одна из операций канала не может быть продолжена и присутствует предложение по умолчанию, выполняется следующее: значение по умолчанию всегда готово к выполнению (то есть: готово к выполнению).

Использование операции отправки в операторе select с регистром по умолчанию гарантирует, что отправка будет неблокирующей!

Чтобы запустить навсегда, используйте его в цикле for:

package main

import (
"fmt"
"time"
)

func Remind(text string, delay time.Duration) <-chan string { //channel only for receiving strings
    ch := make(chan string) // buffered/unbuffered?
    go func() {
        for {
            msg := "The time is " + time.Now().Format("2006-01-02 15:04:05 ") + text
            ch <- msg
            time.Sleep(delay) // waits according to specification
        }
    }()
    return ch
}

func main() {
    ch1 := Remind("Eat", 1000*1000*1000*3) // every third second    
    ch2 := Remind("Work", 1000*1000*1000*8) // every eighth second
    ch3 := Remind("Sleep", 1000*1000*1000*24) // every 24th second
    for {
        select { // chooses one channel that is not empty. Should run forever (?)
        case rem1 := <-ch1:
            fmt.Println(rem1)
        case rem2 := <-ch2:
            fmt.Println(rem2)
        case rem3 := <-ch3:
            fmt.Println(rem3)
        }
    }
}

http://play.golang.org/p/BuPqm3xsv6

person Endre Simo    schedule 31.03.2016
comment
Спасибо за ответ. Что означает блокировка канала? У меня возникли проблемы с поиском определения этого. - person Sahand; 31.03.2016