goroutine和channel基础

goroutine

不同于其他语言的线程,一个Go语言程序中并发执行体是goroutine 
默认一个程序只有一个goroutine,其负责调用main函数执行程序

编程时,可以认为goroutine是线程(虽然完全不是),进而写出正确的程序

goroutine创建

像defer一样: 
go function() 
go ins.method() 
用于创建goroutine,goroutine将执行go关键字给定的函数、方法

go func()不是等待func执行完成后才返回,而是立即返回,之后goroutine会异步执行,像线程一样

goroutine结束

当goroutine执行完其函数、方法后,将自动退出(像线程)

此外,当程序从main函数中返回后,此程序的所有goroutine都被强行终止(像线程)

我们没有直接的办法可以杀死一个goroutine,而只可以使用通信方式来关闭之

通道

goroutine是Go程序的并发执行体,而通道(channel)则是goroutine之间的消息通道,是goroutine们之间互相通信的消息队列

通道的创建

与map类似,通道变量是实际通道的引用,其类型是chan T,T是通道内元素的类型

通道变量的默认值是nil,即

 
<wiz_code_mirror>
 
 
 
 
 
var ch chan int
ch == nil //true
 
 

make创建通道且指定通道容量,并由通道变量对其引用: 
默认通道容量是0

 
<wiz_code_mirror>
 
 
 
 
 
ch := make(chan int)
//ch是chan int类型,0容量通道,是无缓冲通道
ch := make(chan int, 0)
//ch是chan int类型,0容量通道,是无缓冲通道
ch := make(chan int, 3)
//ch是chan int类型,3容量通道,是缓冲通道
 
 

由于通道变量是引用类型,故函数参数传值相当于传引用,是没毛病的

通道操作1、发送数据
<wiz_code_mirror>
 
 
 
 
 
ch <- x //将元素x发送到通道ch
 
 
通道操作2、接收数据
<wiz_code_mirror>
 
 
 
 
 
x := <-ch //将通道中元素弹出赋与变量x
<-ch //将通道中元素弹出并丢弃
 
 
通道操作3、关闭通道
 
<wiz_code_mirror>
 
 
 
 
 
close(ch)
 
 
  • 关闭通道后,在此通道再发送数据会导致宕机
  • 一般当发送者发完消息后,才关闭通道
  • 关闭通道后,在此通道上无论再接收多少次数据,都将立刻得到通道元素类型T的零值

注意: 
通道ch关闭后,变量ch并不被自动垃圾回收,与文件描述符的close不是一回事 
通道变量的回收与普通变量的回收别无区别,没人可引才回收

无缓冲通道(cap=0)

无缓冲通道上进行:

  • 写操作:将会阻塞,直到接收方对管道执行接收操作
  • 读操作:将会阻塞,直到发送发对管道执行发送操作

于是,无缓冲通道使得发送方、接收方完全同步化,故也叫“同步通道

缓冲通道

缓冲通道上进行:

  • 写操作:将数据加到管道尾端,管道若已满则阻塞
  • 读操作:从管道头部弹出数据,管道若已空则阻塞

缓冲通道使得发送者、接收者相对变得解耦

接收者判断通道关闭

由于close将导致管道永远给接收者返回零值,则: 
chan int管道关闭后,接收者将永远收到0 
但是我们无法因为收到0就认为管道关闭,因为有可能发送者确实发送了0

那么接收者如何判断管道关闭?

方法1:
 
<wiz_code_mirror>
 
 
 
 
 
x, ok := <-ch
 
 

ok值为true表示管道有数据;否则表示管道已关闭

方法2:
<wiz_code_mirror>
 
 
 
 
 
for x:= range ch {
    ...
}
//当通道关闭,将退出循环
 
 

不要把通道当普通队列使用

由于管道简单,是否可以用作普通的queue数据结构用于算法啥的?

也没有绝对的理由不能用,但是通道就是给多goroutine用的,A goroutine写,B goroutine读才是王道

为通道声明方向

上文说到,对于同一个管道,在一个goroutine中应该要么收、要么发,建议不要既收也发而作为队列使用

例如:

 
<wiz_code_mirror>
 
 
 
 
 
func my_sender(ch chan int) {
    ch <- xxx
}
func my_receiver(ch chan int) {
    x := <-ch
}
 
 

my_sender仅作为发送者,my_receiver仅作为接收者

但是,在my_sender中接收ch,在my_receiver中向ch发送,都是被允许的:

 
<wiz_code_mirror>
 
 
 
 
 
func my_sender(ch chan int) {
    ch <- xxx
    x := ch //允许
}
func my_receiver(ch chan int) {
    x := <-ch
    ch <- x //允许
}
 
 

为了防止写错,Golang支持管道作为函数参数时的方向声明:

  • chan<- T 表示仅可发送,在此管道上接收将导致编译错误
  • <-chan T表示仅可接收,在此管道上发送将导致编译错误
 
<wiz_code_mirror>
 
 
 
 
 
//声明ch在此函数中仅可发送
func my_sender(ch chan<- int) {
    ch <- xxx
    x := ch //compile error!
}
//声明ch在此函数中仅可接收
func my_receiver(ch <-chan int) {
    x := <-ch
    ch <- x //compile error!
}
func main() {
    ch := make(chan int, 3)
    go my_sender(ch)//调用时没啥区别
    go my_receiver(ch)//调用时没啥区别
    ......
}
 
 

使用通道时,在goroutine函数参数声明上加上方向声明是个非常好的习惯!

goroutine泄漏

两种情况:

  • 协程从ch读数据,但总是无人往此ch写数据;读数据协程将永远阻塞下去
  • 协程往ch写数据,但总是无人读取此ch;写数据一旦阻塞,将永远阻塞下去

协程永远阻塞,无法回收,于是乎:协程泄漏

例子:

 
<wiz_code_mirror>
 
 
 
 
 
func trySpeed() {
    ch := make(char string)
    go func() { ch <- 获取"baidu.com"响应数据 }()
    go func() { ch <- 获取"google.com"响应数据 }()
    go func() { ch <- 获取"127.0.0.1:80"响应数据 }()
    return <-ch
}
 
 

trySpeed函数将读取ch通道,得到最快被访问的网址内容

由于ch是无缓冲管道,故另外两个协程访问到内容后写ch将阻塞 
而由于trySpeed函数运行完后,ch管道也没人能看到了,于是乎,将无人再读取ch,于是剩余两个协程将永远阻塞 =》他俩泄漏了!

猜你喜欢

转载自www.cnblogs.com/pythoncoder/p/goroutine_channel_usage.html