ticker stop behaviour

1,238 views
Skip to first unread message

steview

unread,
Sep 8, 2011, 4:30:49 PM9/8/11
to golang-nuts
Hi,
I was wondering if it is reasonable to expect close to be
automatically called on the Ticker external channel when Stop() is
called on the ticker object.

It seems logical that this would be expected behaviour, as it does not
appear there is a restart once stop has been called.
Otherwise is there any issue in calling (but seems a bit clunky having
to do this):

ticks.Stop()
close(ticks.C)

I ask this as I am using a ticker as a input channel to a for.. range
{} loop

for tick:= range ticks.C{
......
}


and if the Stop also initiated the close it would enable the for loop
to exit cleanly without needing any other action.

Was there a reason this was excluded ?

Andrew Gerrand

unread,
Sep 8, 2011, 7:21:20 PM9/8/11
to golan...@googlegroups.com
This would require any user of Ticker and Close to check for closed-ness on every receive from t.C.

This isn't a problem if you're ranging over t.C - the loop will just end - but if you're receiving from t.C in a case of a `for { select {} }` loop you could end up spinning, receiving zero from t.C forever.

I'm not sure if we considered closing the channel when we designed Ticker, but changing it now is not without side effects so we'd need to consider the value of doing it. The semantics and syntax of close have changed since Ticker was introduced, so it's probably worth looking at it again now.

I'm sure others will supply their opinions. :-)

Andrew

Kyle Lemons

unread,
Sep 8, 2011, 7:41:37 PM9/8/11
to golan...@googlegroups.com
I'm sure others will supply their opinions. :-)

I think it's a worthwhile change; might it be something govet (or the initial gofix) could warn about?
</peanutgallery>

~K

Kyle Consalus

unread,
Sep 8, 2011, 8:02:10 PM9/8/11
to golan...@googlegroups.com
Why is close() allowed on receive-only chans? 

Dave Cheney

unread,
Sep 8, 2011, 8:43:37 PM9/8/11
to Kyle Consalus, golan...@googlegroups.com
That is a good point, if the consumer of a channel should not call
close(chan), should the compile forbit close to be called on recieve
only channels ?

(/cc all)

Andrew Gerrand

unread,
Sep 8, 2011, 9:33:00 PM9/8/11
to golan...@googlegroups.com
On Friday, September 9, 2011 10:02:10 AM UTC+10, kyle wrote:
Why is close() allowed on receive-only chans? 

Because it hasn't been specifically disallowed, that's all.

Andrew 

kortschak

