• go基础之Goroutines和Channels


    1. goroutine

    在Go语言中,每一个并发的执行单元叫作一个goroutine。

    当一个程序启动时,其主函数即在一个单独的goroutine中运行,我们叫它main goroutine。新 的goroutine会用go语句来创建。在语法上,go语句是一个普通的函数或方法调用前加上关键 字go。go语句会使其语句中的函数在一个新创建的goroutine中运行。而go语句本身会迅速地 完成。

    f()
    go f()
    

    除了从主函 数退出或者直接终止程序之外,没有其它的编程方法能够让一个goroutine来打断另一个的执 行,但是之后可以看到一种方式来实现这个目的,通过goroutine之间的通信来让一个 goroutine请求其它的goroutine,并被请求的goroutine自行结束执行。

    func main(){
        go spinner(100 * time.Millisecond)
        const n = 45
        fibN := fib(n)
        fmt.Printf("
    Fibonacci(%d) = %d
    ", n, fibN)
    }
    
    func spinner(delay time.Duration) {
        for {
            for _, r := range `-|/` {
                fmt.Printf("
    %c", r)
                time.Sleep(delay)
            }
        }
    }
    
    func fib(x int) int {
        if x < 3 {
            return x
        }
        return fib(x-1) + fib(x-2)
    }
    

    2. channel

    如果说goroutine是Go语音程序的并发体的话,那么channels它们之间的通信机制。一个 channels是一个通信机制,它可以让一个goroutine通过它给另一个goroutine发送值信息。每 个channel都有一个特殊的类型,也就是channels可发送数据的类型。

    ch := make(chan int)
    

    和map类似,channel也一个对应make创建的底层数据结构的引用。当我们复制一个channel 或用于函数参数传递时,我们只是拷贝了一个channel引用,因此调用者何被调用者将引用同 一个channel对象。和其它的引用类型一样,channel的零值也是nil。

    两个相同类型的channel可以使用==运算符比较。如果两个channel引用的是相通的对象,那 么比较的结果为真。一个channel也可以和nil进行比较。

    一个channel有发送和接受两个主要操作,都是用->运算符。在发送语句中, <- 运算符分割channel和要发送的值。在接收语句中, <- 运 算符写在channel对象之前。一个不使用接收结果的接收操作也是合法的。

    Channel还支持close操作,用于关闭channel,随后对基于该channel的任何发送操作都将导 致panic异常。对一个已经被close过的channel之行接收操作依然可以接受到之前已经成功发 送的数据;如果channel中已经没有数据的话讲产生一个零值的数据。

    使用内置的close函数就可以关闭一个channel:

    close(ch)
    

    2.1 不带缓存的channels

    一个基于无缓存Channels的发送操作将导致发送者goroutine阻塞,直到另一个goroutine在相 同的Channels上执行接收操作,当发送的值通过Channels成功传输之后,两个goroutine可以继续执行后面的语句。反之,如果接收操作先发生,那么接收者goroutine也将阻塞,直到有另一个goroutine在相同的Channels上执行发送操作。

    基于无缓存Channels的发送和接收操作将导致两个goroutine做一次同步操作。因为这个原 因,无缓存Channels有时候也被称为同步Channels。当通过一个无缓存Channels发送数据 时,接收者收到数据发生在唤醒发送者goroutine之前(译注:happens before,这是Go语言并发内存模型的一个关键术语!)。

    在讨论并发编程时,当我们说x事件在y事件之前发生(happens before),我们并不是说x事 件在时间上比y时间更早;我们要表达的意思是要保证在此之前的事件都已经完成了,例如在 此之前的更新某些变量的操作已经完成,你可以放心依赖这些已完成的事件了。

    2.2 串联的Channels(管道)

    Channels也可以用于将多个goroutine链接在一起,一个Channels的输出作为下一个Channels 的输入。这种串联的Channels就是所谓的管道(pipeline)。

    func main() {
        naturals := make(chan int)
        squares := make(chan int)
        
        // 发送者
        go func() {
            for x := 0; ; x++ {
                naturals <- x
            }
        }()
        
        // 接收者
        go func() {
            for{
                x := <-naturals
                squares <- x*x
            }
        }()
        
        // 在主goroutine打印结果
        for {
            fmt.Println(<-squares)
        }
    }
    

    上面的例子只能发送不限的序列,如果我们要发送有限的序列可以通过关闭channels来实现。

    当一个channel被关闭后,再向该channel发送数据将导致panic异常。当一个被关闭的channel 中已经发送的数据都被成功接收后,后续的接收操作将不再阻塞,它们会立即返回一个零值。关闭上面例子中的naturals变量对应的channel并不能终止循环,它依然会收到一个永无 休止的零值序列,然后将它们发送给打印者goroutine。

    没有办法直接测试一个channel是否被关闭,但是接收操作有一个变体形式:它多接收一个结 果,多接收的第二个结果是一个布尔值ok,ture表示成功从channels接收到值,false表示 channels已经被关闭并且里面没有值可接收。

    // 接收者
    go func() {
        for {
            x, ok := -<naturals
            if !ok {
                break
            }
            squares <- x*x
        }
        close(squares)
    }
    

    我们可以使用range循环代替上面的for,它一次冲channel接受数据,当channel被关闭时并且没有值可接收时跳出循环。

    func main() {
        naturals := make(chan int)
        squares := make(chan int)
        
        go func() {
            for x:=0; x < 100; x++ {
                naturals <-x
            }
            close(naturals)
    	}()
    	
    	go func() {
    		for x := range naturals {
    			squares <- x*x
    		}
    		close(squares)
    	}()
    
    	for x:=range squares {
    		fmt.Println(x)
    	}
    }
    

    并不用关闭每一channel。当需要告诉接收者goroutine,所有的数据发送完毕才需要关闭channel。不管一个channel是否被关闭,当他没有被引用时将会被GO的垃圾自动回收器回收。

    重复关闭和关闭一个nil值的channel会导致panic异常。

    上面的代码我们根据函数式编程可以写成

    func main() {
        naturals := make(chan int)
        squares := make(chan int)
        
        go counter(naturals)
        go squarer(squares, naturals)
        printer(squares)
    }
    
    func counter(out chan int){
        for x:=0; x < 100; x++ {
            out <-x
        }
        close(out)
    }
    
    func squarer(out in chan int){
        for x := range in {
            out <- x*x
        }
        close(out)
    }
    
    func printer(in chan int) {
        for x:=range in{
            fmt.Println(x)
        }
    }
    

    但是,在squarer函数中,我们无法保证out用来发送数据,in用来接收数据。

    为了防止这种情况,GO语言类型系统提供了单方向的channel类型,分别用于只能发送或接收数据。

    2.3 单方向的channel

    类型 chan<- int 表示一个只发送int的channel,只能发送不能接收。相反,类型 <-chan int 表示一个只接收int的channel,只能接收不能发送。

    func main() {
        naturals := make(chan int)
        squares := make(chan int)
        
        go counter(naturals)
        go squarer(squares, naturals)
        printer(squares)
    }
    
    func counter(out chan<- int){
        for x:=0; x < 100; x++ {
            out <-x
        }
        close(out)
    }
    
    func squarer(out chan<- int, in <){
        for x := range in {
            out <- x*x
        }
        close(out)
    }
    
    func printer(in chan int) {
        for x:=range in{
            fmt.Println(x)
        }
    }
    

    调用counter(naturals)将导致将 chan int 类型的naturals隐式地转换为 chan<- int 类型只发 送型的channel。

    不支持反向转向。

    2.4 带缓存的channels

    带缓存的Channel内部持有一个元素队列。队列的最大容量是在调用make函数创建channel时 通过第二个参数指定的。

    ch = make(chan string, 3) //声明了一个容量为3的channel
    

    向缓存Channel的发送操作就是向内部缓存队列的尾部插入元素,接收操作则是从队列的头部 删除元素。如果内部缓存队列是满的,那么发送操作将阻塞直到因另一个goroutine执行接收 操作而释放了新的队列空间。相反,如果channel是空的,接收操作将阻塞直到有另一个 goroutine执行发送操作而向队列插入元素。

    可以通过cap函数获取channel内部缓存的容量。len函数返回 channel内部缓存队列中有效元素的个数。

    ch<-"A"
    ch<-"B"
    cap(ch) // "3"
    len(ch) // "2"
    
  • 相关阅读:
    win7颜色反转
    全框眼镜拆卸镜片方法分享
    自定义锁屏图片 win7
    保存chrome主题背景的图片
    广域网设计
    网络方案设计
    电脑不能上网win7 解决办法
    局域网设计
    以太网安全技术ACL原理+配置
    协议无关组播--稀疏模式 PIM-SM
  • 原文地址:https://www.cnblogs.com/you-you/p/14016017.html
Copyright © 2020-2023  润新知