Выберите на ходу отправку и получение канала одновременно

Предположим, у меня есть буферизованный канал отправки и небуферизованный канал приема:

s := make(chan<- int, 5)
r := make(<-chan int)

Можно ли select на них обоих, чтобы r выбирался, если есть что читать, и s, если не заполнен? Что-то эквивалентное этому, но не использующее 100% ЦП:

for {
    if len(s) < cap(s) {
        // Send something
    }
    if len(r) > 0 {
        // Receive something
    }
}

Обратите внимание, что я хочу решить, что отправить, во время отправки, а не раньше.

Редактировать

Этот вопрос в основном эквивалентен вопросу "Могу ли я заблокировать, пока канал не будет готов к отправке, ничего не отправляя?"


person Timmmm    schedule 27.01.2015    source источник


Ответы (4)


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

Поэтому добавьте случай default, который будет выполняться, если ни один из каналов не готов, в котором вы просто немного «спите», а затем попробуйте еще раз (с обновленным новым значением, рассчитанным/полученным для отправки). Во время сна вы не будете потреблять ресурсы процессора:

s := make(chan<- int, 5)
r := make(<-chan int)

for {
    v := valueToSend() // Evaluated each time we try to send
    select {
    case s <- v:
        fmt.Println("Sent value:", v)
    case vr := <-r:
        fmt.Println("Received:", vr)
    default: // If none are ready currently, we end up here
        time.Sleep(time.Millisecond * 1)
    }
}

Обратите внимание, что проверка длины или пропускной способности канала а затем отправка/получение не считается хорошим решением, поскольку канал может оказаться не готовым в промежутке между проверкой его длины и cap и вы пытаетесь отправить/получить, как показано ниже:

if len(r) > 0 {
    // r is ready to receive

    // Optional other code here,
    // meanwhile another goroutine might receive the value from r!

    r <-  // If other goroutine received from r, this will block!
}
person icza    schedule 27.01.2015
comment
Да, пока решение Sleep(1ms) работает, оно кажется неэлегантным, и оно определенно не даст процессору проснуться. Также проверка длины перед отправкой, безусловно, хороша, если я знаю, что являюсь единственным отправителем. - person Timmmm; 27.01.2015
comment
@Timmmm Да, использование Sleep не элегантно, но то, чего вы пытаетесь достичь, не может быть выполнено другими способами, потому что вы хотите, чтобы значение определялось / вычислялось в то время, когда канал становится готовым. Если бы это не было требованием, простой select сделал бы это. - person icza; 27.01.2015
comment
Также обратите внимание, что если 2 канала не связаны, вы можете просто использовать 2 горутины, одну для отправки, а другую для получения. Если канал не готов, операция просто заблокируется и не будет потреблять ресурсы ЦП. - person icza; 27.01.2015
comment
Также ваше решение вызывает valueToSend() каждую миллисекунду. Для меня это немного дорогая операция, и ее следует вызывать только при необходимости. На самом деле мой первоначальный ответ с добавленным time.Sleep() лучше. - person Timmmm; 27.01.2015

Это простой выбор:

select {
case s <- n:
    // Successful send.
case n := <- r:
    // Successful receive. Do something with n.
}
person Ainar-G    schedule 27.01.2015
comment
Но когда оценивается s <- n? Что делать, если я хочу отправить текущее время через канал, например. - person Timmmm; 27.01.2015
comment
@Timmmm Это указано в Spec. Правосторонние выражения операторов отправки оцениваются ровно один раз, при входе в оператор выбора. - person Ainar-G; 27.01.2015

Вместо того, чтобы отправлять значение напрямую, вы можете отправить объект, который может вычислить значение. Затем вы можете определить, когда объект отправлен, а затем вычислить. Вы можете использовать sync.Once, чтобы убедиться, что вычисление выполняется один раз, и закрыть доступ к результату. Это позволяет избежать использования сна.

Примерно так: https://play.golang.org/p/oL2HA2jl91

person Dustin    schedule 28.01.2015

Могу ли я заблокировать, пока канал не будет готов к отправке, ничего не отправляя?

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

https://godoc.org/github.com/eapache/channels#SharedBuffer

person Evan    schedule 27.01.2015