How to drop old value in a channel salety

1,980 views
Skip to first unread message

陶青云

unread,
Nov 12, 2020, 9:19:31 PM11/12/20
to golang-nuts
```
// c  is buffered channel
select {
case c <- result:
default:
     // full, drop old one
      <-c
      c <- result
 }
```

I have a code like this,  but there may be a race condition in the default case. I enconter a
deadlock that the goroutine block on  'c<'.


Ian Lance Taylor

unread,
Nov 12, 2020, 9:56:05 PM11/12/20
to 陶青云, golang-nuts
On Thu, Nov 12, 2020 at 6:19 PM 陶青云 <qing...@gmail.com> wrote:
>
> ```
A channel normally communicates from one goroutine to another. It
doesn't make much sense for a single goroutine to both send and
receive on the same channel, unless there is some protocol that
specifies when the goroutine should switch from sending to receiving.
Code like you show above is inherently racy. When the select starts,
the channel might be full, so the select chooses the default case.
Immediately after the select chooses the default case, but before it
runs the channel receive operation, other goroutines might receive all
the values from the channel. Then, the receive on the original
channel will start, but there is nothing left on the channel, so it
will hang.

The way to avoid this kind of race is for a goroutine to always either
send or receive on a channel, never both.

Ian

Kurtis Rader

unread,
Nov 12, 2020, 10:27:23 PM11/12/20
to 陶青云, golang-nuts
On Thu, Nov 12, 2020 at 6:19 PM 陶青云 <qing...@gmail.com> wrote:
```

In addition to what Ian said you should note that a buffered channel is essentially a FIFO queue. The equivalent code that operates on a queue in any other language (e.g., Python) will have the same race. What you're asking for is something like the behavior provided by an API such as Python's collections.deque. Whether any of the solutions you might find by searching for something like "golang deque" satisfy your requirements can't be answered given the information you have provided.

--
Kurtis Rader
Caretaker of the exceptional canines Junior and Hank

陶青云

unread,
Nov 13, 2020, 12:32:31 AM11/13/20
to golang-nuts
Thanks. I want the receiver always get the relately new vaule,  I don't want the sender blocked and I either choose drop the current value or the first value of the channel. But I don't find a way to safely drop the first value from the channel.

Maybe like this ?
```
// c  is buffered channel
select {
case c <- result:
default:
     // full, drop old one
     go func() {
          <-c
          c <- result
     }()
 }
```

Kurtis Rader

unread,
Nov 13, 2020, 1:36:13 AM11/13/20
to 陶青云, golang-nuts
On Thu, Nov 12, 2020 at 9:32 PM 陶青云 <qing...@gmail.com> wrote:
Thanks. I want the receiver always get the relately new vaule,  I don't want the sender blocked and I either choose drop the current value or the first value of the channel. But I don't find a way to safely drop the first value from the channel.

You seem to be talking about a buffered channel of length one. If that is true why are you using a channel? You can instead use a simple var protected by a mutex. If you're talking about a buffered channel with size greater than one it is unclear why a full channel should drop the first entry in the channel rather than multiple (even all) entries in the queue. This seems like an XY problem.
 
Maybe like this ?

No, since that "solution" just replaces one race with another.

陶青云

unread,
Nov 13, 2020, 2:13:40 AM11/13/20
to golang-nuts

It is not a one-length buffered channel.  I need to drop because the recveiver do heavy work that can't process as quickly as sender.

Ian Lance Taylor

unread,
Nov 13, 2020, 10:19:46 AM11/13/20
to 陶青云, golang-nuts
On Thu, Nov 12, 2020 at 11:13 PM 陶青云 <qing...@gmail.com> wrote:
>
>
> It is not a one-length buffered channel. I need to drop because the recveiver do heavy work that can't process as quickly as sender.

Don't use channels for this. It's not what they are for.

Ian



> 在2020年11月13日星期五 UTC+8 下午2:36:13<Kurtis Rader> 写道:
>>
>> On Thu, Nov 12, 2020 at 9:32 PM 陶青云 <qing...@gmail.com> wrote:
>>>
>>> Thanks. I want the receiver always get the relately new vaule, I don't want the sender blocked and I either choose drop the current value or the first value of the channel. But I don't find a way to safely drop the first value from the channel.
>>
>>
>> You seem to be talking about a buffered channel of length one. If that is true why are you using a channel? You can instead use a simple var protected by a mutex. If you're talking about a buffered channel with size greater than one it is unclear why a full channel should drop the first entry in the channel rather than multiple (even all) entries in the queue. This seems like an XY problem.
>>
>>>
>>> Maybe like this ?
>>
>>
>> No, since that "solution" just replaces one race with another.
>>
>> --
>> Kurtis Rader
>> Caretaker of the exceptional canines Junior and Hank
>
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/5f5cb0c2-54e9-4d5f-bbc7-43c902615b8fn%40googlegroups.com.

