【Go专家编程——并发控制——三剑客】

news2024/12/27 15:22:02

并发控制

我们考虑这么一种场景,协程在A执行过程中需要创建子协程A1、A2、A3…An,协程创建完子协程后就等待子协程退出。

针对这种场景,Go提供了三种解决方案:

  • Channel:使用channel控制子协程
    • 优点:实现简单
  • WaitGroup:使用信号量机制控制子协程
    • 优点:子协程个数动态可调整
  • Context:使用上下文控制子协程
    • 优点:方便控制子协程派生出的孙子协程的控制

1. Channel

channel一般用于协程之间的通信,不过channel也可以用于并发控制。比如主协程启动N个子协程,主协程等待所有子协程退出后再继续后续流程。

场景示例:
下面的程序通过创建N个channel来管理N个协程,每个协程都有一个channel用于与父协程通信,父协程创建完所有协程后等待所有协程结束。

package main

import(
	"time"
	"fmt"
)

func Process(ch chan int) {
	// Do some work...
	time.Sleep(time.Second)
	ch <- 1 //在管道中写入一个元素表示当前协程已结束
}

func main(){
	// 创建一个10个元素的切片,元素类型为channel
	channels := make([]chan int,10)

	for i:=0;i<10;i++{
		channels[i] = make(chan int)//在切片中放入一个channel
		go Process(channels[i])//启动协程,传入一个管道用于通信
	}

	for i,ch := range channels{//遍历子协程结束
		<-ch
		fmt.Println("Routine",i," quit!")
	}
}

小结

优点:实现简单
缺点:需要创建与协程数量相等的channel,且对于子孙协程的控制不方便

2.WaitGroup

WaitGroup可以理解为Wait-Goroutine-Group,即等待一组goroutine结束。比如某个goroutine需要等待其他几个goroutine全部完成,那么使用WaitGroup可以轻松实现。

场景示例:

func main(){
	var wg sync.WaitGroup
	
	wg.Add(2)//设置计数器,数值即goroutine的个数
	go func(){
		//Do some work
		time.Sleep(1*time.Second)
		fmt.Println("Goroutine 1 finished!")
		wg.Done() //goroutine 执行结束后将计数器减1
	}()
	go func(){
		//Do some work
		time.Sleep(1*time.Second)
		fmt.Println("Goroutine 2 finished!")
		wg.Done() //goroutine 执行结束后将计数器减1
	}()
	wg.Wait() // 主goroutine阻塞等待计数器变为0
	fmt.Println("All Goroutine finished!")
}

2.1 基础知识

信号量是Unix系统提供的一种共享保护资源的机制,用于防止多个线程同时访问某个资源

  • 当信号量>0时,表示资源可用,获取信号量时系统自动将信号量减1
  • 当信号量==0时,表示资源不可用,获取信号量时,当前线程会进入睡眠,当信号量为正时被唤醒

2.2 WaitGroup

2.2.1 数据结构

type WaitGroup struct{
	state1 [3]uint32
}

state1 是一个长度为3的数组,包含state和一个信号量,而state实际上又分为两个计数器。

  • counter:当前还未执行结束的goroutine的计数器
  • waiter count:等待goroutine-group结束的goroutine数量,即又多少个等候者
  • semaphore:信号量

WaitGroup对外暴露三个方法

  • Add(delta int):将delta值加到counter中
  • Wait():waiter递增1,并阻塞等待信号量semaphore
  • Done():counter递减1,按照waiter数值释放相应次数信号量

2.2.2 Add(delta int)

Add方法做了两件事:

  1. 把delta值累加到counter中,因为delta可以为负值,也就是锁counter有可能变成0或负值
  2. 当counter变为0时,根据waiter数值释放等量信号量,把等待的goroutine全部唤醒,如果counter变为负值,则触发panic

2.2.3 Wait()

Wait方法做了两件事:

  1. 累加waiter
  2. 阻塞等待信号量

使用了CAS算法保证了多个goroutine同时执行Wait方法是也能正确累加waiter

2.2.4 Done()

Done方法只做一件事,即把counter减1,Done方法实际上调用的是Add(-1)。

