主协程执行打印,子协程不打印
go
package main
import (
"fmt"
)
func do(i int) {
fmt.Println("执行中")
}
func main() {
fmt.Println("main协程")
go do(1)
fmt.Println("执行完了")
}
//main协程
//执行完了
子协程没有打印输出
原因:主和子协程各执行各的,当主协程执行完go语句就退出,并不会等待子协程执行完成
解决办法三个:
sync. WaitGroup的三个方法 Add(), Done(), Wait()
go
package main
import (
"fmt"
"sync"
)
func do(i int) {
fmt.Println("do执行中", i)
}
func main() {
var wg sync.WaitGroup
fmt.Println("main协程")
wg.Add(1)
go func(i int) {
defer wg.Done()
do(i)
}(1)
wg.Wait()
fmt.Println("执行完了")
}
//main协程
//do执行中 1
//执行完了
channel来控制协程
go
package main
import (
"fmt"
)
func do(i int) {
fmt.Println("do执行中", i)
}
func main() {
ch := make(chan bool, 1)
fmt.Println("main协程")
go func(i int, chp chan<- bool) {
defer close(chp)
A(i)
fmt.Println("do执行完")
chp <- true
}(1, ch)
fmt.Println("wait")
<-ch
fmt.Println("main执行完了")
}
//main协程
//wait
//do执行中 1
//do执行完
//main执行完了
通过sync. Cond来实现
go
package main
import (
"fmt"
"sync"
)
func do(i int) {
fmt.Println("do执行中", i)
}
func main() {
var locker = new(sync.Mutex)
var cond = sync.NewCond(locker)
var done bool = false
fmt.Println("main")
cond.L.Lock()
go func(i int) {
do(i)
fmt.Println("do完成")
done = true
cond.Signal()
}(1)
fmt.Println("wait")
if !done {
cond.Wait()
cond.L.Unlock()
}
fmt.Println("main完成")
}
//main
//do执行中
//do完成
//main完成
会出现的问题:协程崩溃,主协程也会停止
go可以通过recover来捕获panic,类似try catch的作用
注意
- recover如果想起作用的话, 必须在defered函数前声明,因为只要panic,后面的函数不会被执行
- recover函数只有在方法内部发生panic时,返回值才不会为nil,没有panic的情况下返回值为nil
go
package main
import (
"fmt"
"sync"
)
func do(i int) {
fmt.Println("do执行中", i)
panic("崩溃")
defer func() { //在panic后声明defer,不能捕获异常
if err := recover(); err != nil {
fmt.Println("恢复", err)
}
}()
}
func main() {
var wg sync.WaitGroup
fmt.Println("main协程")
wg.Add(1)
go func(i int) {
defer wg.Done()
A(i)
}(1)
wg.Wait()
fmt.Println("main执行完了")
}
main协程
do执行中 1
main执行完了
panic: 崩溃
//goroutine 6 [running]:
//main.do(0x0?)
// /Volumes/disk/site/go/demo/gfqa/main.go:11 +0x88
//main.main.func1(0x0?)
// /Volumes/disk/site/go/demo/gfqa/main.go:28 +0x4c
//created by main.main
// /Volumes/disk/site/go/demo/gfqa/main.go:26 +0x100
//进程 已完成,退出代码为 2
改进:panic前声明recover
go
package main
import (
"fmt"
"sync"
)
func do(i int) {
fmt.Println("do执行中", i)
defer func() { //在panic后声明defer,不能捕获异常
if err := recover(); err != nil {
fmt.Println("恢复", err)
}
}()
panic("崩溃")
}
func main() {
var wg sync.WaitGroup
fmt.Println("main协程")
wg.Add(1)
go func(i int) {
defer wg.Done()
A(i)
}(1)
wg.Wait()
fmt.Println("main执行完了")
}
//do执行中 1
//恢复 崩溃
//main执行完了
其他写法
go
func do(i int) {
fmt.Println("do执行中", i)
panic("崩溃")
}
func main() {
var wg sync.WaitGroup
fmt.Println("main协程")
wg.Add(1)
go func(i int) {
defer func() {
if err := recover(); err != nil {
fmt.Println("恢复", err)
}
wg.Done()
}()
do(i)
}(1)
wg.Wait()
fmt.Println("main执行完了")
}
总结:如果在协程内执行其它函数时,为了保证不崩溃,安全的做法是,提前声明defer recover函数
协程超时控制
用select + channel来进行超时控制
超时 : ctx. Done()或者time. After()或者time. Ticket()
select捕获到其中一个channel有数据,就执行对应的代码,然后退出
注意:channel要用有缓冲的,不然,在超时分支退出时,协程还在卡住,造成goroutine泄露
go
func do(ctx context.Context, wg *sync.WaitGroup) {
ctx, cancle := context.WithTimeout(ctx, time.Second*2)
defer func() {
cancle()
wg.Done()
}()
done := make(chan struct{}, 1) //执行成功的channel
go func(ctx context.Context) {
fmt.Println("go goroutine")
time.Sleep(time.Second * 10)
done <- struct{}{} //发送完成的信号
}(ctx)
select {
case <-ctx.Done(): //超时
fmt.Printf("timeout,err:%v\n", ctx.Err())
case <-time.After(3 * time.Second): //超时
fmt.Printf("after 1 sec.")
case <-done: //程序正常结束
fmt.Println("done")
}
}
func main() {
fmt.Println("main协程")
ctx := context.Background()
var wg sync.WaitGroup
wg.Add(1)
do(ctx, &wg)
wg.Wait()
fmt.Println("main执行完成")
}
//main协程
//go goroutine
//timeout,err:context deadline exceeded
//main执行完成
总结:runtime. GOMAXPROCS()可以控制当前程序运行时占用的系统核心数,
一个协程不对应一个os线程,是一个m:n的对应关系,协程对应的os线程runtime. GOMAXPROCS默认为系统逻辑cpu数量