详解Golang并发控制的三种方案

 更新时间:2024年06月19日 09:12:00   作者:拔剑纵狂歌  
本文主要介绍了详解Golang并发控制的三种方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

Channel

Channel是Go在语言层面提供的一种协程间的通信方式,我们可以通过在协程中向管道写入数据和在待等待的协程中读取对应协程的次数来实现并发控制。

func main() {
    intChan := make(chan int, 5)
    waitCount := 5
    for i := 0; i < waitCount; i++ {
       go func() {
          intChan <- 1
       }()
    }

    for i := 0; i < waitCount; i++ {
       <-intChan

    }
    fmt.Println("主进程结束")
}

WaitGroup

waitgroup通常应用于等待一组“工作协程”结束的场景,waitgroup底层是由一个长度为3的数组实现的,其内部有两个计数器,一个是工作协程计数器、一个是坐等协程计数器,还有一个是信号量。工作协程全部运行结束后,工作协程计数器将置为0,会释放对应坐等协程次数的信号量。

两点注意:

  • Add()方法中的参数大小要于工作协程的数量相等,否则会导致坐等协程一直等待,触发死锁panic

  • Done()方法执行的次数要与Add()方法中的工作协程计数器的数量一致,否则当工作协程计数器<0时,会触发panic【panic: sync: negative WaitGroup counter】

func main() {
    wg := sync.WaitGroup{}
    wg.Add(2)
    go func() {
       time.Sleep(3 * time.Second)
       fmt.Println("等待三分钟的协程结束了")
       wg.Done()
    }()

    go func() {
       time.Sleep(3 * time.Second)
       fmt.Println("等待三分钟的协程结束了")
       wg.Done()
    }()

    wg.Wait()
}

Context

适用于一个协程派生出多个协程的情况,可以控制多级的goroutine。我们可以通过一个Context对象,对派生出来的树状goroutine进行统一管理,并且每个goroutine具有相同的上下文。做统一关闭操作、统一定时关闭、统一传值的操作。多个上下文协程之间可以互相嵌套配合。

golang实现了四种原生的上下文对象

  • emptyCtx: 该上下文对象一般是作为父节点的,如果没有父节点,我们通常使用context.Background()方法来获取emptyCtx对象,并将其作为创建其他节点的父节点。

  • cancelCtx: 该上下文对象可以关闭所有拥有同一个上下文的goroutine,通过在子协程中监听cancelCtx.Done方法,来结束所有的派生协程。具体代码看下方,我们通过WithCancel()方法来获取该对象。

  • timerCtx:该上下文对象是对cancelCtx对象的进一步封装,比cancelCtx主动关闭之外,多了了一个定时关闭功能。我们可以通过WithTimeout()和WithDeadline()这两种方法来获取该对象。其中WithTimeout()和WithDeadline()这两种方法点是WithTimeout()是设置过一段时间关闭上下文,WithDeadline()是设置那一个时间点来关闭这一个上下文。

  • valueCtx:该上下文对象并不用于进行协程的控制,而是在多级协程之间进行值得传递,方便共享一些相同得上下文内容。

以上除emptyCtx外的上下文对象和获取实例的方法如下图所示:

 Context示例代码

cancelCtx

我们在所有的派生协程中传入相同的cancelContext对象,并在每一个子协程中使用switch-case结构监听上下文对象是否关闭,如果上下文对象关闭了,ctx.Done()返回的管道就可以读取到一个元素,使所在的case语句可执行,之后退出switch结构,执行协程中的其他代码。

func main() {
	ctx, cancelFunc := context.WithCancel(context.Background())
	deadline, ok := ctx.Deadline()
	fmt.Println(deadline, ok)
	done := ctx.Done()
	fmt.Println(reflect.TypeOf(done))
	fmt.Println(done)

	go HandelRequest(ctx)
	//<-done 阻塞当前一层的goroutine
	time.Sleep(5 * time.Second)
	fmt.Println("all goroutines is stopping!")
	cancelFunc()
	err := ctx.Err()
	fmt.Println(err) //context canceled
	time.Sleep(5 * time.Second)
}

func HandelRequest(ctx context.Context) {
	go WriteMysql(ctx)
	go WriteRedis(ctx)
	for {
		select {
		case <-ctx.Done():
			fmt.Println("HandelRequest Done")
			return
		default:
			fmt.Println("等一等,Handler正在执行中")
			time.Sleep(2 * time.Second)
		}
	}
}

func WriteRedis(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("WriteRedis Done.")
			return
		default:
			fmt.Println("等一等,Redis正在执行中")
			time.Sleep(2 * time.Second)
		}
	}
}

