zoukankan      html  css  js  c++  java
  • golang的Channel

    golang的Channel

    Channel 是 golang 一个非常重要的概念,如果你是刚开始使用 golang 的开发者,你可能还没有真正接触这一概念,本篇我们将分析 golang 的Channel

    1. 引入

    要讲 Channel 就避不开 Goroutine -- 协程。闲话不说, 直接上个例子

    Goroutine demo

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func main() {
    	origin := 1
    	go doSomeCompute(origin)
    	time.Sleep(5 * time.Second)
    }
    
    func doSomeCompute(num int) {
    	result := num * 2
    	fmt.Println(result)
    	return
    }
    

    简单来说,例子中有一个 main 的协程,一个 doSomeCompute 的协程。还有个延时退出的方法等待计算协程计算结果。我们尝试思考这个例子的一些问题:

    a. 如何获取 doSomeCompute 的计算结果?

    b. 如何获取 doSomeCompute 的执行状态,当前是执行完了还是执行中?

    如何解决这种问题呢?

    Channel!

    2. Channel

    Channel怎么处理上面的问题?我们直接上代码:

    举例

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	origin := 1
    	//一个无缓冲Channel
    	res := make(chan int)
    	go doSomeCompute(origin, res)
    	fmt.Println(<-res)
    }
    
    func doSomeCompute(num int, res chan int) {
    	result := num * 2
    	res <- result
    }
    

    例子中,Channel 充当起了协程间通信的桥梁。Channel 可以传递到协程说明它是线程安全的,事实也是如此。Channel 可以理解为管道,协程 doSomeCompute 向 Channel 写入结果, main 中读取结果。注意,例子中读取 Channel 的地方会阻塞直到拿到计算结果,这样就解决了问题 a 和 b。

    2. Channel 的方向性

    上面的例子中,计算协程是负责计算并将计算结果写入 Channel ,如果我们希望保证计算协程不会从 Channel 中读取数据该怎么处理?很简单,看例子:

    func doSomeCompute(num int, res chan<- int) {
    	result := num * 2
    	res <- result
    }
    

    这个参数的声明 chan<- int 就表示该函数只能讲数据写入 Channel ,而不能从中读取数据。后面的 int 表示 Channel 中数据的格式。同样的, 只可以读取数据的 Channel 可以声明为 <-chan int 。而例子中不带有方向声明的 Channel 则既可以写入也可以读取。

    3. 阻塞性质

    Channel 的读取和写入操作在各自的协程内部都是阻塞的。比如例子中 fmt.Println(<-res) , 这一语句会阻塞直至计算协程将计算结果放入,可以读出。也就是说,协程会阻塞直至从 res 中读出数据。

    注意,无缓冲的 Channel 的读写都是阻塞的,有缓冲的 Channel 可以一直向里面写数据,直到缓存满才会阻塞。读取数据同理, 直至 Channel 为空才阻塞。

    用一个典型的例子来说明缓冲和非缓冲 Channel 的区别:

    package main
    
    import "fmt"
    
    func doSomeCompute(ch chan int) {
    	fmt.Println("deadlock test")
    	<- ch
    }
    
    func main() {
    	ch := make(chan int)
    	ch <- 1
    	go doSomeCompute(ch)
    }
    

    例子中,main 协程会向 ch 写入数据, 这一过程是阻塞的,也就是说,doSomeCompute 协程无法执行,程序死锁。输出如下:

    fatal error: all goroutines are asleep - deadlock!
    
    goroutine 1 [chan send]:
    main.main()
    	C:/mygo/src/demo/blog.go:12 +0x73
    exit status 2
    

    如果改成有缓冲的 Channel :

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func doSomeCompute(ch chan int) {
    	fmt.Println("deadlock test")
    	<- ch
    }
    
    func main() {
    	ch := make(chan int, 1)
    	ch <- 1
    	go doSomeCompute(ch)
    	time.Sleep(1 * time.Second)
    }
    

    有与有缓冲的 Channel 写入后不阻塞(下一次写入才会阻塞),程序会继续执行。

    4. Channel 的数据结构

    Channel 在 golang 中实际上就是个数据结构。在 Golang 源码中, Channel 的数据结构 Hchan 的定义如下:

    struct	Hchan
    {
    	uint32	qcount;			// total data in the q
    	uint32	dataqsiz;		// size of the circular q
    	uint16	elemsize;
    	bool	closed;
    	uint8	elemalign;
    	Alg*	elemalg;		// interface for element type
    	uint32	sendx;			// send index
    	uint32	recvx;			// receive index
    	WaitQ	recvq;			// list of recv waiters
    	WaitQ	sendq;			// list of send waiters
    	Lock;
    };
    

    时间仓促,这次对 Channel 介绍写的有点简单粗暴,下次再写。

  • 相关阅读:
    一般操作
    node express mongodb 数据录入
    express新版本后app.use(express.bodyParser())无效
    npm adduser报错问题
    01demo-mongodb
    Win32汇编--02必须了解的基础知识
    第17章 本书最后将学习什么呢(需要回头学习)
    第十六章 直接定址表(需要回头学)
    指令系统总结
    第十五章 外中断
  • 原文地址:https://www.cnblogs.com/artong0416/p/7445188.html
Copyright © 2011-2022 走看看