synchronous vs asynchronous sends on channels

1,746 views
Skip to first unread message

David Leimbach

unread,
Jul 9, 2010, 11:01:14 AM7/9/10
to golang-nuts
It seems to me that if channels had asynchronous sending behavior by
default, that taking a program designed in this way with goroutines in
the same process and making it run on a network would have less
changes to be made than a program with synchronous sends.

The argument that Erlang folks use for their asynchronous sends is
that it more closely resembles what's really going on in a computer
network, and therefore is more natural to the problems that one can
run into when writing a distributed application on a network.

I feel the netchan work Rob's been doing is pretty important stuff, so
I would love to read some thoughts on people's feelings about the
impact of the synchronous vs asynchronous send choice.

Even the new Mozilla Rust language is going with asynchronous sends it
seems, possibly for similar reasons.

http://wiki.github.com/graydon/rust/language-faq

Kai Backman

unread,
Jul 12, 2010, 3:17:21 AM7/12/10
to David Leimbach, golang-nuts
On Fri, Jul 9, 2010 at 6:01 PM, David Leimbach <lei...@gmail.com> wrote:
> It seems to me that if channels had asynchronous sending behavior by
> default, that taking a program designed in this way with goroutines in
> the same process and making it run on a network would have less
> changes to be made than a program with synchronous sends.

If your asynchronous go channel receiver lags your sender your sender
will block. Most practical networks drop packets instead. Your code
will look very different depending on which situation you need to code
against.

Kai

Jessta

unread,
Jul 12, 2010, 5:48:46 AM7/12/10
to David Leimbach, golang-nuts
On Sat, Jul 10, 2010 at 1:01 AM, David Leimbach <lei...@gmail.com> wrote:
> It seems to me that if channels had asynchronous sending behavior by
> default, that taking a program designed in this way with goroutines in
> the same process and making it run on a network would have less
> changes to be made than a program with synchronous sends.

Go channels can be asynchronous, but most of the time that's not what you want.
When communicating between goroutines running on the same machine a
synchronous send/recv improves program flow. Synchronous channels have
a lot of advantages by making program flow predictable and easier to
think about.

Once you start distributing an application across a network you have
to start dealing with a whole new set of problems; node failures,
network issues, lost messages, timeouts etc. If you have to deal with
all that stuff even while running on a single machine then you lose
performance. You only really need these kind of things at the boundary
between processes.

You're not going to distribute a single goroutine to a different
machine they are too lightweight for that. You'll have logical
groupings of goroutines (a process) on a machine with an
interface(netchan,IPC,RPC) to other logical groups of goroutines.

> The argument that Erlang folks use for their asynchronous sends is
> that it more closely resembles what's really going on in a computer
> network, and therefore is more natural to the problems that one can
> run into when writing a distributed application on a network.

Go is much more about concurrency than distributed parallel computing.
It's about how a program is designed rather than how it's executed and
synchronous channels more closely resemble what's really going on in a
computer that is switching between coroutines.

> I feel the netchan work Rob's been doing is pretty important stuff, so
> I would love to read some thoughts on people's feelings about the
> impact of the synchronous vs asynchronous send choice.

Netchans are nice, they can make the boundary for IPC look a lot more
like the internals, but I'd hate to have all channels behave like
that.

- jessta

--
=====================
http://jessta.id.au

David Leimbach

unread,
Jul 12, 2010, 10:28:19 AM7/12/10
to golang-nuts


On Jul 12, 2:48 am, Jessta <jes...@jessta.id.au> wrote:
> On Sat, Jul 10, 2010 at 1:01 AM, David  Leimbach <leim...@gmail.com> wrote:
>
> > It seems to me that if channels had asynchronous sending behavior by
> > default, that taking a program designed in this way with goroutines in
> > the same process and making it run on a network would have less
> > changes to be made than a program with synchronous sends.
>
> Go channels can be asynchronous, but most of the time that's not what you want.
> When communicating between goroutines running on the same machine a
> synchronous send/recv improves program flow. Synchronous channels have
> a lot of advantages by making program flow predictable and easier to
> think about.

I suppose I could put a fairly large buffer on a go channel and get
what I am after.

In a networked or distributed application, synchronous channels appear
to be the wrong model as a synchronous channel attempts to make some
guarantees that are not always going to reflect the real behavior of
the environment. If I have to wait until every send is done to do the
next send for example, to multiple targets, I'm wasting time
potentially if one of the receivers is lagging.

>
> Once you start distributing an application across a network you have
> to start dealing with a whole new set of problems; node failures,
> network issues, lost messages, timeouts etc. If you have to deal with
> all that stuff even while running on a single machine then you lose
> performance. You only really need these kind of things at the boundary
> between processes.

