Goroutine
goroutine
是一种轻量级的"线程",go
是Go语言中的关键字,使用go
就会在一个新的goroutine
中的并发执行.当返回数据的时候,goroutine
自动结束.例如下面代码:
package main
import "fmt"
func Add(x int) {
j := x * x
y := x + j
fmt.Println("x=",x," x*x=:",y," x + x * x = ",y)
}
func main() {
for i := 0; i < 5; i++ {
// 并发执行Add()函数
go Add(i)
}
}
当执行程序并没有任何打印,这是因为Go语言程序执行的机制.当Go程序执行会初始化package main
包并执行main()
开始,当main()
函数返回时程序退出.并不会等待其他goroutine
结束.main()也是一个goroutine
.
goroutine
在main()
函数结束时会一同结束. 要想查看其他goroutine
打印的结果可以使main()
睡眠等待.修改main()
:
func main() {
for i := 0; i < 5; i++ {
// 并发执行Add()函数
go Add(i)
}
// 添加等待时间 2秒
time.Sleep(time.Second * 2)
}
执行打印结果
x= 3 x*x=: 12 x + x * x = 12
x= 0 x*x=: 0 x + x * x = 0
x= 4 x*x=: 20 x + x * x = 20
x= 1 x*x=: 2 x + x * x = 2
x= 2 x*x=: 6 x + x * x = 6
并发通信
两种常见的并发通信模型,即为 共享数据和消息.其中共享数据可以有很多方式.例如内存,磁盘文件,网络数据.最常见的是共享内存.
Go语言通性方式为消息机制,消息机制认为每个并发单元都是独立的个体都有自己的变量,但在不同并发单元之间这些变量是不会共享的.每个并发单元的输入和输出只有一种,那就是消息.可以理解为进程.每个进程不会被其他的进程打扰,只做自己的工作.不同进程间靠消息通信且不会共享内存.
在Go语言中消息通信机制为channel
.让我们`不要通过共享内存来通信,而应该通过通信来共享内存.
Channel
介绍
channel
是goroutine
之间的通信机制,一个goroutine可以通过channel给另一个goroutine发送消息。每一个channel都有一个特殊的类型,例如发送int类型数据的channel写作chan int
.
使用make函数创建一个channel,通过以下生命的channel是双向的,可以想channel发送数据也可以接受数据.,
// 通过make构建channel
ch := make(chan int)
// 通过var声明
var ch chan int
类似于map,channel也对应一个make创建的底层数据结构的引用。当复制一个额channel或用于函数参数传递时,只是靠背了一个channel的引用,因此调用者和被调用者将引用同一个channel的对象。
发送与接收
channel有发送和接受两个主要操作,一个发送语句将值从goroutine通过channel发送到另一个执行接受操作的goroutine。发送和接受两个操作都使用<-
运算符:
ch <- x // channel 接受数据 x
x <- ch // channel发送数据并赋值为x
<- ch // channel 发送数据,忽略接收者
如下对channel发送值,在goroutine中获取channel的值:
package main
import (
"fmt"
"time"
)
func acceptGo(c chan int){ // 死循环接受channel消息
for {
n := <- c // 接受channel传如的消息.赋值为n
fmt.Println(n)
}
}
func myChanDemo(){
c := make(chan int) // 构建一个channel
go acceptGo(c) // 启动一个goroutine接受channel的消息并打印
c <- 1 // 向channel发送数据 1
c <- 2 // 向channel发送数据 2
time.Sleep(time.Second * 2) // 等待2秒防止goroutine过早结束.
}
func main(){
myChanDemo()
}
缓冲channel
通过上述的介绍已经知道如何创建无缓存的channel,需要注意:无缓存channel的发送操作会导致发送者goroutine阻塞,直到另一个goroutine在相同的channel上执行接受操作.当发送的channel成功传输后,两个goroutine可以继续执行后面语句。反之如果接收者先执行接受操作,接收者也会发生阻塞,知道有另一个goroutine在相同的channel上执行发送操作.
对于有缓冲的channel可以不需要有接收者,而无缓冲的channel必须要有接收者,如下:
package main
func main() {
c := make(chan string) // 创建接受无缓冲channel
// 发送消息
c <- "hello"
}
出现错误
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
当生成的channel是有缓冲的可以不需要接收者,如下代码执行没有错误:
package main
func main() {
c := make(chan string, 3) // 创建接受三个消息的缓冲channel
// 发送消息
c <- "hello"
c <- "qvbilam"
c <- "love you"
}
需要注意的是缓冲区接受多少消息只能发送不超过缓冲数量的消息,当上述例子发送第四个消息的时候出现错误.
len(channel)
查看通道的大小关闭
内建close
方法可以用来关闭channel.
c := make(chan int,10)
close(c) // 关闭channel
在接受消息的时候可以使用第二个参数来判断channel的状态.如下
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan string, 3) // 创建接受三个消息的缓冲channel
// 发送消息
go func(c chan string) {
for {
message, ok := <-c // 接受消息
if !ok { // 判断通道是否正常
fmt.Println("channel close")
return
}
// 打印消息
fmt.Println(message)
}
}(c)
c <- "hello"
c <- "qvbilam"
close(c) // 关闭channel
time.Sleep(time.Second * 3)
}
串联channel
channel可以用于将多个goroutine连接在一起,一个channel的输出作为下一个channel的输入.这种串联的channel即是管道(pipeline).
示例
需求
- 生成三个goroutine
- Counter:第一个goroutine生成0,1,2...数列
- Squarer:第二个goroutine将每个整数求平方
- Printer:第三个goroutine打印每个整数
- 创建两个channel
- 第一个channel将Counter的数列发送给Squarer
- 第二个channel将Squarer的结果发送给Printer
实现
package main
import (
"fmt"
)
func main() {
input := make(chan int, 10) // 输入通道
output := make(chan int, 10) // 输出通道
go Counter(input)
go Squarer(input, output)
Printer(output)
}
// 生成数列
func Counter(input chan int) {
for x := 2; x < 10; x++ {
input <- x // 向输入通道发送消息
}
close(input) // 关闭第一个channel
}
// 求平方和
func Squarer(input chan int, output chan int) {
for x := range input {
output <- x * x // 向输出通道发送消息
}
close(output) // 关闭第二个通道
}
// 打印结果
func Printer(output chan int) {
for v := range output { // 直到输出通道关闭为止
fmt.Println(v) // 打印输出通道的消息
}
}
单向channel
通过上述例子可以看到两个channel(input,output)是相同的类型,但是使用的方式是相反的:input只用于输入,output只用于输出,但是无法保证Squarer函数向input进行输入.当余个channel作为函数参数时,它一般总是被专门用于只发送或只接受。
类型chan <- int
表示只发送int的channel,只能发送不能接受,<-chan int
表示只接受不能发送.修改原先示例:
package main
import (
"fmt"
)
func main() {
input := make(chan int, 10) // 输入通道
output := make(chan int, 10) // 输出通道
go Counter(input)
go Squarer(input, output)
Printer(output)
}
// 生成数列
func Counter(input chan<- int) {
for x := 2; x < 10; x++ {
input <- x // 向输入通道发送消息
}
close(input) // 关闭第一个channel
}
// 求平方和
func Squarer(input <-chan int, output chan<- int) {
for x := range input {
output <- x * x // 向输出通道发送消息
}
close(output) // 关闭第二个通道
}
// 打印结果
func Printer(output <-chan int) {
for v := range output { // 直到输出通道关闭为止
fmt.Println(v) // 打印输出通道的消息
}
}
select
select是Go中的控制结构,类似于switch语句,但是在select中每一个case必须是一个通信的操作,即发送或接收.
如下简单的调度,通过死循环不间断的获取消息,当有消息的时候会执行case里的内容,当不存在会执行default.需要注意的是,当通道关闭后应该跳出死循环.
package main
import "fmt"
func main() {
c := make(chan int)
go func(c chan int) { // 向通道传输5个消息
for i := 0; i < 5; i++ {
c <- i
}
close(c) // 传输关闭关闭通道
}(c)
for { // 死循环执行select
select {
case res, ok := <-c: // 接受消息
if !ok { // 通过ok判断通道是否关闭
fmt.Println("结束")
goto end // 跳转结束
}
fmt.Println(res)
default:
fmt.Println("别爱我,没结果") // 没有消息的时候执行
}
}
end: // 结束
fmt.Println("欢迎下次再来")
}
下面的例子当有多个通道,在接受数据的时候不管是哪个通道,谁先发送消息就接受谁的消息,可以使用select进行任务调度:
package main
import (
"fmt"
"time"
)
func myChan() chan int {
out := make(chan int, 10)
go func() {
for i := 0; i < 10; i++ {
time.Sleep(time.Microsecond)
out <- i
}
}()
return out
}
func main() {
var c1, c2 = myChan(), myChan()
var res int
for {
select {
case res = <-c1:
fmt.Println("c1:", res)
case res = <-c2:
fmt.Println("c2:", res)
default:
// todo 不用管
}
}
}
打印结果为乱序的,不管是哪个渠道,只要是先输入就先打印,没次的结果都不一样的~如下执行结果
c1: 0
c2: 0
c2: 1
c1: 1
c1: 2
c1: 3
c2: 2
c2: 3
c1: 4
c2: 4
c1: 5
c2: 5
c1: 6
c2: 6
c1: 7
c2: 7
c1: 8
c1: 9
c2: 8
c2: 9