Different buffer implementations for channels

350 views
Skip to first unread message

Henrik Johansson

unread,
Jan 14, 2014, 7:07:48 AM1/14/14
to golang-nuts
Hi,

This may have been discussed before but would it not be practical to be able to control the behavior of the underlying channel buffer?

Say you don't want the buffer to block when it is full but simply drop one of the ends in an either fifo or lifo way.

It could probably be implemented in a wholly backwards compatible way as perhaps an extra argument to make of type chan.Buffer (or whatever makes sense)?

What do you think?

/ Henrik

Aram Hăvărneanu

unread,
Jan 14, 2014, 7:41:36 AM1/14/14
to Henrik Johansson, golang-nuts
No, because it's possible to implement the semantics you need with the
current channels and slices. It would also be bad because when I see c
<- foo in code I won't be able to know what it does until I look where
c is initialised.

This is a queue that drops old elements: http://play.golang.org/p/VxYNSFmqj4
For a stack that drops elements (why would you need that?) it's easier
to use a slice. If you really need it as a channel a goroutine can do
the translation for you.

--
Aram Hăvărneanu

egon

unread,
Jan 14, 2014, 7:45:33 AM1/14/14
to golan...@googlegroups.com


On Tuesday, January 14, 2014 2:07:48 PM UTC+2, Henrik Johansson wrote:
Hi,

This may have been discussed before but would it not be practical to be able to control the behavior of the underlying channel buffer?

Say you don't want the buffer to block when it is full but simply drop one of the ends in an either fifo or lifo way.

Konstantin Khomoutov

unread,
Jan 14, 2014, 7:58:26 AM1/14/14
to Aram Hăvărneanu, Henrik Johansson, golang-nuts
On Tue, 14 Jan 2014 13:41:36 +0100
Aram Hăvărneanu <ara...@mgk.ro> wrote:

[...]
> This is a queue that drops old elements:
> http://play.golang.org/p/VxYNSFmqj4 For a stack that drops elements
> (why would you need that?)

Why not? For instance, it's what network routers are supposed to do
with UDP datagrams when the outbound link is congested.
UDP is typically used to transmit data which has high redundancy such
as video streams. If a Go server is used for handling something like
this, using a queue discipline which only queues until the buffer is
full and then starts to drop incoming data appears to be reasonable.

Henrik Johansson

unread,
Jan 14, 2014, 7:58:34 AM1/14/14
to golang-nuts
My question is not at all related to any real  need I have now.
It was merely a thought I had when refreshing my message passing...

For example ttl functionality is something easily implemented on a case by case but and the ttl on a message clearly belong to the message.
The dropping of the message feels like it belongs to the queue though.

Maybe more advanced example would be priorities?

We are always advocating separation of concerns and I just think that some of these approaches may belong in the queuing rather than with the user. 



--
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.
For more options, visit https://groups.google.com/groups/opt_out.

Aram Hăvărneanu

unread,
Jan 14, 2014, 8:10:26 AM1/14/14
to Konstantin Khomoutov, Henrik Johansson, golang-nuts
> Why not? For instance, it's what network routers are supposed to do
> with UDP datagrams when the outbound link is congested.
> UDP is typically used to transmit data which has high redundancy such
> as video streams. If a Go server is used for handling something like
> this, using a queue discipline which only queues until the buffer is
> full and then starts to drop incoming data appears to be reasonable.

Ah, yes, dropping incoming data is very common, and trivial, I though
the OP meant something else.

--
Aram Hăvărneanu

egon

unread,
Jan 14, 2014, 8:12:05 AM1/14/14
to golan...@googlegroups.com


On Tuesday, January 14, 2014 2:58:34 PM UTC+2, Henrik Johansson wrote:
My question is not at all related to any real  need I have now.
It was merely a thought I had when refreshing my message passing...

For example ttl functionality is something easily implemented on a case by case but and the ttl on a message clearly belong to the message.
The dropping of the message feels like it belongs to the queue though.

Maybe more advanced example would be priorities?

We are always advocating separation of concerns and I just think that some of these approaches may belong in the queuing rather than with the user. 


Or rather a separate library.

I don't think chan was meant for IPC or as a "does everything related to queueing and messaging". If you are having these kinds of problems then you should probably use a dedicated messaging library/framework... e.g. RabbitMQ.

+ egon

Henrik Johansson

unread,
Jan 14, 2014, 8:20:05 AM1/14/14
to egon, golang-nuts
For networking I would go for something else at least until/if net chans come back.

My point is simply that some of these things may belong in the queue.
Maybe you have a windowing system where communication is distributed via channels and you may want to coalesce some events transparently,
maybe some events are more important?

It is just food for thought and I am sure there must have been much discussion when channels were first conceived.



minux

