How to gracefully close the Go channel

Source: Internet
Author: User
Tags mutex rand

Source:
http://www.jianshu.com/p/d24dfbb33781

This document is translated from: How to Close Channels in Golang elegantly.
A few days ago, I wrote an article explaining the use of channel in Golang. In Reddit and HN, that article received a lot of approval, but I also received the following criticism about the Go Channel design and specification: There is no simple and common way to check if the channel has been closed without changing the channel state. Closing a channel that has already been closed will cause panic, so it is dangerous to close the channel if the closer (the shut-down) does not know if the channel has been closed. Sending a value to a channel that has already been closed causes panic, so if sender ( Sender) It is dangerous to send a value to the channel without knowing if the channel has been closed.

Those criticisms seem to make sense (not really). Yes, there is no built-in function to check if a channel has been closed. If you can be sure that no value will be sent to the channel, then you do need an easy way to check if the channel is closed:

Package main

import ' FMT '

type T int

func IsClosed (ch <-chan T) bool {
    Select {case
    <-ch:
        return true
    default:
    }

    return False
}

func main () {
    c: = Make (chan T)
    fmt. Println (IsClosed (c))//False
    Close (c)
    FMT. Println (IsClosed (c))//True
}

As mentioned above, there is no suitable way to check whether the channel has been closed. However, even if there is a simple closed (Chan T) bool function to check whether the channel has been closed, its usefulness is limited, as the built-in Len function checks the number of elements in the buffer channel. The reason is that the status of the channel that has been checked is likely to be modified after a similar method has been called, so the returned value cannot reflect the current state of the channel just checked.
Although it is possible to stop sending a value to the channel if the call closed (CH) returns true, it is not safe to close the channel or continue to send a value to the channel if the call closed (CH) returns False (panic). The Channel Closing Principle

When using Go channel, one of the principles is not to close the channel from the receiving end, nor to close the channel with multiple concurrent senders. In other words, if sender (sender) is only sender or the last active sender of the channel, then you should close the channel at sender's Goroutine, notifying receiver (s) (receiver) There is no value to read. Maintaining this principle will ensure that it never occurs. Send a value to an already closed channel or close a channel that has been closed.
(Below, we will call the above principles for channel closing principle to break the channel closing principle solution

If you shut down the channel from the receiving end (receiver side) for some reason or one of the multiple senders, you should use the column Golang Panic/recover Cases function to safely send values into the channel (assuming that the channel element type is T)

Func safesend (Ch Chan T, Value T) (closed bool) {
    defer func () {
        if recover () = nil {
            //The return result CA n be altered 
            //-defer function call
            closed = True
        }
    } ()

    ch <-value//Panic if CH is closed
  return false//<=> closed = false; Return
}

If channel CH is not closed, then the performance of this function will be close to CH <-value. When the channel is closed, the Safesend function is only called once per sender Goroutine, so the program does not have much performance penalty.
The same idea can be used to close the channel from multiple goroutine:

Func safeclose (Ch Chan T) (justclosed bool) {
    defer func () {
        if recover () = nil {
            justclosed = False
        }
    } ()

    //assume ch! = nil here.
    Close (CH)//Panic if CH is closed
    return True
}

Many people like to use sync. Once to close the channel:

Type MyChannel struct {
    C    chan T
    once sync. Once
}

func Newmychannel () *mychannel {
    return &mychannel{c:make (chan T)}
}

func (MC * MyChannel) Safeclose () {
    Mc.once.Do (func () {
        Close (MC. C)}
    )
}

Of course, we can also use sync. Mutex to avoid closing the channel multiple times:

Type MyChannel struct {
    C      chan T
    closed bool
    Mutex  sync. Mutex
}

func Newmychannel () *mychannel {
    return &mychannel{c:make (chan T)}
}

func (MC * MyChannel) Safeclose () {
    mc.mutex.Lock ()
    if!mc.closed {
        Close (MC. C)
        mc.closed = True
    }
    mc.mutex.Unlock ()
}

func (MC *mychannel) IsClosed () bool {
    Mc.mutex.Lock ()
    defer mc.mutex.Unlock ()
    return mc.closed
}

We should understand why go does not support the built-in safesend and safeclose functions because it is not recommended to close the channel from the receiving end or multiple concurrent senders. Golang even prohibits the closing of the channel that receives only (RECEIVE-ONLY). Keep Channel closing principle elegant solution

A disadvantage of the savesend function on the

is that it cannot be invoked as a send operation after the case keyword of the SELECT statement (Translator Note: Similar to case safesend (CH, T):). Another drawback is that many people, including myself, feel that the scheme above is not elegant by using the Panic/recover and sync package. For a variety of scenarios, the following is an introduction to the channel solution without using panic/recover and sync packs.
(in the following example total, sync.) Waitgroup is just used to make the example complete. Its use is not always useful in practice) m-receivers, a sender,sender by closing the data channel saying "No more"
This is the simplest scenario, Just let sender close data to close the channel when sender doesn't want to send it again:

