Sahand
Sahand

Reputation: 8360

Golang: select statement exits when it shouldn't

I'm trying to create a program that prints "Eat", "Work", "Sleep" every 3rd, 8th, and 24th second respectively. Here is my code:

package main

import (
"fmt"
"time"
)

func Remind(text string, delay time.Duration) <-chan string { //channel only for receiving strings
    ch := make(chan string) // buffered/unbuffered?
    go func() {
        for {
            msg := "The time is " + time.Now().Format("2006-01-02 15:04:05 ") + text
            ch <- msg
            time.Sleep(delay) // waits according to specification
        }
    }()
    return ch
}

func main() {
    ch1 := Remind("Eat", 1000*1000*1000*3) // every third second    
    ch2 := Remind("Work", 1000*1000*1000*8) // every eighth second
    ch3 := Remind("Sleep", 1000*1000*1000*24) // every 24th second
    select { // chooses one channel that is not empty. Should run forever (?)
        case rem1 := <-ch1:
            fmt.Println(rem1)
        case rem2 := <-ch2:
            fmt.Println(rem2)
        case rem3 := <-ch3:
            fmt.Println(rem3)
    }
}

The problem with it is that it stops running immediately after printing the time followed by "Eat". In other examples I have read, the select statement goes on forever. Why doesn't it now?

Upvotes: 1

Views: 1299

Answers (2)

icza
icza

Reputation: 417572

I don't know where you've read that the select goes on forever, but it doesn't.

Once a case is executed, the select statement is "done". If none of the communication operations specified in cases can proceed and there is no default branch, select will block, for as long as any of the com. ops can proceed. But once a case is executed, select does not repeat.

Read the relevant section from the spec: Select statements.

Put it in an endless for to make it repeat forever:

for {
    select { // chooses one channel that is not empty. Should run forever (?)
    case rem1 := <-ch1:
        fmt.Println(rem1)
    case rem2 := <-ch2:
        fmt.Println(rem2)
    case rem3 := <-ch3:
        fmt.Println(rem3)
    }
}

As a sidenote:

You can create time.Duration values much easier, using constants from the time package:

ch1 := Remind("Eat", 3*time.Second)    // every third second
ch2 := Remind("Work", 8*time.Second)   // every eighth second
ch3 := Remind("Sleep", 24*time.Second) // every 24th second

You may also want to check out the time.Ticker type which is for tasks similar to your Remind() function.

Upvotes: 4

Endre Simo
Endre Simo

Reputation: 11551

select in Go mostly resembles the switch control statement and is sometimes called communication switch. select listens for incoming data on channels, but there could also be cases where a value is sent on a channel. In one word select is used to get or send values on concurrently executing goroutines.

In your example because you are executing the current time in the main goroutine, it's always executed. But because the other goroutines are executed in the select statement these are not always get the chance to be executed, because once a case is executed the channel blocks.

What select does:

  • if all are blocked, it waits until one can proceed
  • if multiple can proceed, it chooses one at random.
  • when none of the channel operations can proceed and the default clause is present, then this is executed: the default is always runnable (that is: ready to execute).

Using a send operation in a select statement with a default case guarantees that the send will be non-blocking!

To run forever use it in a for loop:

package main

import (
"fmt"
"time"
)

func Remind(text string, delay time.Duration) <-chan string { //channel only for receiving strings
    ch := make(chan string) // buffered/unbuffered?
    go func() {
        for {
            msg := "The time is " + time.Now().Format("2006-01-02 15:04:05 ") + text
            ch <- msg
            time.Sleep(delay) // waits according to specification
        }
    }()
    return ch
}

func main() {
    ch1 := Remind("Eat", 1000*1000*1000*3) // every third second    
    ch2 := Remind("Work", 1000*1000*1000*8) // every eighth second
    ch3 := Remind("Sleep", 1000*1000*1000*24) // every 24th second
    for {
        select { // chooses one channel that is not empty. Should run forever (?)
        case rem1 := <-ch1:
            fmt.Println(rem1)
        case rem2 := <-ch2:
            fmt.Println(rem2)
        case rem3 := <-ch3:
            fmt.Println(rem3)
        }
    }
}

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

Upvotes: 2

Related Questions