unread,
Jan 14, 2014, 9:01:16 PM1/14/14
to golang-nuts
On Tue, Jan 14, 2014 at 8:20 AM, Henrik Johansson <dahan...@gmail.com> wrote:
For networking I would go for something else at least until/if net chans come back.

My point is simply that some of these things may belong in the queue.
but the queue doesn't belong to the language, it could (and should) be implemented as
a package. 
Maybe you have a windowing system where communication is distributed via channels and you may want to coalesce some events transparently,
maybe some events are more important?
All these problems should be fixed in user programs, not on the Go language level. 

Henrik Johansson

unread,
Jan 15, 2014, 3:11:40 AM1/15/14
to minux, golang-nuts

What queue? I am talking about the channels underlying queue.

The whole point is to modify the behavior of the channel. If I can do it via a library, fine but it is not possible right?

There would need to be all sorts of intermediary buffers and a number of channels. This can easily be implemented in a library, sure but why not have it in the native channels? There is already the distinction between buffered and non-buffered channels that you have to consider actively when you code. This is no different.

I think it belongs in the language but there could be implementation details too destructive to make it worth it of course.

--

Peter Bourgon

unread,
Jan 15, 2014, 4:32:53 AM1/15/14
to Henrik Johansson, minux, golang-nuts
On Wed, Jan 15, 2014 at 9:11 AM, Henrik Johansson <dahan...@gmail.com> wrote:
> What queue? I am talking about the channels underlying queue.
>
> The whole point is to modify the behavior of the channel. If I can do it via
> a library, fine but it is not possible right?
>
> There would need to be all sorts of intermediary buffers and a number of
> channels. This can easily be implemented in a library, sure but why not have
> it in the native channels? There is already the distinction between buffered
> and non-buffered channels that you have to consider actively when you code.
> This is no different.
>
> I think it belongs in the language but there could be implementation details
> too destructive to make it worth it of course.

Language primitives like channels are building blocks, not prepackaged
solutions to classes of problems.

Systems with the semantics you describe are straightforward to
implement using channels as they exist today.

http://play.golang.org/p/JyTXqkBxVX
(Disclaimer: not thoroughly tested.)

Henrik Johansson

unread,
Jan 15, 2014, 4:50:02 AM1/15/14
to Peter Bourgon, minux, golang-nuts
I think the dropping case(s) lifo/fifo is a pretty natural extension to the current channels.
It provides a bit more control of the behavior without sacrificing easy and nice way that channels are used.

1. Unbuffered channels - Rendezvous
2. Buffered, blocking
3. Buffered non-blocking - drop newest 
4. Buffered non-blocking - drop oldest

And sure these can always be implemented (everything can) but that does not make it a bad evolution of the channels.



egon

unread,
Jan 15, 2014, 5:04:55 AM1/15/14
to golan...@googlegroups.com, Peter Bourgon, minux


On Wednesday, January 15, 2014 11:50:02 AM UTC+2, Henrik Johansson wrote:
I think the dropping case(s) lifo/fifo is a pretty natural extension to the current channels.
It provides a bit more control of the behavior without sacrificing easy and nice way that channels are used.

1. Unbuffered channels - Rendezvous
2. Buffered, blocking
3. Buffered non-blocking - drop newest 
4. Buffered non-blocking - drop oldest

And sure these can always be implemented (everything can) but that does not make it a bad evolution of the channels.


...and then out of order delivery, then prioritized delivery, then UDP style channels that can accept dropped messages... Where should the line be drawn?

If it goes into the language it must have a lot of use-cases; otherwise it's better to implement it in a library. If something is really necessary in the language you should be able to demonstrate it:
i.e. a list of 5-15 practical usefulness examples would be a good starting point, and then a pro/con analysis for adding that. And not just some examples that can be handled by a library... but by examples that will be often needed in many variations.

+ egon

Volker Dobler

unread,
Jan 15, 2014, 5:08:17 AM1/15/14
to golan...@googlegroups.com, Peter Bourgon, minux

Am Mittwoch, 15. Januar 2014 10:50:02 UTC+1 schrieb Henrik Johansson:
I think the dropping case(s) lifo/fifo is a pretty natural extension to the current channels.
It provides a bit more control of the behavior without sacrificing easy and nice way that channels are used.

1. Unbuffered channels - Rendezvous
2. Buffered, blocking
3. Buffered non-blocking - drop newest 
4. Buffered non-blocking - drop oldest

And sure these can always be implemented (everything can) but that does not make it a bad evolution of the channels.

Seeing code like
    select {
    case channel <- value:
    default:
    }
I know by looking at 4 local lines of code that value
won't be sent if nobody is reading from channel or
channel's buffer is full.

With some magic internal non-blocking flag I would
have to consult the definition of channel (which might
be somewhere else, maybe not even under my control).