unread,
Sep 9, 2011, 9:59:09 PM9/9/11
to golang-nuts
I hope that it doesn't become disallowed. I sometimes use (probably
considered an abusive pattern) a receiving end goroutine closing a
channel that a collection of goroutines send on as a simple way to
terminate the sending routines (they defer/recover 'send on closed
channel' panics).

steview

unread,
Sep 10, 2011, 3:58:04 AM9/10/11
to golang-nuts
Hi Andrew,
Sorry just to be clear you said that a range loop over this will just
end.
The following test does not print the "stopped function" unless I
manually close the channel outside the goroutine, which suggests it
does not.

func TestTicks(t *testing.T){
ticks :=time.NewTicker(1000000000)
out:=make(chan int)
go func (ticks *time.Ticker, output chan int)(){
fmt.Printf("started function \n")
for tick:= range ticks.C{
fmt.Printf("sending data %d \n",tick)
output <- 1
}
fmt.Printf("stopped function \n")
}(ticks, out)
time.Sleep(5*1e09)
ticks.Stop()
time.Sleep(5*1e09)
}

Have I misunderstood what you said? There seems to be no way to get
the loop to end unless close is called.
Which also means that if close on read only channels is not allowed
some other mechanism will be needed to signal finish, which will be a
real pain as the range code will always have have some form of
additional close channel. This seems to be putting quite a lot more
complexity into what would be simple code when dealing with things
like tickers (which are nice and elegant ways of supplying a timed
pump at the moment).

Steve

Kyle Lemons

unread,
Sep 10, 2011, 4:06:15 AM9/10/11
to kortschak, golang-nuts
The memory model does not guarantee that that will continue working as
more optimizations are made or as new compilers and interpreters show
up. There are many reasons it is not allowed, and the closed() method
was removed to help prevent such improper communication. FWIW, I
highly recommend finding better ways to manage your channels (many of
which are described on this mailing list), not the least reason being
that you won't have to capture spurious panics and risk dropping data.
----
Kyle Lemons
Google Platforms - Servers and Storage
Georgia Tech - BS CmpE '10

Sent from my iPhone, so pardon any spelling or punctuation issues!

Dan Kortschak

unread,
Sep 10, 2011, 4:21:53 AM9/10/11
to Kyle Lemons, golang-nuts
Thanks. Though it's more a way of managing goroutines than managing
channels. It gives a very convenient way of terminating multiple
goroutines - the alternative that I have used of sending on a term
channel requires that the terminating goroutines resend the term signal.

Dan

yy

unread,
Sep 10, 2011, 5:34:04 AM9/10/11
to Kyle Lemons, kortschak, golang-nuts
2011/9/10 Kyle Lemons <kev...@google.com>:

> There are many reasons it is not allowed, and the closed() method
> was removed to help prevent such improper communication.

But closing a channel in the receiving end is, unfortunately, allowed.

When closed was removed, I proposed to remove close too and merge it
with the send operation:
http://groups.google.com/group/golang-nuts/browse_thread/thread/d91865ae306a081b/
(which neither disallow it unless you have a receive only channel, but
makes it more awkward).

This idea was rejected, and it is not my intention to start that
discussion again, but I'm bringing it here because I find thinking
about it that way a useful rule of thumb of when I should not be
closing a channel and maybe others will benefit of this point of view
too.


--
- yiyus || JGL .

Message has been deleted

steview

unread,
Sep 10, 2011, 7:41:18 AM9/10/11
to golang-nuts
Sorry I think that does not seem reasonable in the Ticker case. The
example above was just a simple test to show that Andrew's statement
was not correct for the Ticker object -- "This isn't a problem if
you're ranging over t.C - the loop will just end - " - not an attempt
to show how to control goroutines in a general sense (although the
range on a channel seems to be shown as idiomatic style in lots of go
examples).

perhaps better expressed without the output channel:

func TestTicks(t *testing.T){
ticks :=time.NewTicker(1000000000)
go func (ticks *time.Ticker)(){
fmt.Printf("started function \n")
for tick:= range ticks.C{
fmt.Printf("do something %d \n",tick)
}
fmt.Printf("stopped function \n")
}(ticks)
time.Sleep(5*1e09)
ticks.Stop()
time.Sleep(5*1e09)
}

One of the strong points it seems to me of the range style for
channels (when there is one consumer of that channel) is that it
deals
cleanly with allowing processes to exit when the channel is stopped
without having an out of band extra communication path. Making
cascade
approaches and process cleanup very straight forward.
So the simple case is simple.
Indeed this is implied in the "Channels of channels" section of the
effective go guide.
The whole point of a dedicated Ticker in this case is that it is an
elegant and clean way to drive a process while it is active, but once
the ticker is Stopped there is no reason that I should have to make
extra checks in a goroutine as to some other channel or timeout
because otherwise the goroutine is now blocked on a channel that can
never be restarted. Indeed I would expect Stop on a ticker to
terminate the channel especially as it is of no further use. Its not
that I want to call close(), indeed I would rather not.
So if I have an ticker driving a process that I want to shutdown when
I stop the ticker, in all cases I have to have some other
notification
mechanisms... that seems very inelegant and makes even the simple
case
the same as the multiple receiver case, which is a bit rubbish.

On Sep 10, 9:06 am, Kyle Lemons <kev...@google.com> wrote:
> The memory model does not guarantee that that will continue working as
> more optimizations are made or as new compilers and interpreters show
> up.  There are many reasons it is not allowed, and the closed() method
> was removed to help prevent such improper communication.  FWIW, I
> highly recommend finding better ways to manage your channels (many of
> which are described on this mailing list), not the least reason being
> that you won't have to capture spurious panics and risk dropping data.
> ----
> Kyle Lemons
> Google Platforms - Servers and Storage
> Georgia Tech - BS CmpE '10
>
> Sent from my iPhone, so pardon any spelling or punctuation issues!
>

Kyle Lemons

unread,
Sep 10, 2011, 1:39:39 PM9/10/11
to Dan Kortschak, golang-nuts
On Sat, Sep 10, 2011 at 1:21 AM, Dan Kortschak <dan.ko...@adelaide.edu.au> wrote:
Thanks. Though it's more a way of managing goroutines than managing
channels. It gives a very convenient way of terminating multiple
goroutines - the alternative that I have used of sending on a term
channel requires that the terminating goroutines resend the term signal.

Possibly the easiest way to terminate arbitrary goroutines is to have a "stop" channel that you close when you want them all to complete.

wg := new(sync.WaitGroup)
stop := make(chan bool)
data := make(chan int)

for i := 0; i < 10; i++ {
  wg.Add(1)
  go func(id int) {
    defer wg.Done()
    for {
      select {
      case <-stop:
        return
      case data <- i:
      }
    }
  }(i)
}

for i := 0; i < 50; i++ {
  fmt.Println(<-data)
}
close(stop)
wg.Wait()

If you don't care about waiting for them to all complete, you don't need the WaitGroup, but that example needs it so that main won't fall off and so that you can see that they do actually all finish.
~K

Dan Kortschak

unread,
Sep 10, 2011, 6:13:49 PM9/10/11
to Kyle Lemons, golang-nuts
Yeah, I found that yesterday after your message - I previously didn't
know that receive from a closed channel doesn't block (I should read the
spec more).

One of the cases that is still complicated (relatively, not absolutely)
is the case of a generator. When you create a goroutine/channel form of
a generator closing the channel terminates the generator through a
panic. I guess to do this now I should have the generator-creating
function return a value channel *and* a stop channel rather than just a
value channel?

thanks
Dan

Kyle Lemons

unread,
Sep 10, 2011, 7:24:17 PM9/10/11
to Dan Kortschak, golang-nuts
Yep.  In one application I have, the generator returns a get and set channel.  Closing the set channel stops the generator, sending a value on the set channel causes the next value read from get to be one following the value in set.  The set behavior isn't necessary in a lot of applications, but if you ever intend to close the generator you still probably need the second channel.

~K

Andrew Gerrand

unread,
Sep 11, 2011, 8:35:31 PM9/11/11
to golan...@googlegroups.com


On Saturday, September 10, 2011 5:58:04 PM UTC+10, steview wrote:
Have I misunderstood what you said? There seems to be no way to get
the loop to end unless close is called.

Yes, you misunderstood. I meant "If t.C was closed, this isn't a problem if you're ranging over it - the loop will just end. But if you're receiving from t.C in a case of a `for { select {} }` loop you could end up spinning, receiving zero from t.C forever."


Reply all
Reply to author
Forward
0 new messages