If you want to work with an program that you plan to eventually run in
a distributed way with Go, you'd not be able to use purely synchronous
channels (I suppose unless they're buffered), to try to write that
program because as soon as you switch to the networked version, the
synchronous behavior you counted on is just gone now. Consider that
netchan basically makes channels appear asynchronous. I do not
believe there's a better choice, networks are asynchronous in every
other programming model I know of, all you know is that the writing of
a message has completed "locally". If you need to know that the
receiver received the message, you have a Two Generals' problem, but
an ack or response on a reliable enough network can get you by most of
the time.

So I totally agree with you, there's new problems to be solved when
dealing with a networked environment, but bringing channels closer to
that behavior from the beginning actually makes it easier for me to
develop code on a single machine before deploying it. I've developed
such code this way with Erlang, and I kind of appreciate the
asynchronous behavior having been there from the start when you have
to run in a distributed mode.

>
> You're not going to distribute a single goroutine to a different
> machine they are too lightweight for that. You'll have logical
> groupings of goroutines (a process) on a machine with an
> interface(netchan,IPC,RPC) to other logical groups of goroutines.

No, but I might want to implement a set of goroutines all in one
process, then attempt to scale them out to other processes locally or
on the network. Packages in fact make this fairly simple to
organize.

>
> > The argument that Erlang folks use for their asynchronous sends is
> > that it more closely resembles what's really going on in a computer
> > network, and therefore is more natural to the problems that one can
> > run into when writing a distributed application on a network.
>
> Go is much more about concurrency than distributed parallel computing.
> It's about how a program is designed rather than how it's executed and
> synchronous channels more closely resemble what's really going on in a
> computer that is switching between coroutines.

That's why I brought this topic up to begin with. Go is young, and
deciding what it's good at or not so good at domain wise might not be
a great idea just yet. If netchan were fleshed out a bit more and a
little more mature, one could do some pretty interesting things with
it, possibly including distributed or parallel computing, and not only
that, doing so using the same great messaging primitives it already
has. The part that had me concerned was how awkward it might seem to
have synchronous "normal" channels and seemingly asynchronous netchan
channels.

>
> > I feel the netchan work Rob's been doing is pretty important stuff, so
> > I would love to read some thoughts on people's feelings about the
> > impact of the synchronous vs asynchronous send choice.
>
> Netchans are nice, they can make the boundary for IPC look a lot more
> like the internals, but I'd hate to have all channels behave like
> that.
>

I'm in exactly the opposite camp, because I feel like I'd want to be
able to program with similar assumptions about local and networked
message passing. Again, I suppose I can simply buffer my channels
when I make them, so perhaps my entire point is moot?

> - jessta
>
> --
> =====================http://jessta.id.au

Jim Whitehead

unread,
Jul 13, 2010, 4:29:08 AM7/13/10
to golang-nuts
Indeed. It's important to consider that you can model asynchronous
message passing either by using buffered channels of by writing an
explicit goroutine that handles the asynchronous message passing for
you with minimal overhead. Implementing synchronous message passing in
an asynchronous system is a much more difficult task that is much more
intrusive to your program.

- Jim

David Leimbach

unread,
Jul 13, 2010, 10:27:30 AM7/13/10
to golang-nuts
I don't know that it is that much more intrusive. It just requires an
abstraction that ever receive is acked and send waits for an ack.

That vs spawning a goroutine for every send seems about the same to me
in terms of intrusiveness.

A buffered channel, however, seems to model sort of what goes on on
the network pretty well, except that the buffering of data received at
a remote node might be more likely to be buffered in the kernel of the
receiving process, not in the buffer of the sending process. In
either case it's best to post a receive before a send if possible
however, so one knows how to arrange their code for distribution
equally.

This rule to post your receives before your sends is even true in MPI
programming, so I think it works on many different systems and
levels. Perhaps the asynchronous/synchronous thing truly isn't that
important.

Dave

>
> - Jim

Andrew Gerrand

unread,
Jul 13, 2010, 8:27:18 PM7/13/10
to David Leimbach, golang-nuts
On 14 July 2010 00:27, David Leimbach <lei...@gmail.com> wrote:
>> Indeed. It's important to consider that you can model asynchronous
>> message passing either by using buffered channels of by writing an
>> explicit goroutine that handles the asynchronous message passing for
>> you with minimal overhead. Implementing synchronous message passing in
>> an asynchronous system is a much more difficult task that is much more
>> intrusive to your program.
>
> I don't know that it is that much more intrusive.  It just requires an
> abstraction that ever receive is acked and send waits for an ack.

That means you can't just perform a send and allow it to block until
the receiver is ready. You would have to create (or get a handle to)
the ack channel, as well as the sending channel, and perform a send
_and_ a receive. Much more bulky and unwieldy.

> That vs spawning a goroutine for every send seems about the same to me
> in terms of intrusiveness.

Jim's not suggesting spawning a goroutine for every send. He's
suggesting a goroutine that reads from one channel continuously
writing to a buffer, and another that reads from that buffer and
continuously sends the data on another channel. Only a few lines to
write, and allows you very explicit control over how the data is
buffered, as well as not bloating the sending side (it's still just a
send).

Andrew

David Leimbach

unread,
Jul 14, 2010, 9:12:09 AM7/14/10
to golang-nuts


On Jul 13, 5:27 pm, Andrew Gerrand <a...@golang.org> wrote:
> On 14 July 2010 00:27, David  Leimbach <leim...@gmail.com> wrote:
>
> >> Indeed. It's important to consider that you can model asynchronous
> >> message passing either by using buffered channels of by writing an
> >> explicit goroutine that handles the asynchronous message passing for
> >> you with minimal overhead. Implementing synchronous message passing in
> >> an asynchronous system is a much more difficult task that is much more
> >> intrusive to your program.
>
> > I don't know that it is that much more intrusive.  It just requires an
> > abstraction that ever receive is acked and send waits for an ack.
>
> That means you can't just perform a send and allow it to block until
> the receiver is ready. You would have to create (or get a handle to)
> the ack channel, as well as the sending channel, and perform a send
> _and_ a receive. Much more bulky and unwieldy.

Yes, I understand what it means, and my claim is that that is the
behavior you'd want for a distributed system. Since that's what I'm
ultimately interested in using Go to do, it seems I'd want some
primitives that more closely model the sorts of failure I can get in
such a system. I've already noted that a buffered synchronous channel
seems to get me pretty much where I want to be in terms of
abstractions however, assuming the buffer is large enough.

See:
http://armstrongonsoftware.blogspot.com/2008/05/road-we-didnt-go-down.html

People have noted that things like CORBA and RMI do not really capture
the essence of what it means to program in a distributed fashion

>
> > That vs spawning a goroutine for every send seems about the same to me
> > in terms of intrusiveness.
>
> Jim's not suggesting spawning a goroutine for every send. He's
> suggesting a goroutine that reads from one channel continuously
> writing to a buffer, and another that reads from that buffer and
> continuously sends the data on another channel. Only a few lines to
> write, and allows you very explicit control over how the data is
> buffered, as well as not bloating the sending side (it's still just a
> send).

Sure that would make sense, and in fact some systems I program with
already do that sort of thing. Erlang has a behavior called
gen_server which ultimately spawns a process that receives all
messages aimed at the service, and then sends them to functions like
"handle_call" or "handle_cast", which do pattern matching to run the
correct clause of those functions.

This works in Erlang due to the asynchronous send channels however,
and while I think I understand where you were going with your example,
I do not believe you've provided enough to make it work in practice.
How do you answer the following question given the labeling below?

Let me call the original sender who wants to send asynchronously
goroutine "A". "B" is the name of the always reads from "A" goroutine
and stores data into a buffer called "Buffer". "C" is the consumer of
this buffer that "B" makes.

How does B synchronize with C over the contents of "buffer"?

One approach might be to use a channel between B and C, however, since
those channels are synchronous, if C is busy, the entire program must
run in lock step with C's availability, as B can not get back to
processing A's messages until C responds. Now if the buffer in
question were a buffered channel from B to C, and you get asynchronous
messaging from B to C as a result, you're in good shape to completely
fill up that buffer before C can block A.

Now if you were going to buffer from B to C, you may as well have
buffered directly from A to C, so B is no longer needed at all.

Also, if A had spawned a goroutine to do the send to C, there would be
no need to wait, but I believe that would not be as efficient in the
long term as a buffered send.

The transitive properties of synchronized message passing are both
great for understanding how a system works, and at the same time, a
little bit of a nuisance if you're coming from asynchronous messaging
background.

I do still think buffered channels get me what I want as far as a
compromise between asynchronous by default and synchronous channels

Dave

Jessta

unread,
Jul 14, 2010, 10:36:22 AM7/14/10
to David Leimbach, golang-nuts
On Wed, Jul 14, 2010 at 11:12 PM, David Leimbach <lei...@gmail.com> wrote:
> How do you answer the following question given the labeling below?
>
> Let me call the original sender who wants to send asynchronously
> goroutine "A".  "B" is the name of the always reads from "A" goroutine
> and stores data into a buffer called "Buffer".  "C" is the consumer of
> this buffer that "B" makes.

What is an asynchronous channel?
well, it's a buffered channel with a buffer of infinite size.
Since a channel of infinite size is impractical, you instead have to
pick a finite size for the buffer. This buffer should be big enough
that in most average runs of the program it will never be full when a
process wants to send to it. But obviously in some circumstances the
buffer will be full and the sending process has to do something with
the message. The best way to handle this is to have the sending
process block until the buffer has a slot available.
All async communication has a buffer limit.

Given this Go's idiom of making async channels from goroutines(to
dynamically increase the size of the buffer as needed) and small
buffered channels seems to perfectly model what you want.