3.Context

Go语言的context是应用开发常用的并发控制技术,对于派生goroutine有更强的控制力,可以控制多级的goroutine。

3.1 context的接口定义

type Context interface {
	Deadline() (deadline time.Time, ok bool)
	Done() <-chan struct{}
	Err() error
	Value(key interface{}) interface{}
}

基础的context接口只定义了4个方法

  • Deadline:返回一个deadline和标识是否已设置deadline的bool值,如果没有设置deadline,则ok=false,此时deadline为一个初始值的time.Time值
  • Done:该方法返回一个用来探测Context是否取消的channel,当Context取消时会自动将该channel关闭
    • 对于不支持取消的Context,该方法可能会返回nil
  • Err:返回context关闭的原因,若context还未关闭,返回nil
  • Value:
    • 适用于在协程中传递信息
    • 根据key值查询map中的value

3.2 空context

context包中定义了一个空的context,名为emptyCtx,用于context的根节点。
同时定义了一个公用的emptyCtx全局变量,名为background,可以使用context.Background()获取

context包中提供了四个方法创建不同类型的context,使用这四个方法时如果没有父节点context,需要传入background作为其父节点。

  • WithCancel()
  • WithDeadline()
  • WithTimeout()
  • WithValue()

3.3 cancelCtx

type cancelCtx struct{
	Context
	mu	sync.Mutex
	done	chan struct{}//lazily初始化
	//记录由此context派生出的所有child
	//此context被取消时会将其child全部cancel
	children	map[canceler]struct{}
	err	error
}

2.3.1 Done方法的实现

Done方法只需要返回一个channel接即可
源码如下:

func (c *cancelCtx) Done() <-chan struct{}{
	c.mu.Lock()
	if c.done == nil{
		c.done = make(chan struct{})
	}
	d := c.done
	c.mu.Lock()
	return d
}

2.3.2 Err方法的实现

Err()只需要返回一个error告知context被关闭的原理。
cancelCtx.err默认是nil,在context被”cancel“时指定一个error变量:
var Canceled = errors.New("context canceled")

func(c *cancelCtx) Err() error{
	c.mu.Lock()
	err := c.err
	c.mu.Unlock()
	return err
}

2.3.3 cancel方法的实现

cancel方法是理解cancelCtx的关键,其作用是关闭自己和其后代。
伪代码如下:

func(c *cancelCtx) cancel(removeFromParent bool,err error){
	c.mu.Lock()
	
	c.err = err	//设置一个error,说明关闭原因
	close(c.done)	//将channel关闭,依次通知派生的context

	for child := range c.children{//遍历child,调用cancel方法
		child.cancel(false,err)	
	}
	c.children = nil
	c.mu.Unlock()
	if removeFromParent{
		//正常情况下,需要将自己从parent中删除
		removeChild(c.Context, c)
	}
}

2.3.4 WithCancel方法的实现

WithCancel()方法做了三件事

  • 初始化一个cancelCtx实例
  • 将cancelCtx实例添加到其父节点的children中(如果父节点也可以被cancel)
  • 返回cancelCtx实例和cancel方法

其实现源码如下:

func WithCancel(parent Context)(ctx Context,cancel CancelFunc){
	c:= newCancelCtx(parent)
	//将自身添加到支持cancel的祖先节点中,如果都不支持,则启动一个协程等待父节点结束,然后把当前context结束。
	propagateCancel(parent, &c)
	return &c, func(){c.cancel(true, Canceled)}
}

2.3.5 使用案例

func HandelRequest(ctx context.Context) {
	go WriteRedis(ctx)
	go WriteDatabase(ctx)
	for {
		select {
		case <-ctx.Done():
			fmt.Println("HandelRequest Done.")
			return
		default:
			fmt.Println("HandelRequest running")
			time.Sleep(2 * time.Second)
		}
	}
}

func WriteRedis(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("WriteRedis Done.")
			return
		default:
			fmt.Println("WriteRedis running")
			time.Sleep(2 * time.Second)
		}
	}
}

