goroutine
并发
- goroutine并非执行并发操作,而是创建一个并发任务单元。新建任务被放置在系统队列中,等待调度器安排合适系统线程去获取执行。当前流程不会阻塞,不会等待任务启动,且运行不保证并发执行次序。
- 每个任务单元除了保存函数指针、调用参数外、还会分配所需栈空间
- 系统线程默认MB,goroutine自定义栈初始化仅2KB,才能创建成千上万并发任务
GMP
- G:goroutine用户态协程
- M:操作系统线程
- P:调度器,与M一一对应,调度goroutine
chan
- 底层实现,通道是一个队列
- chan不用用共享内存通信,让通信共享内存
- 允许全局变量、指针、引用类型非安全内存共享操作,需自行维护数据一致性和完整性
- 同步模式,无缓存chan,发送和接收方配对后传送数据,接收方先准备好,否则死锁
- 异步模式,有缓存chan,在缓冲区未满或数据未读完前,不会阻塞
对于close或nil通道,发送和接收操作相应规则
- 向已关闭通道发送数据,引发panic
- 从已关闭接收数据,返回已缓冲数据或零值
- 无论收发,nil通道都会阻塞
goroutine应用
参数是立即复制执行
func goArgs() {
a := 100
go func(x, y int) {
time.Sleep(time.Second)
fmt.Println("go:", x, y) // go: 100, 1
}(a, counter())
a += 100
fmt.Println("main:", a, counter()) // main: 200, 2
time.Sleep(3 * time.Second)
}
单个并发,可用chan,阻塞控制
func goChanExit() {
exit := make(chan struct{})
go func() {
time.Sleep(time.Second)
fmt.Println("goroutine done") // 2 goroutine done
close(exit) // 关闭通道发出信号
}()
fmt.Println("main...") // 1 main...
<-exit
fmt.Println("main exit") // 3 main exit
}
多个并发,可用sync.WaitGroup
func goWaitGroup() {
// GMP中M线程默认与处理器核数相等
n := runtime.GOMAXPROCS(4) // 修改运行线程数量
var wg sync.WaitGroup
for i := 0; i < n; i++ {
wg.Add(1) // 在gorutine外增加计数器
go func(id int) {
defer wg.Done()
time.Sleep(time.Second)
fmt.Println("goroutine", id, "done") // 无序输出
}(i)
}
fmt.Println("main...")
wg.Wait()
fmt.Println("main exit.")
}
chan应用
同步模式,无缓冲chan,发送数据或信号
func chanNotify() {
done := make(chan struct{}) // 信号
c := make(chan string)
go func() {
s := <-c // 接收数据
fmt.Println(s)
close(done) // 关闭
}()
c <- "hi" // 发送数据
<-done // 阻塞,等待关闭
}
异步模式,有缓存chan
func channelBuffer() {
var c chan int // chan的零值为nil
fmt.Println(c == nil) // true
ping := make(chan string, 2) // chan为指针
pong := make(chan string, 2)
fmt.Println(ping == pong) // false
fmt.Printf("%p,%d\n", pong, unsafe.Sizeof(pong)) // 0xc000056180,8
ping <- "buffered"
ping <- "channel"
// ping <- "hi" 缓存区满阻塞 deadlock
// len返回当前已缓存数量,cap返回缓冲区大小
fmt.Println("a:", len(c), cap(c)) // a: 0,0
fmt.Println("ping:", len(ping), cap(ping)) // ping: 2,2
fmt.Println(<-ping) // buffered
fmt.Println(<-ping) // channel
}
判断chan存在及返回值
func chanOkIdom() {
done := make(chan struct{})
c := make(chan int)
go func() {
defer close(done)
for {
s, ok := <-c // 判断通道是否关闭
if !ok { // 不存在s为类型零值
return
}
fmt.Println(s)
}
}()
c <- 1
c <- 2
c <- 3
close(c)
<-done
}
遍历chan
func chanRange() {
done := make(chan struct{})
c := make(chan int)
go func() {
defer close(done)
for s := range c { // 循环获取消息,直到通道关闭,for循环退出
fmt.Println(s)
}
}()
c <- 1
c <- 2
c <- 3
close(c)
<-done
}
多个chan用select,随机选择一个通道操作
func chanSelect() {
tick := time.Tick(10 * time.Millisecond)
boom := time.After(4 * time.Millisecond)
// default
// BOOM!
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
// break只能跳出select,无法跳出for,用return
// 跳出select后面都不执行,退出函数
return
default:
fmt.Println("default")
time.Sleep(6 * time.Millisecond) // 避免打印大量default
}
}
}