zoukankan      html  css  js  c++  java
  • Go Channel介绍

    一.简单说明

    Channel是Go中的一个核心类型,可以看做是一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯。

    1.1 操作符

    这里Channel的操作符是箭头<-,箭头的指向就是数据的流向。

    ch <- v    # 发送值v到Channel ch中
    v := <-ch  # 从Channel ch中接收数据,并将数据赋值给v
    

    这里channel和map以及slice一样,必须先创建再使用。

    ch := make(chan int)
    

    1.2 Channel类型

    Channel类型的定义格式如下:

    ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
    

    它包括三种类型的定义,可选的<-代表channel的方向,如果没有指定方向,那么Channel就是双向的,既可以接收数据,也可以发送数据。

    chan T           # 可以接收和发送类型为T的数据
    chan<- float64   # 只可以用来发送float64类型的数据
    <-chan int       # 只可以用来接收int类型的数据
    

    使用make初始化Channel,可以设置容量:

    make(chan int, 1000)
    

    容量capacity代表Channel容纳的最多的元素的数量,代表Channel的缓存的大小。通过缓存的使用,可以尽量避免阻塞,提供应用的性能。
    如果没有设置容量,或者容量设置为0,说明Channel没有缓存,只有sender和receiver都准备好了后它们的通讯才会发生。如果设置了缓存,就有可能不会发生阻塞,只有buffer满了后send才会阻塞,而只有缓存空了后receiver才会则色,一个nil channel不会通信。

    可以通过内建的close()方法关闭Channel。
    Channel是一个先入先出(FIFO)的队列,接收的数据和发送的数据顺序是一致的。

    1.3 send

    send语句用来往Channel中发送数据,如:ch <- 3 。它的定义如下:

    SendStmt = Channel "<-" Expression .
    Channel  = Expression .
    

    在通信开始前,Channel和expression必须先求值处理,如下下面的(1+2)先计算出来3然后再发送给Channel:

    c := make(chan int)
    defer close(c)
    go func() { c <- 1 + 2 }()
    i := <-c
    fmt.Println(i)
    

    send被执行前通讯一直被阻塞着,无缓存的channel只有在receiver准备好后send才被执行。如果有缓存,且缓存未满,则send会被执行。
    注意:往一个已经被close的channel中继续发送数据会导致run-time panic,往nil channel中发送数据会一直被阻塞着。

    1.4 receive

    <-ch 用来从channel ch中接收数据,这个表达时候会被一直block,直到有数据可以接收。
    注意:从一个nil channel中接收数据会一直被block。从一个被close的channel中接收数据不会被阻塞,而是立即返回,接收完已发送的数据后会返回元数类型的零值。
    这里我们可以使用一个额外的返回参数来检查channel是否关闭:

    x, ok := <-ch
    x, ok = <-ch
    var x, ok = <-ch
    

    如果ok为false,表示接收的x是产生的零值,这个Channel被关闭了或者为空。

    1.5 blocking

    缺省情况下,发送和接收会一直阻塞着,直到另一方准备好。这种方式可以用来在gororutine中进行同步,而不必使用显示的锁或者条件变量。
    如官方的例子中x, y := <-c, <-c这句会一直等待计算结果发送到channel中。

    import "fmt"
    func sum(s []int, c chan int) {
    	sum := 0
    	for _, v := range s {
    		sum += v
    	}
    	c <- sum // send sum to c
    }
    func main() {
    	s := []int{7, 2, 8, -9, 4, 0}
    	c := make(chan int)
    	go sum(s[:len(s)/2], c)
    	go sum(s[len(s)/2:], c)
    	x, y := <-c, <-c // receive from c
    	fmt.Println(x, y, x+y)
    }
    

    1.6 Range

    for …… range语句可以处理Channel。

    func main() {
    	go func() {
    		time.Sleep(1 * time.Hour)
    	}()
    	c := make(chan int)
    	go func() {
    		for i := 0; i < 10; i = i + 1 {
    			c <- i
    		}
    		close(c)
    	}()
    	for i := range c {
    		fmt.Println(i)
    	}
    	fmt.Println("Finished")
    }
    

    range c产生的迭代值为Channel中发送的值,它会一直迭代直到channel被关闭。上面的例子中如果把close(c)注释掉,程序会一直阻塞在for …… range那一行。

    1.7 select

    select语句选择一组可能的send操作和receive操作去处理。它类似switch,但是只是用来处理通讯操作。
    它的case可以是send语句,也可以是receive语句,亦或者default。receive语句可以将值赋值给一个或者两个变量。它必须是一个receive操作。最多允许有一个default case,它可以放在case列表的任何位置,尽管我们大部分会将它放在最后。

    import "fmt"
    func fibonacci(c, quit chan int) {
    	x, y := 0, 1
    	for {
    		select {
    		case c <- x:
    			x, y = y, x+y
    		case <-quit:
    			fmt.Println("quit")
    			return
    		}
    	}
    }
    func main() {
    	c := make(chan int)
    	quit := make(chan int)
    	go func() {
    		for i := 0; i < 10; i++ {
    			fmt.Println(<-c)
    		}
    		quit <- 0
    	}()
    	fibonacci(c, quit)
    }
    

    如果有同时多个case去处理,比如同时有多个channel可以接收数据,那么Go会伪随机的选择一个case处理(pseudo-random)。如果没有case需要处理,则会选择default去处理,如果default case存在的情况下。如果没有default case,则select语句会阻塞,直到某个case需要处理。
    注意:需要注意的是,nil channel上的操作会一直被阻塞,如果没有default case,只有nil channel的select会一直被阻塞。
    elect语句和switch语句一样,它不是循环,它只会选择一个case来处理,如果想一直处理channel,你可以在外面加一个无限的for循环:

    or {
    	select {
    	case c <- x:
    		x, y = y, x+y
    	case <-quit:
    		fmt.Println("quit")
    		return
    	}
    }
    

    1.8 timeout

    select有很重要的一个应用就是超时处理。 因为上面我们提到,如果没有case需要处理,select语句就会一直阻塞着。这时候我们可能就需要一个超时操作,用来处理超时的情况。
    下面这个例子我们会在2秒后往channel c1中发送一个数据,但是select设置为1秒超时,因此我们会打印出timeout 1,而不是result 1。

    import "time"
    import "fmt"
    func main() {
        c1 := make(chan string, 1)
        go func() {
            time.Sleep(time.Second * 2)
            c1 <- "result 1"
        }()
        select {
        case res := <-c1:
            fmt.Println(res)
        case <-time.After(time.Second * 1):
            fmt.Println("timeout 1")
        }
    }
    

    其实它利用的是time.After方法,它返回一个类型为<-chan Time的单向的channel,在指定的时间发送一个当前时间给返回的channel中。

    1.9 Timer和Ticker

    timer是一个定时器,代表未来的一个单一事件,你可以告诉timer你要等待多长时间,它提供一个Channel,在将来的那个时间那个Channel提供了一个时间值。下面的例子中第二行会阻塞2秒钟左右的时间,直到时间到了才会继续执行。

    timer1 := time.NewTimer(time.Second * 2)
    <-timer1.C
    fmt.Println("Timer 1 expired")
    

    当然如果你只是想单纯的等待的话,可以使用time.Sleep来实现。
    还可以使用timer.Stop来停止计时器。

    timer2 := time.NewTimer(time.Second)
    go func() {
    	<-timer2.C
    	fmt.Println("Timer 2 expired")
    }()
    stop2 := timer2.Stop()
    if stop2 {
    	fmt.Println("Timer 2 stopped")
    }
    

    ticker是一个定时触发的计时器,它会以一个间隔(interval)往Channel发送一个事件(当前时间),而Channel的接收者可以以固定的时间间隔从Channel中读取事件。下面的例子中ticker每500毫秒触发一次,你可以观察输出的时间。

    ticker := time.NewTicker(time.Millisecond * 500)
    go func() {
    	for t := range ticker.C {
    		fmt.Println("Tick at", t)
    	}
    }()
    

    类似timer, ticker也可以通过Stop方法来停止。一旦它停止,接收者不再会从channel中接收数据了。

    1.10 同步

    channel可以用在goroutine之间的同步。
    下面的例子中main goroutine通过done channel等待worker完成任务。 worker做完任务后只需往channel发送一个数据就可以通知main goroutine任务完成。

    import (
    	"fmt"
    	"time"
    )
    func worker(done chan bool) {
    	time.Sleep(time.Second)
    	// 通知任务已完成
    	done <- true
    }
    func main() {
    	done := make(chan bool, 1)
    	go worker(done)
    	// 等待任务完成
    	<-done
    }
    

    1.11 示例说明

    package main
    
    import (
    	"fmt"
    	"time"
    )
    // channel一般用于并发较大的场景,也可以用于做计划任务,消息队列之类的功能
    
    func send(ch_test chan int) {
    	ch_test <- 1
    	ch_test <- 2
    	ch_test <- 3
    	ch_test <- 4
    }
    
    func receive(ch_test chan int) {
    	var recv int
    	for {  // 无限循环 由于receive要不断从channel中读取可能存在的数据,所以receive一般使用一个无限循环来读取channel,避免send发送的数据被丢弃
    		recv = <-ch_test
    		fmt.Println(recv)
    	}
    }
    
    func main() {
    	ch_test := make(chan int)
    	// 两个goroutine,一个用于执行send函数,该函数每次向channel ch_test中发送写入一个int数值,receive函数每次从chan_test中读取一个int数值
    	go send(ch_test)
    	go receive(ch_test)
    	time.Sleep(time.Duration(2) * time.Second)  // 当main函数睡眠时间到了指定的时间main程序终止,也就是主程序结束,所有goroutine停止运行
    }
    

    参考链接:
    https://gobyexample.com/channels
    https://colobu.com/2016/04/14/Golang-Channels/

  • 相关阅读:
    saas 系统租户个性化域名&&租户绑定自己域名的解决方案(转)
    NGINX代理导致请求头header中的信息丢失问题
    Linux系统下查看硬件信息命令大全
    CentOS7开机时在进度条界面卡死(转)
    404 页面不存在
    Failed to set version to docker-desktop: exit code: -1
    centos 中Stream转 Image 报错
    CMM5级
    软件过程模型
    Some common used 3rd party packages for node cli app
  • 原文地址:https://www.cnblogs.com/yuhaohao/p/14626606.html
Copyright © 2011-2022 走看看