func WriteDatabase(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("WriteDatabase Done.")
			return
		default:
			fmt.Println("WriteDatabase running")
			time.Sleep(2 * time.Second)
		}
	}
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	go HandelRequest(ctx)

	time.Sleep(5 * time.Second)
	fmt.Println("It's time to stop all sub goroutines")
	cancel()
	// Just for test whether sub goroutines exit or not
	time.Sleep(5 * time.Second)
}

main协程创建context,并把context在各个子协程间传递,main协程在适当的时机可以"cancel"所有子协程。
在这里插入图片描述

3.4 timerCtx

源码如下:

type timerCtx struct{
	cancelCtx
	timer *time.Timer
	deadline time.Time
}

timerCtx在cancel的基础上增加了deadline,用于标示自动cancel的最终时间,而timer就上触发自动cancel的定时器。
在此基础上衍生出了WithDeadline()和WithTimeout()

  • deadline:指定最后期限
  • timeout:指定最长存活时间

3.4.1 Deadline方法的实现

该方法仅仅返回timerCtx.deadline而已。

3.4.2 cancel方法的实现

cancel()方法基本继承了cancelCtx,只需要额外把timer关闭
timerCtx被关闭后,timerCtx.cancelCtx.err将存储关闭原因:

  • 如果在deadline到来之前手动关闭,则关闭原因与cancelCtx显示一致
  • 如果是到期自动关闭,原因为context deadline exceeded

3.4.3 WithDeadline方法的实现

  • 初始化一个timerCtx实例
  • 将timerCtx实例添加到其父节点的children中(如果父节点可以被“cancel”)
  • 启动定时器,定时器到期后会自动"cancel"本context
  • 返回timerCtx实例和cancel方法

3.4.4 WithTimeout方法的实现

WithTimeout()实际上调用了WithDeadline,两者的实现原理一致。

func WithTimeout(parent Context,timeout time.Duration)(Context,CancelFunc){
	return WithDeadline(parent,time.Now().Add(timeout))
}

3.4.5 典型使用案例

func HandelRequest(ctx context.Context) {
	go WriteRedis(ctx)
	go WriteDatabase(ctx)
	for {
		select {
		case <-ctx.Done():
			fmt.Println("HandelRequest Done.")
			return
		default:
			fmt.Println("HandelRequest running")
			time.Sleep(2 * time.Second)
		}
	}
}

func WriteRedis(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("WriteRedis Done.")
			return
		default:
			fmt.Println("WriteRedis running")
			time.Sleep(2 * time.Second)
		}
	}
}

func WriteDatabase(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("WriteDatabase Done.")
			return
		default:
			fmt.Println("WriteDatabase running")
			time.Sleep(2 * time.Second)
		}
	}
}

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

	time.Sleep(10 * time.Second)
}

3.5 valueCtx

源码如下:

type valueCtx struct{
	Context
	key,value interface{}
}

valueCtx只是在Context基础上增加了一个key-value对,用于在各级协程间传递一些数据,由于valueCtx不需要cancel,也不需要deadline,那么只需要实现Value接口即可。

3.5.1 Value方法的实现

当前context查询不到key时,会向父节点查找,如果查询不到最终返回interface{}

func(c *valueCtx) Value(key interface{}) interface{}{
	if c.key == key{
		return c.val
	}
	return c.Context.Value(key)
}

3.5.2 WithValue方法的实现

伪代码如下:

func WithValue(parent Context,key,val interface{})Context{
	if key == nil {
		panic("nil key")
	}
	return &valueCtx{parent,key,val}
}

3.5.3 典型使用案例

该案例中main()通过WithValue()方法获得一个context,需要指定一个父context、key和value。contextkey用来在父子协程中传递信息。由于父节点是一个Timeout类型的,当父节点结束后,子节点也会结束。

func HandelRequest(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("HandelRequest Done.")
			return
		default:
			fmt.Println("HandelRequest running,parameter:",ctx.Value("parameter"))
			time.Sleep(2 * time.Second)
		}
	}
}


func main() {
	cancelCtx, _ := context.WithTimeout(context.Background(), 10*time.Second)
	ctx := context.WithValue(cancelCtx, "parameter", "1")
	go HandelRequest(ctx)

	time.Sleep(15 * time.Second)
}

