Reputation: 5416
I am a learner of Go. In order to simulate concurrency in Go, I wrote the following example below:
package main
import (
"fmt"
"time"
)
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
for i := 0; i < 5; i++ {
time.Sleep(500 * time.Millisecond)
c1 <- "Every 500 ms"
}
close(c1)
}()
go func() {
for i := 0; i < 5; i++ {
time.Sleep(1000 * time.Millisecond)
c2 <- "Every 1 s"
}
close(c2)
}()
isDone := false
for !isDone {
select {
case msg1, ok := <-c1:
if !ok {
isDone = true
break
}
{
fmt.Println(msg1)
}
case msg2, ok := <-c2:
if !ok {
isDone = true
break
}
{
fmt.Println(msg2)
}
}
}
}
As you can see, inside the infinite for loop in the main function, I am manually getting out of the loop by using the variable isDone
. But this approach looks cumbersome to me. Isn't it possible to do range over channel along with the select
condition?
The output of the above code is (Go Playground Link):
Every 500 ms
Every 1 s
Every 500 ms
Every 500 ms
Every 1 s
Every 500 ms
Every 500 ms
Upvotes: 0
Views: 1203
Reputation: 4204
Same program but in fewer lines. I think it's even more readable and clear.
package main
import (
"time"
)
func stop(tk ...*time.Ticker) {
for _, t := range tk {
t.Stop()
}
}
func main() {
ta := time.NewTicker(500 * time.Millisecond)
tb := time.NewTicker(1 * time.Second)
cta, ctb := 0, 0
for {
select {
case <-ta.C:
println("Every 500 ms")
cta++
case <-tb.C:
println("Every 1 s")
ctb++
}
if cta == 5 || ctb == 5 {
stop(ta, tb)
break
}
}
}
Sample output:
Every 500 ms
Every 500 ms
Every 1 s
Every 500 ms
Every 500 ms
Every 1 s
Every 500 ms
Upvotes: 1
Reputation: 655
The problem here is that:
A "break" statement terminates execution of the innermost "for", "switch", or "select" statement within the same function.
So we are not able to exit the for
loop without additional check after exiting the select
statement. I have updated your code a bit to achieve that in simplified manner:
package main
import (
"fmt"
"time"
)
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
for i := 0; i < 5; i++ {
time.Sleep(500 * time.Millisecond)
c1 <- "Every 500 ms"
}
close(c1)
}()
go func() {
for i := 0; i < 5; i++ {
time.Sleep(1000 * time.Millisecond)
c2 <- "Every 1 s"
}
close(c2)
}()
ok := true
var msg1 string
var msg2 string
for {
select {
case msg1, ok = <-c1:
if !ok {
break
}
{
fmt.Println(msg1)
}
case msg2, ok = <-c2:
if !ok {
break
}
{
fmt.Println(msg2)
}
}
if !ok {
break
}
}
fmt.Println("For loop exited")
}
Go playground link for the above code is here.
I know that this is not very much different from what you have written. Probably slightly more simplified. Another way of achieving the same is using the return
statement instead of break
and defining the for - select
statement in a separate method (assuming that is ok). Find the example below:
package main
import (
"fmt"
"time"
)
func ForSelectMethod(c1 chan string, c2 chan string) {
for {
select {
case msg1, ok := <-c1:
if !ok {
return
}
{
fmt.Println(msg1)
}
case msg2, ok := <-c2:
if !ok {
return
}
{
fmt.Println(msg2)
}
}
}
}
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
for i := 0; i < 5; i++ {
time.Sleep(500 * time.Millisecond)
c1 <- "Every 500 ms"
}
close(c1)
}()
go func() {
for i := 0; i < 5; i++ {
time.Sleep(1000 * time.Millisecond)
c2 <- "Every 1 s"
}
close(c2)
}()
ForSelectMethod(c1, c2)
fmt.Println("For loop exited")
}
Go Playground link here.
Upvotes: 1
Reputation: 51632
A common method to use in this case is to use another channel to signal termination. Reading from that channel becomes one of the cases in the select. Normally, you would close that channel when both writing goroutines are done. There is no need to close the two channels.
In your program, processing ends when none of the channels close, and the contents of the other channel is discarded, effectively leaking the goroutine writing to that channel in some cases. If that goroutine has not finished writing, then it will block waiting to write to a channel that will never be read from.
Upvotes: 0