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

相关推荐
梦想很大很大4 小时前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
lekami_兰9 小时前
MySQL 长事务:藏在业务里的性能 “隐形杀手”
数据库·mysql·go·长事务
却尘13 小时前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤13 小时前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt111 天前
AI DDD重构实践
go
Grassto3 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto4 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室5 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题5 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo
啊汉7 天前
古文观芷App搜索方案深度解析:打造极致性能的古文搜索引擎
go·软件随想