I prefer the local variant and do not see any evolutionary
advantage of these non-blocking channels.

V.

Henrik Johansson

unread,
Jan 15, 2014, 5:13:46 AM1/15/14
to egon, golang-nuts, Peter Bourgon, minux
Yes you are right of course and I subscribe to the usage driven evolution of the language.

I have thought some more and probably the reasonable way would be to either provide some Policy interface that the caller could implement and provide or just settle for the two lifo/fifo cases in the builtin version.

I really don't have that many use cases at all since I really don't need it myself but the discussion can itself bring other people to find use cases that makes sense for them.
Sometimes you have to have it to see the use case.

Henrik Johansson

unread,
Jan 15, 2014, 5:20:26 AM1/15/14
to Volker Dobler, golang-nuts, Peter Bourgon, minux
But this does not cover the two additional cases.

In a truly decoupled design the receiver just eats what it gets and does not care who and how the the message got there.
I know that very often channels are used without any real decoupling but rather as a convenient way to shuffle data from one part to another and that is fine but I like to entertain the idea of more loosely coupled systems.

I still maintain that it makes sense but as has been pointed out, it should carry its weight in terms of usage (not just use cases).



--

Dmitry Vyukov

unread,
Jan 15, 2014, 5:30:30 AM1/15/14
to Henrik Johansson, golang-nuts
You can drop messages using "non-blocking select" today.

An arbitrary complex overload policies must/can not be implemented in runtime. In this case you need an intermidiate dispatcher that will reorder/drop messages:

inc := make(chan *Req, 1000)
outc := make(chan chan *Req, 1000)
go func() {
  // dispatcher
  reqs := MakeHeapOrWhatever()
  for {
    select {
    case r := <-inc:
      reqs.Add(r)
    case c := <-outc:
      c <- reqs.Get()
    case <-time.After(...):
      reqs.Tick()
    }
  }
}()

for i := 0; i < 10; i++ {
  go func() {
    // consumer
    c := make(chan *Req, 1)
    for {
      outc <- c
      r := <-c
      Process(r)
    }
  }()
}


If you are interested in other "simple" cases, then you must be very specific and make a very strong case for the extension.

Henrik Johansson

unread,
Jan 15, 2014, 6:17:04 AM1/15/14
to Dmitry Vyukov, golang-nuts
I think I am mostly (and not even immediately) interested in the dropping buffered scenario but I cant make a strong use case for it at this time.

Could a strong case be performance? Since it can be solved with a buffering dispatcher there is no real functional need to include it other than perhaps convenience.

It mostly seems to complete the way channels work today with the two additional cases for the buffered versions.

I will give it a thought, perhaps I can find a case that is not contrived as they tend to be when not coming from an immediate need.

Thanks!

Dmitry Vyukov

unread,
Jan 15, 2014, 9:39:21 AM1/15/14
to Henrik Johansson, golang-nuts
On Wed, Jan 15, 2014 at 3:17 PM, Henrik Johansson <dahan...@gmail.com> wrote:
> I think I am mostly (and not even immediately) interested in the dropping
> buffered scenario but I cant make a strong use case for it at this time.
>
> Could a strong case be performance?

I guess it depends on details.

And you can do "drop newest" today with good performance and w/o dispatcher.

Henrik Johansson

unread,
Jan 15, 2014, 9:57:35 AM1/15/14
to Dmitry Vyukov, golang-nuts
Is this what you mean?

if len(c) < cap(c) {
    c <- data

A bit racy of course but that does not matter I guess.



Dmitry Vyukov

unread,
Jan 15, 2014, 10:31:35 AM1/15/14
to Henrik Johansson, golang-nuts
On Wed, Jan 15, 2014 at 6:57 PM, Henrik Johansson <dahan...@gmail.com> wrote:
> Is this what you mean?
>
> if len(c) < cap(c) {
> c <- data
> }
>
> A bit racy of course but that does not matter I guess.

No, I mean:

select {
case c <- data:
default:
}

Actually, you can do "drop oldest" as well, this time indeed in a
slightly racy way:

loop: for {
select {
case c <- data:
break loop
default:
select {
case <-c:
default:
}
}
}

This racy-ness (when you recv from an already non-full chan) is fine
in producer-consumer scenarios with sufficiently buffered channel.

Henrik Johansson

unread,
Jan 15, 2014, 10:54:24 AM1/15/14
to Dmitry Vyukov, golang-nuts
Ah yes with the default block in place the select will not block.
Sorry Volker I think it was who pointed this out before and I just dismissed it. My bad!

I think I am starting to think it is simple enough as it is.
The select statement strikes again, it is a very nice thing.

Thanks!
Reply all
Reply to author
Forward
0 new messages