Channel Summary of Go Learning

Keywords: Go

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.

Posted by patriklko on Thu, 15 Aug 2019 06:48:45 -0700