
Go Things I Love: Channels and Goroutines - iamjfu
https://www.justindfuller.com/2020/01/go-things-i-love-channels-and-goroutines/
======
cle
The example under "Communicating by sharing memory" isn't correct, despite the
author claiming that "it works". It's a very common example in concurrency 101
(updating a value). The fact that the author claims that it's correct is
pretty concerning to me.

Adding a print(len(ints)) at the bottom of the function:

    
    
      $ go run test.go
       5
      $ go run test.go
       8  
    
    

More on-topic, channels have their own tradeoffs. I often reach for WaitGroups
and mutexes instead of channels, because things can get complicated fast when
you're routing data around with channels...more complicated than sharing
memory. I don't think it's good advice to broadly recommend one over the other
--understand their tradeoffs and use the right tool for the job at hand.

~~~
iamjfu
Hi, author of the post here. Which example doesn't work? I just pasted the
communicate by sharing memory example into the go playground:
[https://play.golang.org/p/bWtyGTC-EsC](https://play.golang.org/p/bWtyGTC-EsC)
and it gives the same length every time. Am I missing something or are you
referring to a different example?

> More on-topic, channels have their own tradeoffs. I often reach for
> WaitGroups and mutexes instead of channels, because things can get
> complicated fast when you're routing data around with channels

You're absolutely right. I certainly didn't intend to give a blanket
recommendation. It's more of a, "If you're sharing memory, might it become
clearer if you share memory by communicating?" I was worried that the
simplistic examples would not properly represent the cases I was thinking of.
I think that's a communication error on me.

~~~
modernerd
I think it's only consistent in play.golang.org because results are cached:
[https://blog.golang.org/playground](https://blog.golang.org/playground).

If I run it locally it's not consistent:

    
    
        go run lock/main.go
        [3 0 1 2 5 4 6 9 7 8] 10⏎
        
        go run lock/main.go
        [1 5 6 7 8 0] 6⏎
        
        go run lock/main.go
        [0 3 1 2 4 5 6 7 8] 9⏎
    

Here's a version that does work for me:
[https://play.golang.org/p/b6bRb9pgIGZ](https://play.golang.org/p/b6bRb9pgIGZ)

The issue is that appending to slices concurrently is not safe, so you have to
use a lock around the append or similar.

~~~
DougBTX
In a way this supports the argument for the "Do not communicate by sharing
memory; instead, share memory by communicating" mantra, as in a language with
no compile-time checks for incorrect use of shared memory, it is very easy to
get it wrong.

~~~
modernerd
Yes, it looks like the author has updated their post to say as much following
the comments here.

~~~
iamjfu
I have. I didn't have time to rewrite it (I'm working, and I didn't want to
take it down) so I added a few caveats. I am going to research this more and
follow up again. Thanks again to everyone for the great responses. I learned a
lot from the comments here.

------
rubyn00bie
I guess I wish this was a bit more in-depth as to what "go" can do with
channels or goroutines, but maybe I've just been using languages where all
this is already possible. The article is a nice cursory glance, I just want to
learn more :)

I mean the first example it looks like is using a Mutex, and then (b)locking
on it, and the second just looks like having a queue of messages (mailbox)
that it rips through (like the actor pattern).

Some questions I've got after reading this...

How does the go-runtime (?) schedule these calls? Does it manage an internal
thread pool? Is the scheduler asynchronous, parallel, or both? How do you
manage contention or back pressure if I begin to flood messages to to one
channel (or many)? How many channels can I have open and what are the limits?
Can I still lock the system stupidly using channels, if so, how (or how not)?

Edit: Truly, I'm curious because as I researched asynchronous programming and
efforts to better future proof my career (years ago) as we began really
increasing core counts-- Go never stood out. It's a fairly practical language,
yes, but if I want a better paradigm for asynchronous programming the future
it really isn't there (IMHO). BEAM stood out as something unique, the JVM
stood out as something even more practical, and Rust stood out as something
performant (with the serious caveat of not being C or C++), while Go has
always seemed like an odd one to me... People talk about channels and
goroutines like their special but they seem pretty damn run of the mill to
me... WAT AM I MISSING?

~~~
SamReidHughes
Go channels have a fixed capacity, by default zero, and their main special
feature (compared to just having some concurrent_queue<T> type) is the select
statement, which helps devs do things the right way. Nothing stops you from
stupidly locking the system, e.g. by having a thread block itself by writing
to the channel it’s responsible for consuming.

~~~
jayd16
Is the select semantically different than something like this?

    
    
        bool TryWrite(T)
    

How does go handle cancellation with the select syntax? I guess you have a
cancel channel and select across both the cancel and the read channels? Is Go
smart enough to sleep and not busy wait in that case?

~~~
TheDong
> Is the select semantically different than something like this?

Yes. select can do a lot of things. It's overloaded to be something like 5
different things. Here, let me show you:

    
    
        chanOne := make(chan int)
        chanTwo := make(chan int)
        ctx := context.Background()
    
        // Receive whichever is ready first. Blocking
        select {
        case v := <-chanOne:
        case v := <-chanTwo:
        }
    
        // Non-blocking read
        select {
        case v := <-chanOne:
        default:
        }
    
        // Timeout and cancellation
        select {
        case v := <-chanOne:
        case <-time.After(1 * time.Second):
        // timeout
        case <-ctx.Done():
        // cancel
        }
    
        // Write or read, whichever channel is ready first
        select {
        case chanOne <- 1:
        case v := <-chanTwo:
        }
    
    

Another form of cancellation is closing channels, which causes reads to return
the zero value immediately, and causes any writes to that closed channel to
immediately panic (effectively abort the program).

~~~
jayd16
Neat. Most of the permutations are easily handled in other languages as well
except for the full blocking (sleeping) case. Without a channel the scheduler
can reason about, most languages would have to busy wait or possibly pull from
multiple channels.

~~~
SamReidHughes
I think the biggest advantage is that it's a lot harder to screw up using a
select statement than most equivalent API's you'd create in other languages
that allow waiting on one of N channels/signals/channel/event operations, and
branching based off that.

Simply because you're either constructing a bunch of callback objects or you
get some number N saying the Nth parameter had an event, and your code has to
match N to the specific channel correctly.

------
mathw
I do like that select statement which hits the first case that has its channel
ready with a message. That's very nice. And having channels in your standard
library is brilliant and everyone should do it.

A shame about the shared memory thing though. I firmly believe that designing
a language where memory is shared by default is a Bad Idea. You should
probably provide a way to allow it when you really need it (for performance,
usually, in very very very carefully-designed code), but having memory sharing
by default is a source of soooooo many bugs.

I know, because I've caused most of them.

------
meddlepal
I've found channels create more complexity than their usually worth and it's
often simpler, more readable, and more maintainable to just use a sync.Mutex
or sync.RWMutex.

~~~
shakezula
I'd be hard-pressed to disagree. The entire time reading this blog post, I was
confused why a plain function couldn't have been used instead.

------
pojntfx
Channels are really nice; I love writing "workers" with them and sending
errors and "status messages" with a simple `status <\- "starting
supernode"`/`errors <\- err`; doing this with i.e. Node's async/await is just
so much more complex.

------
apta
The only thing golang has going for it is "goroutines". Now that all other
popular languages are getting some variant of async (e.g. C#) or green thread
implementations (e.g. Java), it will be tough to advocate for golang for new
projects given its severe shortcomings.

~~~
Thaxll
Other language do it completely differently, there is no such thing as async
in Go since the paradigm is blocking from a programmer perspective.

I don't think understand how it works in Go.

~~~
crimsonalucard
What are you talking about? A go routine can run async to another go routine.
It's async.

It's just a different perspective of concurrency than node.

I literally quote from go by example:
[https://gobyexample.com/goroutines](https://gobyexample.com/goroutines)

"Our two function calls are running asynchronously in separate goroutines
now."

~~~
loopz
What they really mean by async is sync (await)! ;-)

In relation to Golang: [https://yizhang82.dev/go-and-async-
await](https://yizhang82.dev/go-and-async-await)

~~~
crimsonalucard
I'm aware of what they mean and how they are confused.

You should know that the author of that article failed to mention that node is
free of race conditions caused by context switching, this is a huge deal and
in many cases worth the trouble of async await syntax.

------
_pmf_
Go channels are nicest concurrency mechanism I know. They bring nice
ergonomics to the conceptual simplicity of a select()/epoll() loop.

------
coder006
Apart from the main topic, really liked the layout and theming of your blog.
Curious to know of it's hosted somewhere or self built.

~~~
iamjfu
Thanks! It's a customized hugo theme [https://github.com/justindfuller/hugo-
theme](https://github.com/justindfuller/hugo-theme) that I forked from
[https://github.com/alanorth/hugo-theme-
bootstrap4-blog](https://github.com/alanorth/hugo-theme-bootstrap4-blog)

------
osrec
For anyone interested in using channels and coroutines in PHP, swoole
([https://github.com/swoole/swoole-src](https://github.com/swoole/swoole-src))
provides a reasonable implementation!

------
dickeytk
off tangent, but I really like that syntax of defining types especially for
channels:

    
    
        type Foo(chan<- int)
    

instead of what I usually see

    
    
        type Foo chan<- int
    

unfortunately it doesn't appear compatible with gofmt (entirely), which
changes it to:

    
    
        type Foo (chan<- int)
    

I still think it's a good pattern for channels though. It makes it a lot
clearer what the type is especially if you have a slice of channels:

    
    
        type Foo []chan<- int
    

vs

    
    
        type Foo [](chan<- int)