> The transitive properties of synchronized message passing are both
> great for understanding how a system works, and at the same time, a
> little bit of a nuisance if you're coming from asynchronous messaging
> background.

Something to remember about goroutines(when comparing to erlang
processes) is that they are cooperative and a lot of goroutines you
see in programs are designed to be in lockstep with each other, they
are often more about making the design of the program easier to
understand than to take advantage of multi-processors. It's a language
for concurrent programming, which doesn't always mean parallel
execution.
A great example of this is the prime sieve.
http://golang.org/doc/progs/sieve.go

David Leimbach

unread,
Jul 14, 2010, 11:17:38 AM7/14/10
to golang-nuts


On Jul 14, 7:36 am, Jessta <jes...@jessta.id.au> wrote:
> On Wed, Jul 14, 2010 at 11:12 PM, David  Leimbach <leim...@gmail.com> wrote:
>
> > How do you answer the following question given the labeling below?
>
> > Let me call the original sender who wants to send asynchronously
> > goroutine "A".  "B" is the name of the always reads from "A" goroutine
> > and stores data into a buffer called "Buffer".  "C" is the consumer of
> > this buffer that "B" makes.
>
> What is an asynchronous channel?
> well, it's a buffered channel with a buffer of infinite size.
> Since a channel of infinite size is impractical, you instead have to
> pick a finite size for the buffer. This buffer should be big enough
> that in most average runs of the program it will never be full when a
> process wants to send to it. But obviously in some circumstances the
> buffer will be full and the sending process has to do something with
> the message. The best way to handle this is to have the sending
> process block until the buffer has a slot available.
> All async communication has a buffer limit.

