zoukankan      html  css  js  c++  java
  • Go 通道(channel)与协程间通信

    协程间通信

    协程中可以使用共享变量来通信,但是很不提倡这样做,因为这种方式给所有的共享内存的多线程都带来了困难。

    在 Go 中有一种特殊的类型,通道(channel),就像一个可以用于发送类型化数据的管道,由其负责协程之间的通信,从而避开所有由共享内存导致的陷阱;这种通过通道进行通信的方式保证了同步性。

    数据在通道中进行传递:在任何给定时间,一个数据被设计为只有一个协程可以对其访问,所以不会发生数据竞争。数据的所有权(可以读写数据的能力)也因此被传递。

    通道服务于通信的两个目的:值的交换,同步的,保证了两个计算(协程)任何时候都是可知状态。

    声明与初始化

    通道的声明格式如下:

    var identifier chan datatype

    未初始化的通道的值为 nil。
    从声明的格式能够看出来,通道只能传输一种类型的数据,比如 chan int 或者 chan string,所有的类型都可以用于通道,空接口 interface{} 也可以。

    通道也是引用类型,所以我们使用 make() 函数来给它分配内存。

    var ch1 chan string
    ch1 = make(chan string)
    //或者简写为
    ch2 := make(chan string)
    

    通信操作符

    操作符 <- 直观的表示了数据的传输,信息按照箭头的方向流动。

    • 发送(数据流向通道)
      ch <- int1 表示:将变量 int1 放入通道中,用通道 ch 发送变量 int1
    • 接收(数据从通道中流出)
      int2 = <- ch 表示:变量 int2 从通道 ch 接收数据(获取新值)。

    下面的例子展示了两个协程之间的通信:

    import (
       "fmt"
       "time"
    )
    
    func main() {
       ch := make(chan string)
    
       go sendData(ch)
       go getData(ch)
    
       time.Sleep(1e9)
    }
    
    func sendData(ch chan string){
      ch <- "golang"
    }
    
    func getData(ch chan string){
      fmt.Println(<- ch)
    }
    

    输出结果:

    在 main() 方法的最后一行中,使用了 time 包中的 sleep 函数来暂停1秒,以确保 main() 方法会在另个两个协程之后结束,如果不在 main() 方法中等待,协程会随着程序的结束而消亡。

    通道阻塞

    默认情况下,通信是同步且无缓冲的,通道的发送/接收操作在对方准备好之前都是阻塞的:

    • 对于同一个通道,在没有接受者接收数据之前,发送操作会被阻塞。
    • 对于同一个通道,在没有发送者发送数据之前,接收操作会被阻塞。

    现在我们把上面的例子修改一下,去掉 sendData() 方法前的 go 关键字:

    func main() {
        ch := make(chan string)
    	
        //go sendData(ch)
    	sendData(ch)
        go getData(ch)
    	
        time.Sleep(1e9)
    }
    

    输出结果:

    运行程序后出错了,抛出了一个 panic,这是为什么呢?

    这是因为 Go 程序在运行时会检查所有的协程,查找是否存在有阻塞(读取或者写入某个通道)的情况。

    而上面这段代码中的 sendData() 方法阻塞了 main() 方法,导致 go getData()无法执行,也就是说通道的接收操作也就无法被执行,而 sendData() 中的发送操作也会一直等待,此时所有的协程都休眠了,程序无法继续运行。这就是死锁(deadlock)形式。

    如果我们接着再修改一下代码,保留 sendData() 方法的关键字,而去掉 getData() 方法的关键字:

    func main() {
      ch := make(chan string)
    
       go sendData(ch)
       getData(ch)
    
       time.Sleep(1e9)
    }
    

    因为发送和接收操作都会被执行,所以结果是正常输出“golang”。

    通信是一种同步形式:通过通道,两个协程在通信(协程会和)中某刻同步交换数据。无缓冲通道成为了多个协程同步的完美工具。

    带缓冲的通道

    一个无缓冲通道只能包含 1 个元素,但是我们可以为通道提供了一个缓存,来扩展通道可容纳的元素个数。

    在 make() 函数中设置容量:

    buf := 100
    ch1 := make(chan string, buf)
    
    • 在缓冲被满载(缓冲被全部使用)之前,给一个带缓冲的通道发送数据是不会阻塞的
    • 在缓冲被清空之前,从通道读取数据也不会阻塞。

    如果容量大于 0,通道就是异步的了:缓冲满载(发送)或者变空(接收)之前通信不会阻塞,元素会按照发送的顺序被接收。如果容量是 0 或者未设置,通信仅在收发双方准备好的情况下才可以成功。

  • 相关阅读:
    Yarn调度器负载模拟器——Yarn Scheduler Load Simulator (SLS)
    代理模式之动态代理
    12.怎样自学Struts2发送邮件和验证补充[视频]
    jquery 深入学习笔记之中的一个 (事件绑定)
    IBM中国研究院、SAP、网易游戏、IBM2015应届生招聘笔试面试问题分享
    [LeetCode]Generate Parentheses
    oracle故障解决
    hdu 1065 I Think I Need a Houseboat
    hdu 1065 I Think I Need a Houseboat
    hdu 1237 简单计算器
  • 原文地址:https://www.cnblogs.com/liyutian/p/10203261.html
Copyright © 2011-2022 走看看