目录
一、goroutine goroutine 是 Go 语言中的轻量级线程实现,由 Go 运行时(runtime)管理。Go 程序会智能地将 goroutine 中的任务合理地分配给每个 CPU 。
Go 程序从 main 包的 main() 函数开始,在程序启动时,Go 程序就会为 main() 函数创建一个默认的 goroutine;所有 goroutine 在 main() 函数结束时会一同结束。
1. 创建 goroutine
Go 程序中使用 go 关键字为一个函数创建一个 goroutine 。一个函数可以被创建多个 goroutine,一个 goroutine 必定对应一个函数。
(1)格式
为一个函数创建 goroutine 的写法如下:
go 函数名(参数列表)
- 使用 go 关键字创建 goroutine 时,被调用函数的返回值会被忽略;
- 如果需要在 goroutine 中返回数据,需要使用通道(channel)特性,通过通道把数据从 goroutine 中作为返回值传出。
(2)示例
func main(){
go run()
for i := 0; i < 3; i++ {
fmt.Println(i)
}
}
func run(){
fmt.Println("I'm running.")
}
// 第一次运行结果:
0
1
I'm running.
2
// 第二次运行结果:
0
I'm running.
1
2
该例中,Go 程序在启动时,运行时(runtime)会默认为 main() 函数创建一个 goroutine 。在 main() 函数的 goroutine 中执行到 go run() 语句时,归属于 run() 函数的 goroutine 被创建,run() 函数开始在自己的 goroutine 中执行。此时,main() 继续执行,两个 goroutine 通过 Go 程序的调度机制同时运作。
2. 协程管理
func main(){
var wg sync.WaitGroup // 协程管理器
wg.Add(2) // 有2个协程
go run(&wg)
go run(&wg)
// 让协程管理器执行等待,等待 wg.Add(2) 的数字减到 0 才结束
wg.Wait()
}
func run(wg *sync.WaitGroup){
fmt.Println("I'm running.")
wg.Done() // 关闭协程
}
/*
输出结果:
I'm running.
I'm running.
*/
二、channel
channel 是 goroutine 之间通信的一种方式,其本身还需关联一个类型,也就是 channel 可以发送数据的类型(例如:发送 int 类型消息的 channel 写作 chan int )。
1. channel 的创建
// 声明一个 chan int 类型的 channel
ch := make(chan int) // 可读可写无缓存
// 声明一个 chan int 类型的 channel
ch := make(chan int, 10) // 可读可写有缓存
channel 和 map 类似,make 创建了一个底层数据结构的引用,当赋值或参数传递时,只是拷贝了一个 channel 引用,指向相同的 channel 对象。和其他引用类型一样,channel 的空值为 nil 。使用 == 可以对类型相同的 channel 进行比较,只有指向相同对象或同为 nil 时,才返回 true 。
2. channel 的类型
(1)无缓存的 channel
从无缓存的 channel 中读取消息会阻塞,直到有 goroutine 向该 channel 中发送消息;同理,向无缓存的 channel 中发送消息也会阻塞,直到有 goroutine 从 channel 中读取消息。
(2)有缓存的 channel
有缓存的 channel 类似一个循环队列(采用环形数组实现)。当缓存未满时,向 channel 中发送消息时不会阻塞,当缓存满时,发送操作将被阻塞,直到有其他 goroutine 从中读取消息;相应的,当 channel 中消息不为空时,读取消息不会出现阻塞,当 channel 为空时,读取操作会造成阻塞,直到有 goroutine 向 channel 中写入消息。
通过 len() 函数可以获得 channel 中的元素个数,通过 cap() 函数可以获得 channel 的缓存长度。
(3)单向 channel
c := make(chan int, 7)
// 以下两个单向 channel 共享 c,主要是在声明方法时使用,防止 channel 的滥用
var readc <-chan int = c // readc 是属于 c 的只可读取的 channel
var writec chan<- int = c // writec 是属于 c 的只可写入的 channel
3. channel 的读写操作
c := make(chan int)
go func() {
for i := 0; i < 10; i++ {
c <- i // 写
}
}()
for i := 0; i < 10; i++{
fmt.Printf("%d ", <-c) // 读
}
/*
输出结果:
0 1 2 3 4 5 6 7 8 9
*/
4. channel 的关闭
ch := make(chan int)
// golang 提供了内置的 close 函数对 channel 进行关闭操作
close(ch)
注意:
- 关闭一个未初始化(nil)的 chennel 会产生 panic;
- 同一个 channel 只能关闭一次,重复关闭会产生 panic;
- 向一个已关闭的 channel 发送消息会产生 panic;
- 从已关闭的 channel 中读取消息永远不会产生 panic,能读出 channel 中还未被读取的消息,若消息均已读出,则会读到类型的默认值,此时还会返回一个为 false 的 ok-idiom(多返回值中用一个名为 ok 的布尔值来标记操作是否成功),可以用来判断 channel 是否关闭;
- 关闭 channel 会产生一个广播机制,所有向 channel 读取消息的 goroutine 都会收到消息。
// 注意的第4点
c := make(chan int, 2)
c <- 7
close(c)
val1, ok1 := <-c
fmt.Println(val1, ok1) // 7 true
val2, ok2 := <-c
fmt.Println(val2, ok2) // 0 false
5. channel 的遍历
对 channel 使用 range 遍历时,会一直从 channel 读取消息,直到有 goroutine 对该 channel 使用 close() 操作,循环才会结束,也可以在遍历前先关闭 channel 。
c := make(chan int, 3)
c <- 0
c <- 1
c <- 2
close(c)
for v := range c {
fmt.Printf("%d ", v) // 0 1 2
}
6. channel 与 select 配合使用
select {
case <- ch1:
...
case <- ch2:
...
case ch3 <- 7:
...
default:
...
}
- select 可以同时监听多个 channel 的读取或写入;
- 执行 select 时,若只有一个 case 通过(不阻塞),则执行这个 case 块;
- 若有多个 case 通过,则随即挑选一个 case 执行;
- 当所有 case 均阻塞时,若定义了 default ,则执行 default 块;若未定义 default ,则 select 语句阻塞,直到有 case 被唤醒;
- 可以使用 break 跳出 select 块。
7. 通过 channel 实现 goroutine 的通信
func main(){
c := make(chan int, 3)
var readc <-chan int = c
var writec chan<- int = c
go SetChan(writec)
GetChan(readc)
}
func SetChan(writec chan<-int){
for i := 0; i < 3; i++ {
writec <- i
}
}
func GetChan(readc <-chan int){
for i := 0; i < 3; i++ {
fmt.Printf("%d ", <-readc)
}
}
/*
输出结果:
0 1 2
*/