Yes. This is sometimes referred to as flow control in an asynchronous
system. The nice thing about Go is the flow control implementation is
hidden in the channel implementation, and you just pass it some
parameters like buffered or unbuffered.

>
> Given this Go's idiom of making async channels from goroutines(to
> dynamically increase the size of the buffer as needed) and small
> buffered channels seems to perfectly model what you want

After pondering about this for a while, I'd have to say that I agree,
and not only that I agree, but that I think I like that I get to be
pretty explicit about the size of these buffers. In a language like
Erlang, it's all up to the runtime. For some programs that's ok, but
for others you really need to know what's going on with your
resources.
.
>
> > The transitive properties of synchronized message passing are both
> > great for understanding how a system works, and at the same time, a
> > little bit of a nuisance if you're coming from asynchronous messaging
> > background.
>
> Something to remember about goroutines(when comparing to erlang
> processes) is that they are cooperative and a lot of goroutines you
> see in programs are designed to be in lockstep with each other, they
> are often more about making the design of the program easier to
> understand than to take advantage of multi-processors. It's a language
> for concurrent programming, which doesn't always mean parallel
> execution.

Conversely if you wanted to write an application that can run pieces
in parallel, concurrent techniques are a good help. Also I don't
think that there is anything in the Go language's specification that
says goroutines may not run in parallel in future runtime
implementations.

> A great example of this is the prime sieve.http://golang.org/doc/progs/sieve.go
>

roger peppe

unread,
Jul 16, 2010, 5:34:50 AM7/16/10
to David Leimbach, golang-nuts
in defense of synchronous channels, they have some
very useful properties that you don't get with asynchronous channels.

once i've sent on a synchronous channel, i know that
the receiver must be in a particular state.
this makes them much easier to reason about.

for instance, consider the following code fragment,
where we're trying to execute one of two possible
requests and retrieve the result:

select{
case svc1 <- req1:
<-req1.reply
case svc2 <- req2:
<-req2.reply
}

if svc1 and svc2 are synchronous channels, then
we know that when we've sent a request that
the serving goroutine is actively working on that request
and will send a reply eventually.

if they are asynchronous, one of the sends will always
succeed (non-deterministically) and we've no way of knowing
whether the serving goroutine is actually yet aware
of the request - maybe it's exited and never will be.

this kind of property is very useful when creating relatively
closely coupled concurrent programs, but doesn't scale
well - if i interpose a goroutine, the channel becomes
asynchronous and you can't build synchronous channels from asynchronous
channels. that's just fine - it's easy to build asynchronous
channels if you need them, with whatever behaviour you
like when the queue is full.

Reply all
Reply to author
Forward
0 new messages