Go语言协程使用

主协程执行打印,子协程不打印

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数量

相关推荐
下次一定x3 小时前
深度解析 Kratos 客户端服务发现与负载均衡:从 Dial 入口到 gRPC 全链路落地(下篇)
后端·go
乐茵lin10 小时前
大厂都在问:如何解决map的并发安全问题?三种方法让你对答如流
开发语言·go·编程·map·并发安全·底层源码·sync.map
不会写DN1 天前
GORM 实战入门:从环境搭建到企业级常用特性全解析
sql·mysql·go·gin
F1FJJ1 天前
Shield CLI 的 PostgreSQL 插件 v0.5.0 发布:数据库导出 + 协作增强,ER 图全新体验
网络·数据库·docker·postgresql·go
liangbm33 天前
AI-ViewNote:把网课和会议视频自动卷成结构化笔记
ai·typescript·go·软件构建·开源软件·react·桌面软件
我叫黑大帅3 天前
Gin 实战入门:从环境搭建到企业级常用特性全解析
后端·面试·go
我叫黑大帅3 天前
Gin 日志体系详解
后端·面试·go
F1FJJ3 天前
Shield CLI v0.3.3 新增 PostgreSQL 插件:浏览器里管理 PG 数据库
网络·网络协议·docker·postgresql·容器·go
mCell4 天前
【万字长文】从 AI SDK 到 mini-opencode:一次很巧的 Go Agent 架构实践
架构·go·agent
jump_jump4 天前
深入理解 Go Context:从原理到实战(基于 Go 1.26)
go·源码