18.goroutine与channel¶
goroutine¶
go语言中,有一个称为goroutine的概念,这个概念和其他编程语言中的协程、进程和线程又相似之处,但又有所不同。
goroutine主要用于并发操作。
go语言中并发操作很简单,在要执行的函数或代码前加go
关键词就行,其他部分无需修改,这也是go语言的一大优势。例如
要注意,如果main函数结束了,无论goroutine是否结束,都会立即停止程序。
和其他语言一样,go的并发不能保证其执行先后顺序,即使按照特定顺序先后启动goroutine。
Warning
只要goroutine没有返回,其就会一直占用内存等资源。
channel¶
既然goroutine不能保证这个并发操作在main函数退出时继续,go语言设计了另一个工具,chanel。
channel的概念来源于老式气动管道传输系统。在go语言中,channel用于多个并发操作之间的数据传输。
创建通道¶
如果不使用make创建通道,通道变量的默认值就是nil
。
通道中数据的发送与接受¶
当一个goroutine执行了发送操作,其会阻塞,直到这个值被接受。相同,执行接受操作的goroutine也会被阻塞,直到接受到数据。
数据之间的传输与接受是通过同一个channel对象完成的,即这个对象必须传给goroutine,goroutine向其发送或接收,main或者其他goroutine接受或发送。可以看出,channel其实是一个指针变量。
等待接受,直到数秒后¶
对于网络程序,我们在等待数秒后没有接收到任何响应,就认为其链接超时并主动断开连接。
go语言的标准库提供了处理上述问题的代码。标准库time
中的time.After(<time>)
会返回一个channel,并在指定时间后,go语言的运行时会向这个channel发送数据。其中传入变量为time
为time包内置相关时间变量,如time.Second
。
光有上述工具还不够,go语言提供了一个名为select
的语句。这个语句的写法类似switch
,不过每个case上都是一个通道,如果任意一个通道接受到数据,则执行相应case。下面是select
语句的示例:
c := make(chan int)
go routineFunction(c)
timeout := time.After(2* time.Second)
select {
case res := <- c:
<statement>
case <- timeout:
<statement>
}
Warning
select
语句如果不包含任何case
,其将永远等待下去。
关闭通道¶
对chan
对象,go提供了一个函数close(<chanVariable>)
。对通道对象执行该函数后,会将通道关闭。对关闭的通道写入数据会引发panic
,读取则会获得对应数据类型的零值。
那么如果检查通道是否关闭呢?可以使用如下代码:
当ok
为False
则代表通道关闭了。
range遍历通道变量¶
通道变量还可以使用range
变量,其结果是直到通道被关闭前的所有输入值。
互斥锁¶
为了保证各goroutine不会同时访问(主要是写入)某一个共享值而引发错误,go语言中有一个称为互斥锁的概念。
互斥锁的实现在sync
包中,通过类型sync.Mutex
实现。
上锁与解锁¶
解锁一般使用defer
来实现。互斥锁通常在同一包中声明并使用。
当一个goroutine尝试对互斥锁对象执行上锁操作时,如果这个变量已经被上锁,则当前goroutine会被阻塞,直到其解锁。依据这个原理,从而实现了共享资源的安全访问。
通常,将一个互斥锁对象放在结构体中,组成一个共享资源对象。