Как я могу вычислить сообщение, которое будет отправлено на канал как можно позже?

Мой сценарий:

  • У меня есть производитель и потребитель. Обе являются горутинами и общаются через один канал.
  • Производитель способен (теоретически) генерировать сообщение в любое время.
  • Генерация сообщения требует некоторых вычислений.
  • Сообщение в некоторой степени чувствительно ко времени (т. е. чем оно старше, тем менее актуально).
  • Потребитель время от времени читает с канала. Для примера предположим, что потребитель использует time.Ticker для чтения сообщения каждые пару секунд.
  • Потребитель предпочел бы «свежие» сообщения (то есть сообщения, которые были сгенерированы как можно раньше).

Итак, возникает вопрос: Как производитель может сгенерировать сообщение как можно позже?


Пример кода, демонстрирующий общую идею:

func producer() {
    for {
        select {
        ...
        case pipe <- generateMsg():
            // I'd like to call generateMsg as late as possible,
            // i.e. calculate the timestamp when I know
            // that writing to the channel will not block.
        }
    }
}

func consumer() {
    for {
        select {
        ...
        case <-timeTicker.C:
            // Reading from the consumer.
            msg <- pipe
            ...
        }
    }
}

Полный код (немного отличающийся от приведенного выше) доступен на Go Playground: https://play.golang.org/p/y0oCf39AV6P


Одна из идей, которые у меня были, состояла в том, чтобы проверить, не будет ли блокироваться запись на канал. Если бы он не блокировался, я мог бы сгенерировать сообщение, а затем отправить его. Однако…

  • Я не мог найти способа проверить, будет ли блокироваться запись на канал или нет.
  • В общем случае это плохая идея, потому что она вводит условие гонки, если у нас есть несколько производителей. В данном конкретном случае у меня только один производитель.

Другая (плохая) идея:

func producer() {
    var msg Message
    for {
        // This is BAD. DON'T DO THIS!
        select {
        case pipe <- msg:
            // It may send the same message multiple times.
        default:
            msg = generateMsg()
            // It causes a busy-wait loop, high CPU usage
            // because it re-generates the message all the time.
        }
    }
}

person Denilson Sá Maia    schedule 12.06.2019    source источник
comment
Если вас волнует срочность, зачем использовать бегущую строку? Почему бы просто не прочитать, как только сообщение будет готово?   -  person Flimzy    schedule 12.06.2019
comment
ИМО, использование канала и подпрограммы для этого требования побеждает цель. Это проблема производителя-потребителя по запросу. Если вы не хотите генерировать сообщение асинхронно, т. е. производитель не должен производить до тех пор, пока этого не захочет потребитель, то зачем вам в первую очередь канал и рутина? Почему вы не можете просто вызвать функцию напрямую, и если generateMsg включает доступ к критической памяти, то защитите ее с помощью Mutex. Мьютекс не зло, в конце концов, канал просто оборачивает Мьютекс, насколько я знаю, скрывает все грязные детали.   -  person IhtkaS    schedule 12.06.2019
comment
Я исключил из сценария некоторые другие случаи оператора select. Производитель на самом деле считывает данные с другого канала, вычисляет некоторую статистику «на лету» и передает эту статистику потребителю. Потребителю нужно отображать статистику только один раз в секунду или около того, поэтому ему нужно потреблять только один раз в секунду. Но, возможно, это слишком запутанно, как вы говорите.   -  person Denilson Sá Maia    schedule 12.06.2019
comment
@DenilsonSáMaia: Пусть он вычисляет статистику раз в секунду. Нет необходимости в шаблоне потребитель/производитель, горутинах или каналах.   -  person Flimzy    schedule 12.06.2019
comment
Согласовано. Это похоже на случай разделяемой памяти, которую производитель обновляет по своему расписанию, а потребитель читает по своему расписанию, с блокировкой мьютекса, чтобы избежать гонок.   -  person Adrian    schedule 12.06.2019
comment
Не лучше ли полностью изменить логику? Производитель, когда он готов создать сообщение, просто отправляет какой-то токен потребителю, что означает, что я готов к работе. Затем потребитель получает токен и либо явно просит производителя выполнить работу, либо выполняет работу сам. В простейшем случае производитель может отправить значение функции, которое затем вызовет потребитель. Конечно, в этом случае следует продумать синхронизацию, если она нужна, так как потребитель инициирует выполнение работы в неопределенный момент времени.   -  person kostix    schedule 12.06.2019


Ответы (1)


Этот ответ (для Отправить неблокирующий канал , тест на отказ перед попыткой отправки?) предлагает использовать второй канал для отправки сигнала от потребителя к производителю:

  1. Потребитель хочет получить сообщение (например, после получения тика от timer.Ticker).
  2. Потребитель отправляет сигнал через побочный канал горутине производителя. (Итак, для этого побочного канала роли производителя/потребителя меняются местами).
  3. Производитель получает сигнал из побочного канала.
  4. Производитель начинает вычислять реальное сообщение.
  5. Производитель отправляет сообщение по основному каналу.
  6. Потребитель получает сообщение.
person Denilson Sá Maia    schedule 12.06.2019
comment
Это кажется мне слишком сложным. На самом деле, весь сценарий кажется ненужным усложнением без какой-либо выгоды. - person Flimzy; 12.06.2019