Wonderful channel.

Source: Internet
Author: User
This is a creation in Article, where the information may have evolved or changed.

The channel is often used in the process of writing Golang programs. The use of channel is indeed very special, but also very practical.

The original is here: Http://dave.cheney.net/2013/04/30/curious-channels

———— Translation Divider Line ————

Wonderful channel.

In the Go programming language, the channel is a shining feature. It provides a powerful way to send a stream of data from one goroutine to another goroutine without using a lock or critical section.

Today I would like to discuss two important features of channel, which are not only very useful in controlling data flow, but also effective in process control.

A channel that has been closed will never block.

First feature, I would like to talk about the channel that has been closed. Once a channel is closed, it is no longer possible to send data to the channel, but you can still try to get the value from the channel.

package main import "fmt" func main() {        ch := make(chan bool, 2)        ch <- true        ch <- true        close(ch)         for i := 0; i < cap(ch) +1 ; i++ {                v, ok := <- ch                fmt.Println(v, ok)        }}

In this example, we create a channel with a buffer of two values, fill the buffer, and close it off.

True Truetrue TrueFalse False

Executing this program will first show us the two values sent to the channel, and then the third attempt on the channel will return flase and false. The first false is the 0 value of the channel type, the type of channel is Chan bool, and that is false. The second indicates the enabled state of the channel, which is currently false, indicating that the channel is closed. The channel will always return these values. As an attempt, you can modify this example to take a 100-time value from the channel.

The ability to detect if the channel is closed is a useful feature that can be used to range the channel and exit the loop when the channel is emptied.

package main import "fmt" func main() {        ch := make(chan bool, 2)        ch <- true        ch <- true        close(ch)         for v := range ch {                fmt.Println(v) // 被调用两次        }}

But its true value is embodied when combined with select. Let's start with this example.

package main import (        "fmt"        "sync"        "time") func main() {        finish := make(chan bool)        var done sync.WaitGroup        done.Add(1)        go func() {                select {                case <-time.After(1 * time.Hour):                case <-finish:                }                done.Done()        }()        t0 := time.Now()        finish <- true // 发送关闭信号        done.Wait()    // 等待 goroutine 结束        fmt.Printf("Waited %v for goroutine to stop\n", time.Since(t0))}

On my system, this program runs with a very short wait delay, so it's clear that Goroutine won't wait a full one hours and then call done. Done ()

Waited 129.607us for Goroutine to stop

But there are some problems in this program. The first is that the finish channel is not buffered, so if the receiver forgets to add finish to its SELECT statement, sending the data to it may result in blocking. The select block to be sent can be encapsulated to ensure that it is not blocked or that the finish channel is buffered to resolve the problem. However, if many goroutine are listening on the finish channel, you need to track the situation and remember to send the correct amount of data to the finish channel. It can be tricky to control the creation of Goroutine, and they may also be created by another part of the program, such as in response to a network request.

A good solution to this problem is to use the channel that has been closed to return this mechanism in real time. Using this feature to rewrite the program, now contains 100 goroutine, without the need to track the number of goroutine generated, or adjust the size of the finish channel.

package main import (        "fmt"        "sync"        "time") func main() {        const n = 100        finish := make(chan bool)        var done sync.WaitGroup        for i := 0; i < n; i++ {                done.Add(1)                go func() {                        select {                        case <-time.After(1 * time.Hour):                        case <-finish:                        }                        done.Done()                }()        }        t0 := time.Now()        close(finish)    // 关闭 finish 使其立即返回        done.Wait()      // 等待所有的 goroutine 结束        fmt.Printf("Waited %v for %d goroutines to stop\n", time.Since(t0), n)}

On my system, it returns

Waited 231.385us for Goroutines to stop

So what's going on here? When the finish channel is closed, it returns immediately. Then all waits to receive time. The SELECT statement for the after channel or finish Goroutine is completed immediately, and Goroutine is called done. Done () to reduce the Waitgroup counter after exiting. This powerful mechanism makes it possible to send signals to them without having to know any details of the unknown number of goroutine, without worrying about deadlocks.

Before moving on to the next topic, take a look at a few simple examples that many Go programmers love. In the example above, the data is never sent to the finish channel, and any data received by the receiving party is discarded. So it's normal to write a program like this:

package main import (        "fmt"        "sync"        "time") func main() {        finish := make(chan struct{})        var done sync.WaitGroup        done.Add(1)        go func() {                select {                case <-time.After(1 * time.Hour):                case <-finish:                }                done.Done()        }()        t0 := time.Now()        close(finish)        done.Wait()        fmt.Printf("Waited %v for goroutine to stop\n", time.Since(t0))}

When close relies on the message mechanism to close the channel, and no data is sent and received, the definition of finish as type Chan struct{} means that the channel has no data and is only interested in its closed properties.

A nil channel is always blocked.

The second feature I want to talk about is exactly the opposite of the characteristic of a channel that has been closed. A nil channel; When the value of the channel has not been initialized or assigned to nil, it is always blocked. For example

package main func main() {        var ch chan bool        ch <- true // 永远阻塞}

When CH is nil, it will be deadlocked and will never send data. For the reception is the same

package main func main() {        var ch chan bool        <- ch // 永远阻塞}

This may not seem very important, but it is really a useful feature when using the closed channel mechanism to wait for multiple channel closures. For example

// WaitMany 等待 a 和 b 关闭。func WaitMany(a, b chan bool) {        var aclosed, bclosed bool        for !aclosed || !bclosed {                select {                case <-a:                        aclosed = true                case <-b:                        bclosed = true                }        }}

Waitmany () is a good way to wait for channel A and b to close, but there is a problem. Suppose channel A is closed first, and then it returns immediately. But since bclosed is still false, the program goes into a dead loop, and Channel B is never judged to be closed.

A safe way to solve this problem is to take advantage of the blocking characteristics of the nil channel and rewrite the program as follows

In the overridden Waitmany (), once a value is received, the reference to a or B is set to nil. When the nil channel is part of a SELECT statement, it is actually ignored, so setting a to nil removes it from select, leaving only b waiting for it to be closed, and then exiting the loop.

Run on my system to get

package main import (        "fmt"        "time") func WaitMany(a, b chan bool) {        for a != nil || b != nil {                select {                case <-a:                        a = nil                case <-b:                        b = nil                }        }} func main() {        a, b := make(chan bool), make(chan bool)        t0 := time.Now()        go func() {                close(a)                close(b)        }()        WaitMany(a, b)        fmt.Printf("waited %v for WaitMany\n", time.Since(t0))}

In summary, it is close and nil Channlechannel These features are very simple, making them a powerful component for creating highly concurrent programs.

(Transferred from: http://mikespook.com/2013/05/%E7%BF%BB%E8%AF%91%E7%BB%9D%E5%A6%99%E7%9A%84-channel/)

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.