如何计算尽可能晚在通道上发送的消息?

My scenario:

  • I have a producer and a consumer. Both are goroutines, and they communicate through one channel.
  • The producer is capable of (theoretically) generating a message at any time.
  • Generating a message requires some computation.
  • The message is somewhat time-sensitive (i.e. the older it is, the less relevant it is).
  • The consumer reads from the channel occasionally. For the sake of this example, let's say the consumer uses a time.Ticker to read a message once every couple of seconds.
  • The consumer would prefer "fresh" messages (i.e. messages that were generated as recently as possible).

So, the question is: How can the producer generate a message as late as possible?


Sample code that shows the general idea:

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
            ...
        }
    }
}

Full code (slightly different from above) is available at the Go Playground: https://play.golang.org/p/y0oCf39AV6P


One idea I had was to check if writing to a channel would block. If it wouldn't block, then I can generate a message and then send it. However…

  • I couldn't find any way to test if writing to a channel would block or not.
  • In the general case, this is a bad idea because it introduces a racing condition if we have multiple producers. In this specific case, I only have one producer.

Another (bad) idea:

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.
        }
    }
}

This answer (for Go non-blocking channel send, test-for-failure before attempting to send?) suggests using a second channel to send a signal from the consumer to the producer:

  1. Consumer wants to get a message (e.g. after receiving a tick from timer.Ticker).
  2. Consumer sends a signal through a side channel to the producer goroutine. (So, for this side channel, the producer/consumer roles are reversed).
  3. Producer receives the signal from the side channel.
  4. Producer starts computing the real message.
  5. Producer sends the message through the main channel.
  6. Consumer receives the message.