首页 > 其他分享 >GoLang 使用 goroutine 停止的几种办法

GoLang 使用 goroutine 停止的几种办法

时间:2023-05-12 20:32:13浏览次数:34  
标签:goroutine sync 几种 GoLang select func Go channel

[toc]

前言

我们有很多情况下需要主动关闭goroutine,如需要实现一个系统自动熔断的功能就需要主动关闭goroutine

为什么要中断GoRoutine?

场景:

俩个相互依赖的的操作,“依赖”是指如果其中一个失败,那么另一个就没有意义,而不是第二个操作依赖第一个操作的结果(那种情况下,两个操作不能并行)。在这种情况下,如果我们很早就知道其中一个操作失败,那么我们就会希望能取消所有相关的操作。

goroutine介绍

goroutine是Go语言实现并发编程的利器,是 Go语言中的轻量级线程实现,由 Go 运行时(runtime)管理,简单的一个指令go function就能启动一个goroutine;Go 程序会智能地将 goroutine 中的任务合理地分配给每个 CPU。

但是,Go语言并没有提供终止goroutine的接口,也就是说,我们不能从外部去停止一个goroutine,只能由goroutine内部退出(main函数终止除外);

几种停止的办法

1. 使用 for-range

for-range 从 channel 上接收值,直到 channel 关闭,该结构在Go并发编程中很常用,这对于从单一通道上获取数据去执行某些任务是十分方便的

package main

import (
	"fmt"
	"sync"
)


var wg sync.WaitGroup //等待组,用来阻塞程序

func worker(ch chan int) {
	defer wg.Done() //等待组 -1
	for v := range ch {
		fmt.Println(v)
	}
}

func main() {

	ch := make(chan int)
	wg.Add(1) //等待组 +1
	go worker(ch)

	for i := 0; i < 5; i++ {
		ch <- i
	}

	close(ch) //必须要加close,因为在打印完0、1、2、3、4后会发生阻塞,直到chan关闭。
	wg.Wait()
}

GoLang 使用 goroutine 停止的几种办法_Go

去掉close的情况

GoLang 使用 goroutine 停止的几种办法_Group_02


2. 使用 for-select (向退出通道发出退出信号)

当channel比较多时,for-range结构借不是很方便了;

Go语言提供了另外一种和channel相关的语法: select;

select能够让goroutine在多个通信操作上等待(可以理解为监听多个channel);

由于这个特性,for-select结构在Go并发编程中使用的频率很高;

我在使用Go的开发中,这是我用的最多的一种组合形式:

这里用 quit通道接收退出信号。

package main

import (
	"fmt"
	"sync"
	"time"
)

var wg sync.WaitGroup

func worker(in, quit <-chan int) {
	defer wg.Done()
	for {
		select {
		case <-quit:
			fmt.Println("收到退出信号")
			return //必须return,否则goroutine不会结束
		case v := <-in:
			fmt.Println(v)
		}
	}
}

func main() {
	quit := make(chan int) //退出通道
	in := make(chan int)

	wg.Add(1)
	go worker(in, quit)

	for i := 0; i < 3; i++ {
		in <- i
		time.Sleep(1 * time.Second)
	}

	quit <- 1 //想通道写入退出信号
	wg.Wait()
}

GoLang 使用 goroutine 停止的几种办法_后端_03


3. 使用for-select(关闭退出通道)

当我们就需要向 quit 通道中发送100次数据,如果再用以上的代码就很麻烦,有一个很简单的方法,关闭 channel,这样所有监听 quit channel 的 goroutine 就都会收到关闭信号。

package main



import (
	"fmt"
	"sync"
	"time"
)

var wg sync.WaitGroup

func worker(in, quit <-chan int) {
	defer wg.Done()
	for {
		select {
		case <-quit:
			fmt.Println("收到退出信号")
			return //必须return,否则goroutine不会结束
		case v := <-in:
			fmt.Println(v)
		}
	}
}

func main() {
	quit := make(chan int) //退出通道
	in := make(chan int)

	wg.Add(1)
	go worker(in, quit)

	for i := 0; i < 3; i++ {
		in <- i
		time.Sleep(1 * time.Second)
	}

	// quit <- 1 //想通道写入退出信号
	close(quit) // 直接关闭通道,程序退出
	wg.Wait()
}

GoLang 使用 goroutine 停止的几种办法_Go_04


4. 使用for-select(关闭多个channel)

如果select上监听了多个通道,需要所有的通道都关闭后才能结束goroutine,这里就利用select的一个特性,select不会在nil的通道上进行等待,因此将channel赋值为nil即可,此外,还需要利用channel的ok值。

package main


import (
	"fmt"
	"sync"
	"time"
)

var wg sync.WaitGroup

func worker(in1, in2 <-chan int) {
	defer wg.Done()

	for {
		select {
		case v, ok := <-in1:
			if !ok {
				fmt.Println("收到退出信号1")
				in1 = nil
			}
			fmt.Println(v)
		case v, ok := <-in2:
			if !ok {
				fmt.Println("收到退出信号2")
				in2 = nil
			}
			fmt.Println(v)
		}
		if in1 == nil && in2 == nil {
			return
		}
	}
}

func main() {
	in1 := make(chan int)
	in2 := make(chan int)
	wg.Add(2)
	go worker(in1, in2)
	go worker(in1, in2)
	for i := 0; i < 3; i++ {
		in1 <- i
		time.Sleep(1 * time.Second)
		in2 <- i
	}
	close(in1)
	close(in2)
	wg.Wait()
}

GoLang 使用 goroutine 停止的几种办法_后端_05


5. 使用context包

context包是官方提供的一个用于控制多个goroutine写作的包;

使用context的cancel信号,可以终止goroutine的运行,context是可以向下传递的