Jesper Louis Andersen

unread,
Nov 13, 2020, 10:57:34 AM11/13/20
to 陶青云, golang-nuts
On Fri, Nov 13, 2020 at 8:12 AM 陶青云 <qing...@gmail.com> wrote:

It is not a one-length buffered channel.  I need to drop because the recveiver do heavy work that can't process as quickly as sender.

You need to write a flow control scheme then. Channels may be part of the solution, but they can't solve it on their own. If the receiver can periodically stop work and check on the channel, a relatively simple way is to record queue sojourn: add a timestamp at send time, and call time.Since upon pulling it out of the queue. The duration in the queue is the sojourn time, and you can use that to error out on work that has spent too long in the queue. If this happens inside the same goroutine, just use a standard queue, or perhaps a ring buffer though memory eludes me if the stdlib ring is useful for this case.

Aleksey Tulinov

unread,
Nov 13, 2020, 11:11:44 AM11/13/20
to 陶青云, golang-nuts
Try to use two channels: one to signal that the receiver needs a new
value and another one to send new value to the receiver. Supposedly
the sender won't block if you're using `select` to check what
receivers need values, and the receiver can block until a new value is
arrived at the input channel.

However as others pointed out, if you're experiencing resistance from
the framework, it might indicate that you are trying to implement some
less than idiomatic approach and perhaps an overhaul of this approach
might be more beneficial.

Hope this helps.

пт, 13 нояб. 2020 г. в 09:12, 陶青云 <qing...@gmail.com>:

Tarmigan

unread,
Nov 13, 2020, 1:09:49 PM11/13/20
to Ian Lance Taylor, 陶青云, golang-nuts
I see some smart people suggesting that what OP is doing is not
correct. I am going to cautiously disagree with some of those replies
and suggest that a similar pattern can be useful for some
applications.