在这里插入图片描述

4. 小结

三种context实例可以互为父节点,从而可以组合成不同的应用形式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1712096.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【稳定检索】2024年核能科学与材料、物理应用国际会议(NESMPA 2024)

2024年核能科学与材料、物理应用国际会议 2024 International Conference on Nuclear Energy Science and Materials, Physical Applications 【1】会议简介 2024年核能科学与材料、物理应用国际会议即将拉开帷幕&#xff0c;这是一场汇聚全球核能科学、材料研究及物理应用领域…

全志T527 适配双目tp2815_mipi

一、硬件信息 TP2815&#xff1a; 确认硬件信息&#xff1a; 1、通信接口&#xff1a;TWI2总线&#xff0c;引脚组为PE1 、PE2 2、RESET脚&#xff1a; 二、软件配置 1、设备树 t527 dtsi: bsp/configs/linux-5.15/sun55iw3p1.dtsi t527 uboot-board.dts device/config/chi…

必看丨SSL证书是什么?怎么免费申请一张?

SSL证书是一种网络安全证书&#xff0c;它能帮助网站实现数据加密传输&#xff0c;保障用户信息在浏览器和网站服务器之间的安全交流。想象一下SSL证书就像一封密信的封蜡&#xff0c;确保信件内容在途中不会被他人偷看或篡改。 SSL证书的作用主要有两点&#xff1a; 1. 身份验…

gin框架精通篇(二)

原生数据库使用 导入模块&#xff1a;go get -u github.com/go-sql-driver/mysql 安装 mysql 数据库 安装数据库可能遇到的问题&#xff1a;&#xff08;网上的方法基本可以解决&#xff09; ERROR 1045 (28000): Access denied for user ‘-root’‘localhost’ (using passwo…

【leetcode2765--最长交替子数组】

要求&#xff1a;给定一个数组&#xff0c;找出符合【x, x1,x,x-1】这样循环的最大交替数组长度。 思路&#xff1a;用两层while循环&#xff0c;第一个while用来找到符合这个循环的开头位置&#xff0c;第二个用来找到该循环的结束位置&#xff0c;并比较一下max进行记录。 …

LLVM技术在GaussDB等数据库中的应用

目录 LLVM和数据库 LLVM适用场景 LLVM对所有类型的SQL都会有收益吗&#xff1f; LLVM在OLTP中就一定没有收益吗&#xff1f; GaussDB中的LLVM 1. LLVM在华为应用于数据库的时间线 2. GaussDB LLVM实现简析 3. GaussDB LLVM支持加速的场景 支持LLVM的表达式&#xff1a…

河南道路与桥梁乙级资质升级门槛条件解读

河南道路与桥梁乙级资质升级门槛条件解读如下&#xff1a; 一、企业基本条件 法人资格&#xff1a; 企业需具备独立企业法人资格&#xff0c;能够独立承担民事责任。注册资金&#xff1a; 企业的注册资金应不少于100万元人民币&#xff0c;这一数字直接体现了企业的经济实力和…

Yann LeCun 和 Elon Musk 就 AI 监管激烈交锋

&#x1f989; AI新闻 &#x1f680; Yann LeCun 和 Elon Musk 就 AI 监管激烈交锋 摘要&#xff1a;昨天&#xff0c;Yann LeCun 和Elon Musk 在社交媒体就人工智能的安全性和监管问题展开激烈辩论。LeCun 认为目前对 AI 的担忧和监管为时过早&#xff0c;主张开放和共享。而…

【linux:基础IO】

目录 系统调用的文件接口&#xff1a; open read: write: lseek: close: 系统调用的文件接口&#xff1a; open 当文件存在时&#xff1a;int open (const char*pathname,int flags)当文件不存在时&#xff1a;int open (const char* pathname,int flags,mode_t mode) 返…

拉格朗日插值法的推导

1、插值的基本定义   设函数 y f ( x ) yf(x) yf(x)在区间 [ a , b ] [a,b] [a,b]上有定义&#xff0c;且已知它在 n 1 n1 n1个互异点 a ≤ x 0 < x 1 < . . . < x n ≤ b a\leq x_0<x_1<...<x_n\leq b a≤x0​<x1​<...<xn​≤b上的函数值 y 0 …

