Q: can you reopen a channel? (e.g. when using close() as a barrier)

4,002 views
Skip to first unread message

Jason E. Aten

unread,
May 6, 2014, 8:33:59 PM5/6/14
to golan...@googlegroups.com
I'm reflecting on John Graham-Cumming's Channel Compendium talk at GopherCon: Specifically, where he suggests using channel close() as a barrier synchronization method.

// example from slide 10 of https://github.com/gophercon/2014-talks/blob/master/John_Graham-Cumming_A_Channel_Compendium.pdf

func worker(start chan bool) {
  <- start
  // ... do stuff
}

func main() {
  start := make(chan bool)
  for i := 0; i < 100; i++ {"
    go worker(start)"
  }
  close(start)
  // ... all workers running now (line 12)
}

First a quick observation: there's no *guarantee* that all (or even any) of the workers have running by the the time (line 12) is encountered right? It is non-deterministic is it not? I thought that, for the moment assuming without loss of generality that we are on a single thread, that execution could proceed straight on past the close(), so actually I *don't* know that all workers are running by line 12, right?

Then on to my question:  Q: If close() is a reasonable way to implement barrier synchronization, I wondered about how to reset such a barrier; i.e. can I re-open a channel?  Of course I could over-write a global channel variable with a reference to a new open channel, but I have no guarantee that the write to a global channel would be atomic, right? (i.e. if the global variable reference was multiple words, could it ever be read in a half-written state)?

If not, it doesn't seem like close() makes for a very good barrier after all.

Thanks.

Jason

Jesse McNelis

unread,
May 6, 2014, 8:51:01 PM5/6/14
to Jason E. Aten, golang-nuts
On Wed, May 7, 2014 at 10:33 AM, Jason E. Aten <j.e....@gmail.com> wrote:
> I'm reflecting on John Graham-Cumming's Channel Compendium talk at
> GopherCon: Specifically, where he suggests using channel close() as a
> barrier synchronization method.
>
> // example from slide 10 of
> https://github.com/gophercon/2014-talks/blob/master/John_Graham-Cumming_A_Channel_Compendium.pdf
>
> func worker(start chan bool) {
> <- start
> // ... do stuff
> }
>
> func main() {
> start := make(chan bool)
> for i := 0; i < 100; i++ {"
> go worker(start)"
> }
> close(start)
> // ... all workers running now (line 12)
> }
>
> First a quick observation: there's no *guarantee* that all (or even any) of
> the workers have running by the the time (line 12) is encountered right?

From the perspective of the goroutine running main() all the
goroutines are running.
It doesn't matter if they are running from their own perspectives.

If main() wants to communicate with those goroutines then it will
have to do some kind of synchronisation and wait for them to catch up.

> Then on to my question: Q: If close() is a reasonable way to implement
> barrier synchronization, I wondered about how to reset such a barrier; i.e.
> can I re-open a channel?

You can't re-open a channel, that would be lying.

> Of course I could over-write a global channel
> variable with a reference to a new open channel, but I have no guarantee
> that the write to a global channel would be atomic, right? (i.e. if the
> global variable reference was multiple words, could it ever be read in a
> half-written state)?

Yes, you can't 'reset' the barrier. So you'd have a different solution
if that was a requirement.

> If not, it doesn't seem like close() makes for a very good barrier after
> all.

Not if you need to reset it.

Rui Ueyama

unread,
May 6, 2014, 9:25:01 PM5/6/14
to Jason E. Aten, golang-nuts
You can create a new channel and assign it to a global variable, yes. However, other goroutines may continue observing the old value because the Go memory model does not guarantee such thing. http://golang.org/ref/mem

You need some synchronization mechanism, like a channel, mutex, WaitGroup, etc, to update and share the global variable. But if you have such object, you could use it as a barrier in the first place.

So, reassigning to the global variable is a strategy that won't work.

If not, it doesn't seem like close() makes for a very good barrier after all.

Thanks.

Jason

--
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/d/optout.

Jason E. Aten

unread,
May 6, 2014, 9:45:47 PM5/6/14
to Jesse McNelis, golang-nuts
Thanks, Jesse. I discovered sync.WaitGroup() and that does seem to be the idiomatic way to do barriers.

Dmitry Vyukov

unread,
May 7, 2014, 2:33:14 AM5/7/14
to Jason E. Aten, golang-nuts
On Wed, May 7, 2014 at 4:33 AM, Jason E. Aten <j.e....@gmail.com> wrote:
> I'm reflecting on John Graham-Cumming's Channel Compendium talk at
> GopherCon: Specifically, where he suggests using channel close() as a
> barrier synchronization method.
>
> // example from slide 10 of
> https://github.com/gophercon/2014-talks/blob/master/John_Graham-Cumming_A_Channel_Compendium.pdf
>
> func worker(start chan bool) {
> <- start
> // ... do stuff
> }
>
> func main() {
> start := make(chan bool)
> for i := 0; i < 100; i++ {"
> go worker(start)"
> }
> close(start)
> // ... all workers running now (line 12)
> }
>
> First a quick observation: there's no *guarantee* that all (or even any) of
> the workers have running by the the time (line 12) is encountered right? It
> is non-deterministic is it not? I thought that, for the moment assuming
> without loss of generality that we are on a single thread, that execution
> could proceed straight on past the close(), so actually I *don't* know that
> all workers are running by line 12, right?

Correct

> Then on to my question: Q: If close() is a reasonable way to implement
> barrier synchronization, I wondered about how to reset such a barrier; i.e.
> can I re-open a channel? Of course I could over-write a global channel
> variable with a reference to a new open channel, but I have no guarantee
> that the write to a global channel would be atomic, right? (i.e. if the
> global variable reference was multiple words, could it ever be read in a
> half-written state)?

If somebody is reading the global variable concurrently with the
write, then you are already in trouble, because it can read the
reference to the old channel and block on it forever. If nobody is
reading the variable, then there is no need to make it atomic.


> If not, it doesn't seem like close() makes for a very good barrier after
> all.
>
> Thanks.
>
> Jason
>

Jason E. Aten

unread,
May 8, 2014, 6:46:08 AM5/8/14
to golang-nuts
Just wanted to follow up that I started to understand why folks would recommended closing a channel over using a WaitGroup; a WaitGroup doesn't have a channel you can use in a select {}.  The conceptual addition that I needed to get "WaitGroup" like outcome was to add a 2nd channel in addition to done. I called it ackdone in this example, to communicate back to the supervisor that the done was acknowledged. For example:

http://play.golang.org/p/k0Qs0ZOSfF

Then I just have to think in terms of allocating new channels for done and ackdone on each fan-out, and it should work fine to use close(done) on a channel done to indicate shutdown.

As a side effect of implementing a circle/ring/chain, I'm able to measure send/receive overhead.  The circle.go code in the playground reference about shows me that the overhead to send and receive an integer between two goroutines (even using a buffer) on my linux/x86_64 box using go 1.2.1 is about 380 microseconds. This seems really expensive. Does this match anyone's expectations?

Thanks,
Jason



--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/e0jYSvJhPqA/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.

Dmitry Vyukov

unread,
May 8, 2014, 7:02:27 AM5/8/14
to Jason E. Aten, golang-nuts
$ go test -run=run -bench=BenchmarkChanProdCons100 runtime
BenchmarkChanProdCons100 50000000 60.1 ns/op
> 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
Reply all
Reply to author
Forward
0 new messages