We have an application where we have a similar need. Specifically,
there is a periodic ticker goroutine that calculates the value to send
and there is an IO goroutine that performs i/o with the latest value
(and that i/o could block for an unknown amount of time. It is OK to
drop values that are not the most recent values, but we would like to
have the i/o happen as quickly as possible after the calculation. As
an example, imagine an application that cares about soft realtime
responsiveness like telephony or multiplayer gaming (those
applications would probably be OK using UDP for example).

For a long time we used some shared memory for the value protected by
a mutex. Then the signaling would be performed using a condition
variable. That worked OK, but we also wanted to have a way to queue a
value if i/o is in progress and have a second round of i/o start
immediately after the first one returns so we needed another indicator
for that. We ended up using a pointer to the value where the nil
pointer could represent that no new value is waiting. We ran with
that code for years and it worked, but was a little subtle and hard to
follow.

Eventually I rewatched Bryan Mill's excellent "Rethinking Classical
Concurrency" talk (https://www.youtube.com/watch?v=5zXAHh5tJqQ) and
tried to think about how to apply some of those ideas to the problem
instead of using the condition variable. My initial solution was
similar to some of the ideas in the talk and have 2 channels: 1 as a
semaphore to control the "direction" of the flow and the other channel
with the value. Thinking about it more, the thing being sent over the
"semaphore" channel could actually be the value itself as long as only
one goroutine is doing the sending. That simplifies the design down
to a single channel. The pattern we ended up looks like this:
https://play.golang.org/p/m6ONLGZ7nU2

There are a few caveats with that solution:
- Only one goroutine can do the triggering and sending on the channel
(if you wanted to have multiple sending goroutines, then I think the
multiple channel semaphore pattern can work).
- With a single channel (and single goroutine trigger) solution, the
buffered channel should be drained unconditionally by the triggering
goroutine in a non-blocking way before each send of a new value.
- There is a logical race (not a data race) between the triggering
goroutine and the i/o goroutine and the values that actually get sent
will be subject to the i/o delays and goroutine scheduler. Your
application must be OK with that (that was acceptable for our
application).

Best,
Tarmigan
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CAOyqgcUjEPj3%2BWFpnBsgiPvKMZqbpNn%3DM1-eN6m6B4bXtvRp7A%40mail.gmail.com.

Gregg Townsend

unread,
Nov 13, 2020, 2:09:24 PM11/13/20
to golang-nuts
If I understand what you're trying to do, I'd approach it this way, using a generously buffered channel and discarding the extras at the consumer, as shown below, instead of at the producer:

result <- c // wait for result to appear
for len(c) > 0 {
    // there is a newer result available
    result <- c
}
// process latest available result




陶青云

unread,
Nov 14, 2020, 8:43:21 PM11/14/20
to golang-nuts
In normal case I don't want to discarding.  Even consumer find the channel is full, it should not to discard.


陶青云

unread,
Nov 14, 2020, 8:43:54 PM11/14/20
to golang-nuts
在2020年11月13日星期五 UTC+8 下午11:19:46<Ian Lance Taylor> 写道:
On Thu, Nov 12, 2020 at 11:13 PM 陶青云 <qing...@gmail.com> wrote:
>
>
> It is not a one-length buffered channel. I need to drop because the recveiver do heavy work that can't process as quickly as sender.

Don't use channels for this. It's not what they are for.

Ian

I see some examples using the buffered channel as a job queue. Could you give me some advise on this.

Kurtis Rader

unread,
Nov 14, 2020, 9:48:52 PM11/14/20
to 陶青云, golang-nuts
On Sat, Nov 14, 2020 at 5:43 PM 陶青云 <qing...@gmail.com> wrote:
Don't use channels for this. It's not what they are for.

Ian  
I see some examples using the buffered channel as a job queue. Could you give me some advise on this.

If you search for terms like "go channel job queue" you'll find examples that at first glance appear relevant to your problem. Such as https://www.opsdash.com/blog/job-queues-in-go.html. But none of them, AFAICT, implement the behavior you want. If you don't need compatibility with Go's `select` statement probably the simplest solution is a ring (circular) buffer. Search for "go ring buffer" for some examples; although, implementing a ring buffer is simple enough it can be used as an interview question. So I leave the details as an exercise for the student.

You could use a Go channel if you added an explicit mutex that has to be acquired by both the channel consumer(s) and producer(s) before popping and/or inserting a value into the channel. But that is suboptimal since the channel send/receive ops already use an implicit mutex to make those individual operations concurrent safe. The only purpose of the explicit mutex is to make it possible for the producer(s) in your example to atomically drop an "old" entry before inserting a "new" entry. As Ian said a Go channel is the wrong mechanism for your problem.

Robert Engels

unread,
Nov 14, 2020, 10:55:37 PM11/14/20
to Kurtis Rader, 陶青云, golang-nuts
I don’t think a ring buffer works. At least in a traditional ring buffer the producer cannot pass the consumer - if the buffer is full the producer blocks or drops. Trying to ensure the consumer always reads the most recent available item is better (and more simply) served with a shared hand off queue. 

On Nov 14, 2020, at 8:48 PM, Kurtis Rader <kra...@skepticism.us> wrote:


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Kurtis Rader

unread,
Nov 14, 2020, 11:19:07 PM11/14/20
to Robert Engels, 陶青云, golang-nuts
On Sat, Nov 14, 2020 at 7:54 PM Robert Engels <ren...@ix.netcom.com> wrote:
I don’t think a ring buffer works. At least in a traditional ring buffer the producer cannot pass the consumer - if the buffer is full the producer blocks or drops. Trying to ensure the consumer always reads the most recent available item is better (and more simply) served with a shared hand off queue.

It is trivial to create a ring buffer type that drops the oldest queued item when appending a new item if the ring is full. Which would seem to be the behavior the O.P. wants. The important question in this scenario is whether such an object must be useful in a `select` statement. If not the implementation is borderline trivial.

Robert Engels

unread,
Nov 14, 2020, 11:35:05 PM11/14/20
to Kurtis Rader, 陶青云, golang-nuts
That wasn’t my take on the OPs need. He said the consumer is very expensive - implying to me they only want to process the latest. If dropping the oldest is viable you might as well drop all old entries and use the latest. 

Pretty common in trading systems with a price feed - in many cases only the latest price matters. 

Not a big deal either way - only pointing it out. 

On Nov 14, 2020, at 10:18 PM, Kurtis Rader <kra...@skepticism.us> wrote:


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Kurtis Rader

unread,
Nov 15, 2020, 12:13:03 AM11/15/20
to Robert Engels, 陶青云, golang-nuts
On Sat, Nov 14, 2020 at 8:34 PM Robert Engels <ren...@ix.netcom.com> wrote:
That wasn’t my take on the OPs need. He said the consumer is very expensive - implying to me they only want to process the latest. If dropping the oldest is viable you might as well drop all old entries and use the latest. 

The O.P. wrote two days ago:

> Thanks. I want the receiver always get the relately new vaule,  I don't want the sender blocked and I either choose drop the current value or the first value of the channel. But I don't find a way to safely drop the first value from the channel.

In other words, they want the consumer to always fetch, and process, the oldest available value. They want the producer(s) to always enqueue their value; even if doing so requires dropping the oldest value from the queue to make room for the new value. Whether that is sensible behavior, thus requiring something other than a simple Go channel, is not obvious given what the O.P. has written so far.

Tamás Gulácsi

unread,
Nov 15, 2020, 2:56:52 AM11/15/20
to golang-nuts
If you read more than write, then you can use a sync.RWMutex an RLock when reading, Lock when writing.

陶青云

unread,
Nov 15, 2020, 6:45:12 PM11/15/20
to golang-nuts
The ring buffer should work, but it seems to need the consumer to test and sleep when the ring is empty, which is not as efficient as a channel.

Kurtis Rader

unread,
Nov 15, 2020, 9:01:50 PM11/15/20
to 陶青云, golang-nuts
If I was doing this in C I would use a counting semaphore. In Go create a channel of the empty interface. Whenever the producer adds an entry to the ring, and the ring isn't already full, it sends `nil` on the channel. When the consumers/workers receive a `nil` token from the channel they know there is a new entry in the ring buffer to be processed. Voila! Your ring buffer now plays nicely with `select. Note that you still need a spinlock to protect modifications of the ring buffer by the producer(s) and consumers.

Robert Engels

unread,
Nov 15, 2020, 10:22:15 PM11/15/20
to Kurtis Rader, 陶青云, golang-nuts
I will argue if it is ok to drop the oldest then it is ok to drop all but the latest. You need more metrics on volume and latency requirements to know which is the proper approach. 

On Nov 15, 2020, at 8:01 PM, Kurtis Rader <kra...@skepticism.us> wrote:


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Kurtis Rader

unread,
Nov 15, 2020, 10:33:34 PM11/15/20
to Robert Engels, 陶青云, golang-nuts
On Sun, Nov 15, 2020 at 7:21 PM Robert Engels <ren...@ix.netcom.com> wrote:
I will argue if it is ok to drop the oldest then it is ok to drop all but the latest. You need more metrics on volume and latency requirements to know which is the proper approach.

If true your solution implies the O.P.'s question is an example of the XY problem. But since the behavior they asked for was not an obvious example of the XY problem it is reasonable to take their requirements at face value. In my four decades of programming I've seen (or read about) many scenarios I might have initially responded to, incorrectly, with a "WTF?".

Robert Engels

unread,
Nov 15, 2020, 10:39:44 PM11/15/20
to Kurtis Rader, 陶青云, golang-nuts
If any can be dropped than clearly all are not needed. What is needed differs greatly if you have a billion events versus 10. Without more information on the system you cannot create an appropriate design.

It is my guess that a simple hand off queue will suffice in this case. (Otherwise you need to worry about ring size, per element processing time, event rate, etc). 

On Nov 15, 2020, at 9:33 PM, Kurtis Rader <kra...@skepticism.us> wrote:



Aleksey Tulinov

unread,
Nov 15, 2020, 11:01:48 PM11/15/20
to 陶青云, golang-nuts
If you're looking for efficiency, try to sync on a single mutex. The
best kind of syncing is the one that didn't happen, depending on your
circumstances, pthreads mutex can beat channels even taking into
account context switching because syncing on channel isn't free
either.

Implementing thread-safe pops() and pushes() to a channel with
optional blocking and all that stuff is kind of tricky business and
might require extra syncing here and there. If you could decay your
problem to sort of single producer single consumer then one mutex per
consumer might be enough to sync this and the rest of syncing can be
potentially bypassed completely.

Of course this depends on specific implementation under the Go
channels and may change over time, so this is just guessing and actual
performance has to be measured to make any reasonable conclusion.

пн, 16 нояб. 2020 г. в 01:44, 陶青云 <qing...@gmail.com>:
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/486267f7-c3fc-4563-814a-1fff54bb2d73n%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages