Go channel problem

I got these code. not gonna work in play (play error: take too long to process )

package main

import "fmt"

var i = 0

func httpHandler() {
	errCh := make(chan error, 1)
	resultCh := make(chan int, 1)
	go func() {
		defer close(errCh)
		defer close(resultCh)
		errCh <- fmt.Errorf("shit")
	}()
	select {
	case <-errCh:
	case <-resultCh:
		i++
		println(i, " this shall not happen dude!")
	}
}

func main() {
	for i := 0; i < 1000000; i++ {
		httpHandler()
	}
}

run first time:

1  this shall not happen dude!
2  this shall not happen dude!
3  this shall not happen dude!
4  this shall not happen dude!
5  this shall not happen dude!
6  this shall not happen dude!
7  this shall not happen dude!

run second time:

1  this shall not happen dude!
2  this shall not happen dude!
3  this shall not happen dude!
4  this shall not happen dude!
5  this shall not happen dude!
6  this shall not happen dude!
7  this shall not happen dude!
8  this shall not happen dude!
9  this shall not happen dude!
10  this shall not happen dude!
11  this shall not happen dude!
12  this shall not happen dude!
13  this shall not happen dude!
14  this shall not happen dude!
15  this shall not happen dude!

this line should never been printed, but when i run, every time it get printed some times, the number of times appeared is also uncertain.

so, what’s going on? can someone explain these?

Because you close resultCh, receive on a closed channel is always a success.

There are multiple Srackoverflow answers explaining how to get around that. I’m on a mobile therefore I can’t search for you right now.

1 Like

Also worth noting is that when the main loop ends, then the whole process ends and stops all running goroutines. Because of this, only a few of the 1000000 possible lines are actually printed. To wait for all goroutines to end, ues a WaitGroup (from the sync package).

A quick and dirty way of preventing the main program from exiting too early is to place an empty select{} at the end of func main. An empty select block blocks forever, so the process only ends when stopping it from the outside; for example, by hitting Ctrl-C.

(As a side note, use fmt.Println() rather than println(). The latter one is. not guaranteed to stay in the language.)

@NobbZ @christophberger wow, thanks for your reply!

@NobbZ sure, the resultCh was closed, but i think the order in this case is: errCh was written first (cause errCh is a buffered channel), then resultCh was closed, so in select clause, errCh should been read first, not resultCh, actually only a few of 1000000 call of httpHandler print that line just prove my point. But why some call just went on the wrong order? and just a small number of them got wrong?

@christophberger check these out. as far as i can see, all goroutines was executed

package main

import "fmt"

var i, j = 0, 0

func httpHandler() {
	errCh := make(chan error, 1)
	resultCh := make(chan int, 1)
	go func() {
		defer close(errCh)
		defer close(resultCh)
		errCh <- fmt.Errorf("shit")
	}()
	select {
	case <-errCh:
		j++
	case <-resultCh:
		i++
		println(i, " this shall not happen dude!")
	}
}

func main() {
	for i := 0; i < 1000000; i++ {
		httpHandler()
	}
	fmt.Println(j)
}
1  this shall not happen dude!
2  this shall not happen dude!
3  this shall not happen dude!
4  this shall not happen dude!
5  this shall not happen dude!
6  this shall not happen dude!
7  this shall not happen dude!
8  this shall not happen dude!
9  this shall not happen dude!
10  this shall not happen dude!
999990

even with waitGroup, the result is just the same

package main

import "fmt"
import "sync"

var i, j = 0, 0
var wg sync.WaitGroup

func httpHandler() {
	errCh := make(chan error, 1)
	resultCh := make(chan int, 1)
	wg.Add(1)
	go func() {
		defer close(errCh)
		defer close(resultCh)
		errCh <- fmt.Errorf("shit")
		wg.Done()
	}()
	select {
	case <-errCh:
		j++
	case <-resultCh:
		i++
		println(i, " this shall not happen dude!")
	}
}

func main() {
	for i := 0; i < 1000000; i++ {
		httpHandler()
	}
	fmt.Println(j)

	wg.Wait()
}
1  this shall not happen dude!
2  this shall not happen dude!
3  this shall not happen dude!
4  this shall not happen dude!
5  this shall not happen dude!
6  this shall not happen dude!
7  this shall not happen dude!
999993

If in a select there are 2 channels available for read, then it is indeterministic which one gets choosen first.

But the order of two channel’s readability is deterministic, errCh was written first, then resultCh got closed, right? so 2 channels are not available for read at the same time, why select read the resultCh before errCh?

You’re not checking if either channel has been closed, as said before a select case on a channel will fire when its been closed.

select {
 case data, open := <-someChannel:
  if !open {
   //handle channel closure
  } else {
   //handle channel data
  }
}

I would optimise your code, create the channels and run the select case in its own goroutine outside of the httpHandler() function, and pass them along to the handler, Create another channel to signify the work is done so then the goroutine can exit gracefully.

That’s the crux with concurrently running stuff. Sending and closing may happen both before the other goroutine tried the first read.

@NobbZ thank you for you suggestion. my answer in English is same to you. I do not want to repeat that. so I
reference sentence in https://golang.org/ref/spec#Select_statements

If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection

Can you please answer in English? The answers should be available for everyone.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.