Package main import ("Time" "Math/rand" "Sync" "Log") func main () {rand. Seed (time. Now (). Unixnano ()) log. SetFlags (0)//... const MAXRANDOMNUMBER = 100000 Const Numreceivers = wgreceivers: = Sync. waitgroup{} wgreceivers.add (numreceivers)//... Datach: = make (chan int, +)//the sender go Func () {for {if value: = Rand. INTN (Maxrandomnumber);
                Value = = 0 {//The only sender can close the channel safely.
        Close (Datach) return} else {Datach <-value} }} ()//receivers for I: = 0; i < numreceivers;
            i++ {go func () {defer wgreceivers.done ()//Receive values until Datach is closed and
            The value of buffer queue of Datach is empty. For value: = Range Datach {log.
       Println (Value)} } ()} wgreceivers.wait ()} 
A receiver,n sender,receiver by closing an extra signal channel saying "please stop sending"
This scenario is a bit more complicated than the previous one. We cannot let receiver close the data channel, because doing so would break the channel closing principle. But we can have receiver close an additional signal channel to notify sender to stop sending the value:
Package main import ("Time" "Math/rand" "Sync" "Log") func main () {rand. Seed (time. Now (). Unixnano ()) log. SetFlags (0)//... const MAXRANDOMNUMBER = 100000 Const Numsenders = wgreceivers: = Sync.
        waitgroup{} wgreceivers.add (1)//... Datach: = make (chan int, +) stopch: = Make (chan struct{})
        STOPCH is an additional signal channel.
        Its sender is the receiver of the channel Datach.

    Its reveivers is the senders of channel Datach. Senders for I: = 0; i < numsenders; i++ {go func () {for {value: = Rand.
                INTN (maxrandomnumber) Select {Case <-Stopch:return
        Case Datach <-Value:}}} ()}//The receiver Go func () { Defer Wgreceivers.done () for value: = Range Datach {if value = = Maxrandomnumber-1 {//The receiver of the Datach channel is//also the sender of the stopch Cahnnel.
                It's safe to close the stop channel here. Close (stopch) return} log. Println (Value)}} ()//... wgreceivers.wait ()}

As the note says, for an additional signal channel, its sender is receiver of the data channel. This additional signal channel was closed by its only sender, followed by the channel closing principle. M receiver,n sender, either of them by notifying a moderator (arbitrator) to close the extra signal channel "Let's End the game"
This is the most complicated scenario. We cannot allow arbitrary receivers and senders to close the data channel, nor can any one receivers notify all senders and receivers to exit the game by closing an additional signal channel. Doing so would break the channel closing principle. However, we can introduce a moderator to close an additional signal channel. A tip for this example is how to notify moderator to close the additional signal channel:

Package main import ("Time" "Math/rand" "Sync" "Log" "StrConv") func main () {rand. Seed (time. Now (). Unixnano ()) log. SetFlags (0)//... const MAXRANDOMNUMBER = 100000 CONST Numreceivers = Ten Const NUMSENDERS = WG Receivers: = Sync. waitgroup{} wgreceivers.add (numreceivers)//... Datach: = make (chan int, +) stopch: = Make (chan struct
        {})//STOPCH is an additional signal channel.
        Its sender is the moderator goroutine shown below.
    Its reveivers is all senders and receivers of Datach. Tostop: = Make (chan string, 1)//The channel Tostop was used to notify the moderator//to close the Addit
        Ional Signal Channel (STOPCH).
        Its senders is any senders and receivers of Datach.

    Its reveiver is the moderator goroutine shown below. var stoppedby string//Moderator go func () {stoppedby = <-tostop//Part of the trick used to nOtify the moderator//to close the additional signal channel. Close (STOPCH)} ()//senders for I: = 0; i < numsenders; i++ {go func (ID string) {for {value: = Rand. 
                    INTN (maxrandomnumber) if value = = 0 {//Here, a trick are used to notify the moderator
                    To close the additional signal channel.
                    Select {Case Tostop <-"sender#" + Id:default:} return}//The first select here are to try to exit the//GO
                Routine as early as possible.

                Select {Case <-stopch:return default:}
    Select {Case <-stopch:return case Datach <-value:            }}} (StrConv. Itoa (i))}//Receivers for I: = 0; i < numreceivers; i++ {go func (ID string) {defer Wgreceivers.done () for {//same as Send
                ERS, the first select here are to//try to exit the Goroutine as early as possible.

                Select {Case <-stopch:return default:} 
                    Select {Case <-stopch:return case Value: = <-datach: if value = = MaxRandomNumber-1 {//The same trick is used to notify the Moder
                        Ator//To close the additional signal channel.
                        Select {Case Tostop <-"receiver#" + Id:default:

} return}                    Log. Println (Value)}}} (StrConv. Itoa (i))}//... wgreceivers.wait () log. Println ("Stopped by", Stoppedby)}

In this case, the channel closing principle is still adhered to.
Note that the buffer size of the channel tostop is 1. This is to avoid the Mederator Goroutine is ready before the first notification has been sent, resulting in a loss. More scenes.
Many of the scenario variants are based on the above three types. For example, a variant based on the most complex scenario might require receivers to read all the remaining values in the buffer channel. This should be easy to handle, and all this article will not be mentioned.
Although the above three scenarios do not cover all of the go channel usage scenarios, they are the most basic, and most of the scenarios in practice can be categorized into those three types. Conclusion

There is no scene here that requires you to break the channel closing principle. If you encounter this scenario, consider your design and rewrite your code.
Programming with GO is like creating art.

Related Article

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.