经典文献阅读之--SMERF(通过标清导航地图增强车道感知和拓扑理解)

Tip: 如果你在进行深度学习、自动驾驶、模型推理、微调或AI绘画出图等任务&#xff0c;并且需要GPU资源&#xff0c;可以考虑使用Compshare的GPU算力云平台。他们提供高性价比的4090 GPU&#xff0c;按时收费每卡2.6元&#xff0c;月卡只需要1.7元每小时&#xff0c;并附带200G…

【全开源】西陆家政系统源码小程序(FastAdmin+ThinkPHP+原生微信小程序)

打造高效便捷的家政服务平台 一、引言&#xff1a;家政服务的数字化转型 随着人们生活节奏的加快&#xff0c;家政服务需求日益增长。为了满足广大用户对高效、便捷的家政服务的需求&#xff0c;家政小程序系统源码应运而生。这款源码不仅能够帮助家政服务提供商快速搭建自己…

【VTKExamples::Utilities】第十六期 WindowModifiedEvent

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 公众号:VTK忠粉 前言 本文分享VTK样例WindowModifiedEvent,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 1. WindowModifi…

基于 RNNs 对 IMDB 电影评论进行情感分类

前言 系列专栏:【深度学习:算法项目实战】✨︎ 涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域,讨论了各种复杂的深度神经网络思想,如卷积神经网络、循环神经网络、生成对抗网络、门控循环单元、长短期记…

Sqli-labs-master靶场1-20通关教程

目录 SQL注入基本语句 Less-1&#xff08;字符型-闭合 &#xff09; Less-2&#xff08;数字型&#xff09; Less-3&#xff08;字符型-闭合 ) &#xff09; Less-4&#xff08;字符型-闭合 ") &#xff09; Less-5&#xff08;报错注入-闭合 &#xff09; Less-…

实战教程:使用Go的net/http/fcgi包打造高性能Web应用

实战教程&#xff1a;使用Go的net/http/fcgi包打造高性能Web应用 简介理解FCGI协议FastCGI工作原理CGI与FastCGI对比其他接口对比 初步使用net/http/fcgi包设置和配置基础环境一个简单的FastCGI应用示例本地测试与调试 深入net/http/fcgi包的高级用法探索net/http/fcgi的主要功…

【贪心算法】C++ 解决算法题:买卖股票 / K次取反 / 按身高排序 / 优势洗牌

1. 前言 1.1 贪心算法介绍 贪心算法&#xff08;Greedy Algorithm&#xff09;是一种在每一步选择中都采取当前状态下最优决策的算法。贪心算法通常用来解决最优化问题&#xff0c;其核心思想是通过局部最优解逐步推导出全局最优解。 在贪心算法中&#xff0c;我们并不总是考…

探索电商ERP平台的功能架构:实现高效运营的关键

在当今数字化时代&#xff0c;电子商务已经成为了商业运营的主流模式之一。为了应对日益激烈的市场竞争&#xff0c;企业需要借助先进的技术工具来提高运营效率和管理能力。在这篇博客中&#xff0c;我们将深入探讨电商ERP平台的功能架构&#xff0c;揭示其如何成为实现高效运营…

港科夜闻|香港科大与利丰成立供应链研究院,应对多变商业环境中的挑战与机遇...

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、香港科大与利丰成立供应链研究院&#xff0c;应对多变商业环境中的挑战与机遇。该研究院的成立&#xff0c;旨在推动全球供应链管理的研究与创新商业模式。研究院结合香港科大卓越研究实力及利丰于供应链管理方面的深厚行…

Dify快速接入微信

一、Dify简介 项目官网&#xff1a;Dify.AI 生成式 AI 应用创新引擎 Dify 是一款开源的大语言模型(LLM) 应用开发平台。它融合了后端即服务&#xff08;Backend as Service&#xff09;和 LLMOps 的理念&#xff0c;使开发者可以快速搭建生产级的生成式 AI 应用。即使你是非…