Golang并发(三) - Channel

90design · · 735 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

What you are wasting today is tomorrow for those who died yesterday; what you hate now is the future you can not go back.

你所浪费的今天是昨天死去的人奢望的明天; 你所厌恶的现在是未来的你回不去的曾经。

 

Channel通道介绍

    什么是channel, channel可以理解为goroutine之间通信的通道。一端发送数据方到达接收数据方的桥梁。

 

声明通道

    每个channel都有一个与之相关联的类型, 这种类型是信道允许传输的数据类型。

    channel的零值是nil, 一个nil channle没有任何用途。因此必须使用类似map和slice的方式定义。

package main

import "fmt"

func main() {  
    var A chan int
    if A == nil {
        fmt.Println("nil channle ,定义channle")
        A = make(chan int)
        fmt.Printf("Type of A is %T", A)
    }
}

 

channel的发送和接收

chs := make(chan int)

chs <- 0   // 写入
i := <- chs  // 接收

 

发送、接收阻塞

    channel的发送和接收默认是阻塞的。当从channel读取数据时, main在读取时阻塞,知道有数据写入channel, 通道的发送也是如此。

    通道的这个属性可以使goroutine有效地进行通信,而无需使用像其他编程语言中,很常见的显式锁或条件变量。

 

channel的使用

package main

import (  
    "fmt"
    "time"
)

func hello() {  
    fmt.Println("Hello goroutine")
}
func main() {  
    go hello()
    time.Sleep(1 * time.Second)
    fmt.Println("main function")
}

    在这里我们使用sleep main进程的方式,阻塞main的继续执行,好让goroutine有时间执行 hello()内的内容。

    但是这样做并不能有效的解决多个协程(main 和 goroutine)并行时出现的协程未执行完,main程退出问题。 如果hello()中的执行时间超过1S, 那么sleep就无实际意义了。

    下面使用channle来改善以上程序:

package main

import (
	"fmt"
)
var done chan bool

func hello() {
	fmt.Println("Hello world goroutine")
	done <- true
}
func main() {
	done = make(chan bool)
	go hello()
	<-done
	fmt.Println("main function")
}

    首先main程会初始化一个channle, 然后go hello() 创建一个新的协程,并立即执行<-done (前面我们说过,只是简单的调用,并不关心协程的输出,而立刻返回继续往下进行),并没有发送数据到channel done,所以main程阻塞。直到goroutine执行完成并发送数据到channle,main才会继续执行输出。

    还有一点要注意的是:channel的接受可以不用赋值到任何变量,即有效的阻塞。

    下面的程序会更好的理解channle:

package main

import (
	"fmt"
	"time"
)

func hello(done chan bool) {
	fmt.Println("go hello() sleep ...")
	time.Sleep(4 * time.Second)
	fmt.Println("go hello() awake")
	done <- true
}
func main() {
	done := make(chan bool)
	fmt.Println("Main going to call hello go goroutine")
	go hello(done)
	<-done
	fmt.Println("Main received data")
}

    程序会在goroutine中sleep后继续写入channle。

package main

import (
	"fmt"
)

func add(number int, addChs chan int) {
	number = number + 10
	addChs <- number
}

func reduce(number int, reduceChs chan int) {
	number = number - 10
	reduceChs <- number
}

func main() {
	number := 589
	addChs := make(chan int)
	reduceChs := make(chan int)
	go add(number, addChs)
	go reduce(number, reduceChs)
	addVar, reduceVar := <-addChs, <-reduceChs
	fmt.Println(addVar, reduceVar)
}

 

 

死锁

    当程序中只有channle的读取或者发送的其中一个操作, 那么程序就会发生Deadlock。

package main


func main() {
	ch := make(chan int)
	<-ch
}

fatal error: all goroutines are asleep - deadlock!

 

单向通道

    channel既可以发送也可以接收, 我们成为双向channle。 如果channle只能接收或者发送,就称为单向通道。

    实例code说明一切:

package main

import "fmt"

func sendData(sendch chan<- int) {  
    sendch <- 10
}

func main() {  
    sendch := make(chan<- int)
    go sendData(sendch)
    fmt.Println(<-sendch)
}

    在mian程开始时,我们定义了一个只写(只发送)的channel, 但是后面main程又从中读取。所以导致go程序编译失败。

    在上面我们看到sendData()接收到一个只写的channel ,那么如果我们传入的是双向的channel会发生什么?   

package main

import "fmt"

func sendData(sendch chan<- int) {  
    sendch <- 10
}

func main() {  
    chnl := make(chan int)
    go sendData(chnl)
    fmt.Println(<-chnl)
}

    程序照常执行输出。为什么?

    可以这么简单的理解一下:  因为在方法内(像java 作用域)sendData限制了sendch通道的读取,只能对channle进行写入操作,但是main程不属于同一作用域,sendData限制无效,main继续读取操作channle。

 

通道的关闭

    发送端可以关闭通道,以便告诉接收方,不再发送数据。

    接收方也可以在接收的同时,通过附加变量的方式检测channle的关闭状态。

v, ok := <- ch  

    如果开启状态下,ok为true, 否则为false。 

    在channel为关闭状态时, v(接收到的值)也是有值的,即channle type( 通道类型 )的零值。 比如 make(chan int) 零值就是0,  make(chan string)零值就是"", make(chan map[string]int)呢?大家可以联想一下。

    

package main

import (  
    "fmt"
)

func producer(chnl chan int) {  
    for i := 0; i < 10; i++ {
        chnl <- i
    }
    close(chnl)
}
func main() {  
    ch := make(chan int)
    go producer(ch)

    // 试试将此处改用range代替
    for {
        v, ok := <-ch
        if ok == false {
            break
        }
        fmt.Println("Received ", v, ok)
    }
}

前面我们说过,<- channel 通道的读取也会使程序阻塞,那么range操作呢?

package main

import (
	"fmt"
)

func options(number int, dchnl chan int) {
	for i:= 0 ;i< 10 ;i++ {
		digit := number + i
		dchnl <- digit
	}
	close(dchnl)
}
func box(number int, s chan int) {
	sum := 0
	dch := make(chan int)
	go options(number, dch)
	for digit := range dch {
		sum += digit + digit
	}
	s <- sum
}

func main(){
	s := make(chan int)
	go box(1, s)
	sVal := <-s
	fmt.Println("Final output: ",sVal)
}

range读取channle同样可以阻塞程序,并不是单单<-channle。

 

The End.

 


有疑问加站长微信联系(非本文作者)

本文来自:开源中国博客

感谢作者:90design

查看原文:Golang并发(三) - Channel

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

735 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传