目录
一、goroutine
1. 创建 goroutine
(1)格式
(2)示例
2. 协程管理
二、channel
1. channel 的创建
2. channel 的类型
3. channel 的读写操作
4. channel 的关闭
5. channel 的遍历
6. channel 与 select 配合使用
7. 通过 channel 实现 goroutine 的通信
goroutine 是 Go 语言中的轻量级线程实现,由 Go 运行时(runtime)管理。Go 程序会智能地将 goroutine 中的任务合理地分配给每个 CPU 。
Go 程序从 main 包的 main() 函数开始,在程序启动时,Go 程序就会为 main() 函数创建一个默认的 goroutine;所有 goroutine 在 main() 函数结束时会一同结束。
Go 程序中使用 go 关键字为一个函数创建一个 goroutine 。一个函数可以被创建多个 goroutine,一个 goroutine 必定对应一个函数。
为一个函数创建 goroutine 的写法如下:
go 函数名(参数列表)
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 程序的调度机制同时运作。
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 是 goroutine 之间通信的一种方式,其本身还需关联一个类型,也就是 channel 可以发送数据的类型(例如:发送 int 类型消息的 channel 写作 chan int )。
// 声明一个 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 。
(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
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
*/
ch := make(chan int)
// golang 提供了内置的 close 函数对 channel 进行关闭操作
close(ch)
注意:
// 注意的第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
对 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
}
select {
case <- ch1:
...
case <- ch2:
...
case ch3 <- 7:
...
default:
...
}
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
*/