Channel is a core type of Go, and you can think of it as a pipeline through which data can be sent or received to communicate through concurrent core units.
type
T represents an arbitrary type
- Two-way: chan T
- One-way only send:chan<-
- One-way acceptance only: <-chan
One-way channel s can be created not only by declaring make (chan < - interface {}), but also by transforming through Chan through stealth or display, as follows
func main() { channel := make(chan int, 10) convert(channel) } func convert(channel chan<- int) {}
In the convert function, you can use channel as a one-way input pipe
Since two-way chan can be received or sent, why does one-way chan exist? One of my understandings is privilege convergence. For example, in a crawler system, some processes a are only responsible for grabbing the content of the page and forwarding it to process b. Process a only needs chan sent one way.
Blocking
By default, sending or receiving chan is blocked until the other party is ready. This approach can be used to synchronize in gororutine without using the displayed lock or conditional variable.
For example, in the official example, the sentence x, y: = <-c, <-c will wait until the result of calculation is sent to channel. Take a look at the following examples
func bufferChannel() { channel := make(chan int) i := 0 go func(i int) { fmt.Printf("start goroutine %d\n", i)① channel <- i fmt.Printf("send %d to channel\n", i)② }(i) time.Sleep(2 * time.Second) fmt.Println("sleep 2 second") value := <-channel③ fmt.Println("got ", value) }
The output is as follows
start goroutine 0 sleep 2 second got 0 send 0 to channel
It can be seen that the go func does not continue to execute after the execution of the first one, but waits for the execution of the third one to be completed before the execution of the second one, which can also be explained that channel <-i blocked the continuation of the goroutine.
If I don't want to block here, I just put the data into the channel, and when the receiver is ready, how to deal with it for its own use in the channel, another concept of buffered channel is involved here.
buffered channel
Let's revise the program.
func bufferChannel() { channel := make(chan int, 1) // A parameter is added here. i := 0 go func(i int) { fmt.Printf("start goroutine %d\n", i)① channel <- i fmt.Printf("send %d to channel\n", i)② }(i) time.Sleep(2 * time.Second) fmt.Println("sleep 2 second") value := <-channel③ fmt.Println("got ", value) }
Output results
start goroutine 0 send 0 to channel sleep 2 second got 0
We find that go func executes after the execution of 1 and 2 without waiting for the execution of 3 to finish. This is the effect of buffered channel.
We just need to declare the bottom two parameters when making, that is, the buffer size of chan.
From the above program, we can see that we have been using the formation of <-chan to read the data in chan, but if there are multiple goroutine s writing data like a Chan at the same time, we can use only <-chan.
for { value <- chan }
Is there any more elegant way?
for ... range
Or the above program, we use for... range is being revamped
func bufferChannel() { channel := make(chan int, 1) i := 0 go func(i int) { fmt.Printf("start goroutine %d\n", i) channel <- i fmt.Printf("send %d to channel\n", i) }(i) time.Sleep(2 * time.Second) fmt.Println("sleep 2 second") for value := range channel { fmt.Println("got ", value) } }
This way we can traverse the data in channel, but when we run it, we will find that, hey, why can't this program stop? The iteration value generated by range channel is the value sent in Channel. It will iterate until the channel is closed. So after we goroutine sends the data, we try to close the channel. This time, we don't do time.Sleep(2 * time.Second)
func bufferChannel() { channel := make(chan int, 1) i := 0 go func(i int) { fmt.Printf("start goroutine %d\n", i) channel <- i fmt.Printf("send %d to channel\n", i) close(channel) }(i) for value := range channel { fmt.Println("got ", value) } }
In this way, the whole program can exit normally, so when using range, it should be noted that if the channel is not closed, the range will be blocked here all the time.
select
What we have been talking about above is how to do when there is only one channel. We should add two or more channels. What should we do? Here we introduce multiplexing select ion in go. Take the following program as an example.
func example() { tick := time.Tick(time.Second) after := time.After(3 * time.Second) for { select { case <-tick: fmt.Println("tick 1 second") case <-after: fmt.Println("after 3 second") return default: fmt.Println("come into default") time.Sleep(500 * time.Millisecond) } } }
time.Tick is a function of a timer provided by go's time package. It returns a channel and sends a data to the channel within a specified time interval. time.Tick(time.Second) sends a data to the channel every second.
time.After is a function of a timer provided by go's time package. It returns a channel and sends a data to the channel after a specified time interval. time.After(3 * time.Second) is to send a data to the channel after 3 seconds.
Output results
come into default come into default tick 1 second come into default come into default tick 1 second come into default come into default tick 1 second after 3 second
As you can see, select selects a non-blocking channel and executes the logic under the response case, thus avoiding subsequent logical blockages due to a channel blocking.
Let's go on with a little experiment and try putting the closed channel in select.
func example() { tick := time.Tick(time.Second) after := time.After(3 * time.Second) channel := make(chan int, 1) go func() { channel <- 1 close(channel) }() for { select { case <-tick: fmt.Println("tick 1 second") case <-after: fmt.Println("after 3 second") return case value := <- channel: fmt.Println("got", value) default: fmt.Println("come into default") time.Sleep(500 * time.Millisecond) } } }
Output results
. . . . got 0 got 0 got 0 got 0 got 0 after 3 second
It's the scene of a car accident. Fortunately, we set up 3s to withdraw voluntarily. When case is set up, is there any way to judge whether the channel is closed or not? Of course, it's OK. Look at the following procedure.
func example() { tick := time.Tick(time.Second) after := time.After(3 * time.Second) channel := make(chan int, 1) go func() { channel <- 1 close(channel) }() for { select { case <-tick: fmt.Println("tick 1 second") case <-after: fmt.Println("after 3 second") return case value, ok := <- channel: if ok { fmt.Println("got", value) } else { fmt.Println("channel is closed") time.Sleep(time.Second) } default: fmt.Println("come into default") time.Sleep(500 * time.Millisecond) } } }
Output results
come into default got 1 channel is closed tick 1 second channel is closed channel is closed after 3 second
In summary, we can see that in the form of value, ok: = < - channel, OK is used to judge channel.
If the channel is closed, ok is true, indicating that the channel is normal, otherwise, the channel is closed.