func WriteMysql(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("WriteMysql Done.")
			return
		default:
			fmt.Println("等一等,Mysql正在执行中")
			time.Sleep(2 * time.Second)
		}
	}
}

timerCtx

这里代码以WithTimeout举例,相比与我们之前的手动调用关闭,使用timerCtx定时上下文对象后,可以是实现到达指定的时间自动进行关闭的操作。

func main() {
	deadline, _ := context.WithTimeout(context.Background(), 5*time.Second)
	go HandelRequest(deadline)

	time.Sleep(10 * time.Second)

}

func HandelRequest(ctx context.Context) {
	go WriteMysql(ctx)
	go WriteRedis(ctx)
	for {
		select {
		case <-ctx.Done():
			fmt.Println("HandelRequest Done")
			return
		default:
			fmt.Println("等一等,Handler正在执行中")
			time.Sleep(2 * time.Second)
		}
	}
}

func WriteRedis(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("WriteRedis Done.")
			return
		default:
			fmt.Println("等一等,Redis正在执行中")
			time.Sleep(2 * time.Second)
		}
	}
}

func WriteMysql(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("WriteMysql Done.")
			return
		default:
			fmt.Println("等一等,Mysql正在执行中")
			time.Sleep(2 * time.Second)
		}
	}
}

valueCtx

我们可以通过嵌套WithValue上下文,来进行多个key-value在派生协程中传递,共享常量

func main() {
	ctx, cancelFunc := context.WithCancel(context.Background())
	value1 := context.WithValue(ctx, "param1", 1)
	value2 := context.WithValue(value1, "param2", 2)
	go ReadContextValue(value2)

	time.Sleep(10 * time.Second)
	cancelFunc()
	time.Sleep(5 * time.Second)
}

func ReadContextValue(ctx context.Context) {
	fmt.Println(ctx.Value("param1"))
	fmt.Println(ctx.Value("param2"))
}

到此这篇关于详解Golang并发控制的三种方案的文章就介绍到这了,更多相关Golang并发控制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解Golang中哪些类型可以作为map的key

    详解Golang中哪些类型可以作为map的key

    在 Go 语言中,map 是一种内置的关联数据结构类型,由一组无序的键值对组成,每个键都是唯一的,并与一个对应的值相关联,本文将详细介绍哪些类型的变量可以作为 map 的键,并通过实例进行说明,感兴趣的朋友可以参考下
    2024-01-01
  • Golang守护进程用法示例分析

    Golang守护进程用法示例分析

    这篇文章主要介绍了Golang守护进程用法示例,创建守护进程首先要了解go语言如何实现创建进程,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习吧
    2023-05-05
  • Go语言实现请求频率限制的方法实践

    Go语言实现请求频率限制的方法实践

    本文介绍了Go语言中四种主流的请求限流方案,包括计数器法、Redis滑动窗口、令牌桶算法和使用成熟中间件,每种方案都有优缺点,适用于不同的场景,下面就来详细的介绍一下,感兴趣的可以了解一下
    2026-04-04
  • Golang中Json的序列化和反序列化的使用

    Golang中Json的序列化和反序列化的使用

    本文主要介绍了Golang中Json的序列化和反序列化的使用,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04
  • Golang结合chromedp实现浏览器自动化操作

    Golang结合chromedp实现浏览器自动化操作

    当大家提到浏览器自动化,第一反应往往是 Python + Selenium, 但其实 Go 语言也能做浏览器自动化,下面我们就来看看Golang如何结合chromedp实现自动化操作网页吧
    2025-12-12
  • 浅谈Golang中创建一个简单的服务器的方法

    浅谈Golang中创建一个简单的服务器的方法

    这篇文章主要介绍了浅谈Golang中创建一个简单的服务器的方法,golang中的net/http包对网络的支持非常好,这样会让我们比较容易的建立起一个相对简单的服务器,有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-06-06
  • Go 库性能分析工具pprof

    Go 库性能分析工具pprof

    这篇文章主要为大家介绍了Go 库性能分析工具pprof,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • Go语言编程入门超级指南

    Go语言编程入门超级指南

    这篇文章主要介绍了Go语言编程的入门指南,包括对Go的变量及函数的基本介绍,需要的朋友可以参考下
    2016-01-01
  • Go语言模型:string的底层数据结构与高效操作详解

    Go语言模型:string的底层数据结构与高效操作详解

    这篇文章主要介绍了Go语言模型:string的底层数据结构与高效操作详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Go语言中Serverless实战

    Go语言中Serverless实战

    本文主要介绍了Go语言中Serverless实战,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-03-03

最新评论