package main



import (
	"context"
	"errors"
	"fmt"
	"time"
)

func operation1(ctx context.Context) error {
	// 让我们假设这个操作会因为某种原因失败
	// 我们使用time.Sleep来模拟一个资源密集型操作
	time.Sleep(100 * time.Millisecond)
	return errors.New("failed")
}

func operation2(ctx context.Context) {
	// 我们使用在前面HTTP服务器例子里使用过的类似模式
	select {
	case <-time.After(500 * time.Millisecond):
		fmt.Println("done")
	case <-ctx.Done():
		fmt.Println("halted operation2")
	}
}

func main() {
	// 新建一个上下文
	ctx := context.Background()
	// 在初始上下文的基础上创建一个有取消功能的上下文
	ctx, cancel := context.WithCancel(ctx) //需要取消时,就调用cancel(),发出取消事件。
	// 在不同的goroutine中运行operation2
	go func() {
		operation2(ctx)
	}()

	err := operation1(ctx)
	fmt.Println(err)
	// 如果这个操作返回错误,取消所有使用相同上下文的操作
	if err != nil {
		cancel()
	}
}

// func main() {
// 	// 创建一个监听8000端口的服务器
// 	http.ListenAndServe(":8000", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 		ctx := r.Context()
// 		// 输出到 STDOUT 展示处理已经开始
// 		fmt.Fprint(os.Stdout, "processing request\n")
// 		// 通过select监听多个channel
// 		select {
// 		case <-time.After(2 * time.Second):
// 			// 如果两秒后接受到了一个消息后,意味请求已经处理完成
// 			// 我们写入"request processed"作为响应
// 			w.Write([]byte("request processed"))
// 		case <-ctx.Done():

// 			// 如果处理完成前取消了,在STDERR中记录请求被取消的消息
// 			fmt.Fprint(os.Stderr, "request cancelled\n")
// 		}
// 	}))
// }

标签:goroutine,sync,几种,GoLang,select,func,Go,channel
From: https://blog.51cto.com/wangshiyu/6271813

相关文章

  • 第八节:导入css的几种方案剖析
    一.        二.        三.         !作       者:Yaopengfei(姚鹏飞)博客地址:http://www.cnblogs.com/yaopengfei/声     明1:如有错误,欢迎讨论,请勿谩骂^_^。声     明2:原创博客请在转载......
  • 服务器上的证书有哪几种格式 答案来自chatGPT
    在Linux服务器上常见的证书格式有以下几种:PEM格式:PEM(PrivacyEnhancedMail)是基于Base64编码的密钥证书文件格式,主要用于OpenSSL的证书以及各种Web服务器,如Nginx、Apache等。PEM格式的文件一般以.pem或.crt为后缀名。DER格式:DER(DistinguishedEncodingRules)是二进制格式的密钥......
  • golang web页面动态加载实现
            Go的web页面动态加载实现。  1.在MySQL中添加表项users,构造多条数据。CREATETABLEIFNOTEXISTSusers(idINTUNSIGNEDAUTO_INCREMENT,usernameVARCHAR(255)NOTNULL,passwordVA......
  • 使用golang编写支持C++调用的动态库,接口支持结构体和回调函数
    网上有很多例子介绍如何使用cgo实现C/C++与golang进行接口交互。我有个项目是使用Qt写的客户端程序,但Qt在需要使用redis、支持表单的web服务、mq或网络化日志库等需求时,往往需要加载一大堆第三方库,且编译复杂,跨平台(如Windows/linuxarm/linuxx86)编译时较为复杂。鉴于有使用go......
  • Golang调用Dll案例
    Golang调用Dll案例前言在家办公已经两个多星期了,目前最大的困难就是网络很差。独自一个人用golang开发调用dll的驱动程序。本来就是半桶水的我,还在为等待打开一个页面而磨平了耐心。本想依葫芦画瓢把这个驱动做了。可网上找到的案例都是一些简单的调用dll。对于各种传参、获取......
  • golang调用dll,windows
    使用syscall.LoadLibrary(dllPath)函数加载dll,syscall.Syscall(...)函数调用具体的函数接口funcGoCallDll1(a,bint)uintptr{ dllFile:=syscall.NewLazyDLL(dllFileName) fmt.Println("dll:",dllFile.Name) add:=dllFile.NewProc("add") fmt.Println("......
  • c# 调用webapi的几种方式
     HttpHelper帮助类publicstaticclassHttphelper{//Post请求publicstaticstringPostResponse(stringurl,stringpostData,outstringstatusCode){stringresult=string.Empty;//设置Http的正文......
  • Golang for循环遍历小坑
    一、for循环循环:让程序多次执行相同的代码块for循环是Go语言中唯一一个循环结构for循环经典语法先执行表达式1执行表达式2判断是否成立,如果成立执行循环体循环体执行完成后,执行表达式3再次执行表达式2,判断是否成立.for循环用的最多的地方就是遍历数组或切片等for表达式1......
  • 【mysql】类似replace 存在更新,否则插入的几种方式
    我们在向数据库里插入数据的时候,会遇到要将原有主键或者unique索引所在记录更新的情况,而如果没有主键或者unique索引冲突的时候,直接执行插入操作。这种情况下,有三种方式执行:1.直接直接每条select,判断,然后insert,毫无疑问,这是最笨的方法了,不断的查询判断,有主键或索引冲突,执......
  • Go语言(Golang)数据库编程
    Go数据库编程一、连接数据库准备连接到数据库要想连接到SQL数据库,首先需要加载目标数据库的驱动,驱动里面包含着于该数据库交互的逻辑。sql.Open()数据库驱动的名称数据源名称得到一个指向sql.DB这个struct的指针sql.DB是用来操作数据库的,它代表了0个或者多个......