Go语言中的并发

news2024/9/22 11:37:06

简单介绍go中的并发编程. 涉及内容主要为goroutine, goroutine间的通信(主要是channel), 并发控制(等待、退出).

想查看更多与Go相关的内容, 可以查看我的Go编程栏目

Goroutine

语法

在一个函数调用前加上go即可, go func(). 语法很简单, 可以说是并发写起来最简单的程序语言了.

goroutine与线程

开始可能会把goroutine当做线程来看, 在我们这的计算密集型任务中, 确实可以认为和线程差不多, 但在I/O比较多的任务中, 就能看到作为协程的一面了. 在go中, goroutine数与线程数可以是m对n的关系, 即m个goroutine运行在n个线程上, 可以认为一个线程能调度执行多个goroutine, 线程内部调度goroutine比线程间的切换调度开销小很多, 这也是协程的优势. 和python那样的协程比起来, goroutine除了能通过阻塞、系统调用让出线程之外, 还能被调度(抢占式调度), 避免一些goroutine执行时间过长, 导致其他goroutine饥饿.

G-M-P模型动态演示

请添加图片描述

不过在计算密集型的任务中协程并没有什么优势, 要计算的任务量是固定的, 过多的协程调度反而降低效率. 所以在我们这写代码的时候, 一般是把goroutine当作线程来用的, 根据cpu核数来创建goroutine, 这要根据具体的任务类型来考虑.

通信

从创建goroutine的语法可以看到, 并没有一个对应函数返回值的方法. 如果想在创建goroutine的协程中获取返回值需要进行goroutine间的通信, 常用的为channel, 和基于共享变量的通信. 通信的用途很广.

闭包

一个函数和其词法环境的引用绑定在一起, 是一个闭包.

func closure() func() int {
	tmp := 1
	return func() int {
		tmp++
		return tmp
	}
}

func main() {
	test1 := closure()
	test2 := closure()
	test1()  // 2
	test1()  // 3
	test2()  // 2
}

其中tmp本来是closure函数中的一个局部变量, 但是closure的返回值是一个闭包函数, 其中引用了tmp, 那tmp就不能随着closure的结束而销毁, 会逃逸到堆上. 有点像创建了一个对象, 对象中有个成员变量tmp, 成员方法执行时会引用该变量.

go的闭包用着也挺方便的, 不过局部变量逃逸到堆上也会引起一些额外开销, 本来在栈上创建变量, 随着栈销毁, 变量也自动销毁, 但如果逃逸到堆上就需要通过gc来回收. 除了闭包也会有其他一些情况引起逃逸, 如使用了interface{}动态类型, 栈空间不足等.

闭包也容易引起一些问题, 在闭包中引用的变量, 可以认为是使用了它的引用(指针), 这样就容易引发一些错误.

func main() {	
	s := []int{1, 2, 3, 4}
	for i, elem := range s {
        go func() {
			fmt.Println(elem)  // 引用的都是elem的地址
		}()
	}
}

func runTime() {
    start := time.Now()
    defer fmt.Println(time.Since(start))  // 0
    defer func() {
        fmt.Println(time.Since(start))  // 预期的时间
    }()
    ...
}

基于共享变量的通信

和其他编程语言类似, 可以通过加锁的方式来比较安全地对变量进行并发方法. sync.Mutex、sync.RWMutex, 要注意的是锁被创建之后就不能拷贝了, 要传递锁(作为参数等)只能传引用, 这和go的实现有关, 要传引用也可以理解, 要保证大家用的是同一把锁, 才能起到控制访问的功能.

基于Channel

Channel是go中推荐使用的通信方式, 一个channel可以认为是一个线程安全的消息队列, 先进先出.

  1. 语法

    Go Channel详解

  2. 一些特殊情况

    • 向已经关闭的channel或为nil的channel中写, 会引发panic
    • 从为nil的channel中读, 会永久阻塞
    • 从已经关闭的channel中读, 如果channel内已经没有数据了, 会返回相应零值, 可以用elem, ok := <-ch, 使用ok来判断获取的值是不是有效值.
  3. 非阻塞式收发
    正常使用channel进行数据的收发都是阻塞式的, 如果channel缓存已满, 再往里写就会阻塞, 如果channe中没有数据, 尝试读的话也会引起阻塞. 要实现非阻塞式的channel访问, 使用select. select是go中一个特殊语法, 看起来和switch有点像.

select {
	case ele := <-readCh:
	case e -> writeCh:
	case <-checkCh:
	default:
		...
}

select语句的效果是看各个case的channel操作是否可以完成(不会被阻塞), 如果有, 从所有可以执行的case中随机选一个执行, 如果没有看有没有default语句, 有的话执行defalut语句, 如果还是没有的话挂起, 等待可执行条件.

::: warning
一些特殊情况:

  1. 空的select语句, 也就是select{}会使当前goroutine直接挂起, 永远无法被唤醒
  2. 只有一个case, 和直接使用channel效果是一样的
  3. 从已关闭的channel中读, 是直接可执行的
    :::

可以简单了解一下select语句的实现, 一些特殊情况会单独处理, 常规逻辑是这样的:

  1. 以一定顺序锁定所有case中的channel, 再根据随机生成的轮询顺序, 遍历各个case查找是否有可以立即执行的case, 有的话选定对应的case执行, 解锁各channel
  2. 如果没有可以立即执行的case, 也没有default, 将当前goroutine加入到所有相关channel的收发队列中, 将自己挂起
  3. 当该goroutine再次被唤醒时, 再锁定各个case, 如此循环

并发控制

退出

一个goroutine不能直接停止另外一个goroutine, 如果可以的话可能会导致goroutine之间的共享变量落在未定义的状态上, 所以只能让goroutine自己退出.

  1. 利用select和被关闭的channel的性质, 能实现简单的退出
control := make(chan struct{})
inData := make(chan int, 2)
go func() {
forTag:
	for {
		select{
			case <- control:
				for data := range inData {
					// do something
				}
            	// 退出
				break forTag
			case data := <- inData:
				// do something
		}
	}
}()
inData <- 1
inData <- 2
time.Sleep(time.Second)
close(control)

  1. 使用Context
    Context在本质上和上面的做法是类似的, 通过关闭channel来进行消息传递, 不过做了些封装, 使用更方便一些.
func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()

	go handle(ctx, 1500*time.Millisecond)
	select {
	case <-ctx.Done():
		fmt.Println("main", ctx.Err())
	}
}

func handle(ctx context.Context, duration time.Duration) {
	select {
	case <-ctx.Done():
		fmt.Println("handle", ctx.Err())
	case <-time.After(duration):
		fmt.Println("process request with", duration)
	}
}

等待

很多时候一个goroutine要等待其他一些goroutine结束之后再执行后续流程, 比如两个任务有前后依赖关系, 可以利用channel的阻塞进行等待.

  1. goroutine结束之后发送完成信号
workerNum := 10
finishCh := make(chan struct{}, workerNum)
worker := func() {
	// do something
	finish <- struct{}{}
}
for i := 0; i < workerNum; i++ {
	go worker()
}
// 等待
for i := 0; i < workerNum; i++ {
	<-finish
}
  1. 利用sync.WaitGroup(类似信号量)
workerNum := 10
wg := &sync.WaitGroup{}
// 要稍微注意一下Add和Done的位置
wg.Add(workerNum)
worker := func() {
	// do something
	wg.Done()
}
for i := 0; i < workerNum; i++ {
	go worker()
}
// 等待
wg.Wait()
  1. 用上面提到的select语句

系统中的一些应用

  1. map-reduce
  2. 几个任务流顺序执行
  3. 递归中的并发数控制

性能分析工具

  1. benchmark基准测试
  2. pprof

参考资料

  1. 《Go语言圣经》
  2. 《Go语言高级编程》
  3. 《Go语言设计与实现》
  4. 《Go语言高性能编程》

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

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

相关文章

数据结构(Java):力扣 二叉树面试OJ题(二)【进阶】

目录 &#x1f48e; 1、题一&#xff1a;二叉树的层序遍历 &#x1f31f; 1.1 思路1&#xff08;递归求解&#xff09; &#x1f31f; 1.1.1 思路1代码 &#x1f506; 1.2 思路2&#xff08;队列求解&#xff09; &#x1f506; 1.2.1 思路2代码 &#x1f48e; 2、题二&…

2024.7.16日 最新版 docker cuda container tookit下载!

nvidia官方指导 https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html 其实就是这几个命令&#xff0c;但是有墙&#xff1a; curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/shar…

【JavaEE】-- 网络编程基础概念(详解)

&#x1f387;&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳&#xff0c;欢迎大佬指点&#xff01; 人生格言: 当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友…

AV1技术学习: Compound Prediction

一、双向 Compound Prediction AV1支持两个参考帧的预测通过多种复合模式线性组合。复合预测公式为 其中&#xff0c;权重m(x, y) is scaled by 64 以进行整数计算&#xff0c;R1(x, y)和R2(x, y)表示两个参考块中位于(x, y)的像素。P(x, y)将按比例缩小 1/64 以形成最终的预测…

十五、【机器学习】【监督学习】- 神经网络回归

系列文章目录 第一章 【机器学习】初识机器学习 第二章 【机器学习】【监督学习】- 逻辑回归算法 (Logistic Regression) 第三章 【机器学习】【监督学习】- 支持向量机 (SVM) 第四章【机器学习】【监督学习】- K-近邻算法 (K-NN) 第五章【机器学习】【监督学习】- 决策树…

MyPostMan 迭代文档管理、自动化接口闭环测试工具(自动化测试篇)

MyPostMan 是一款类似 PostMan 的接口请求软件&#xff0c;按照 项目&#xff08;微服务&#xff09;、目录来管理我们的接口&#xff0c;基于迭代来管理我们的接口文档&#xff0c;文档可以导出和通过 url 实时分享&#xff0c;按照迭代编写自动化测试用例&#xff0c;在不同环…

定制QCustomPlot 带有ListView的QCustomPlot 全网唯一份

定制QCustomPlot 带有ListView的QCustomPlot 文章目录 定制QCustomPlot 带有ListView的QCustomPlot摘要需求描述实现关键字: Qt、 QCustomPlot、 魔改、 定制、 控件 摘要 先上效果,是你想要的,再看下面的分解,顺便点赞搜藏一下;不是直接右上角。 QCustomPlot是一款…

Spring中IoC容器和Bean

目录 IoC(Inversion of Control)控制反转思想 Spring技术对IoC思想的实现 DI(Dependency Injection)依赖注入 目标 最终效果 IoC入门案例 传统方法&#xff0c;不使用IoC思想调用dao层 使用IoC思想调用dao层 第一步&#xff1a;导入Spring坐标 第二步&#xff1a;创建…

stm32:CAN通讯

目录 介绍 协议层 CAN的 帧/报文 种类 数据帧 远程帧&#xff08;遥控帧&#xff09; 错误帧 过载帧 帧间隔 总线仲裁 stm32的CAN外设 工作模式 测试模式 功能框图 时序 标准时序 例子 环回静默模式测试 寄存器代码 HAL版本 介绍 一种功能丰富的车用总线标…

【ffmpeg命令入门】重新编码媒体流、设置码率、设置帧速率

文章目录 前言ffmpeg的描述重新编码媒体流重新编码媒体流的命令ffmpeg支持的媒体流 设置视频码率视频码率是什么设置视频的码率 设置文件帧数率帧数率是什么ffmpeg设置帧数率 总结 前言 在数字媒体处理领域&#xff0c;ffmpeg是一款非常强大的工具&#xff0c;它可以用来进行媒…

自动化产线 搭配数据采集监控平台 创新与突破

自动化产线在现在的各行各业中应用广泛&#xff0c;已经是现在的生产趋势&#xff0c;不同的自动化生产设备充斥在各行各业中&#xff0c;自动化的设备会产生很多的数据&#xff0c;这些数据如何更科学化的管理&#xff0c;更优质的利用&#xff0c;就需要数据采集监控平台来完…

解决Ubuntu 20.04下外接显示屏无信号问题【多次尝试无坑完整版!!!】

解决Ubuntu 20.04下外接显示屏无信号问题【多次尝试无坑完整版&#xff01;&#xff01;&#xff01;】 一、引言 作为一名开发者&#xff0c;我经常在Windows和Ubuntu之间切换&#xff0c;以满足不同的开发需求。最近&#xff0c;我在使用惠普暗影精灵9&#xff08;搭载RTX 4…

HLS加密技术:保障流媒体内容安全的利器

随着网络视频内容的爆炸性增长&#xff0c;如何有效保护视频内容的版权和安全成为了一个亟待解决的问题。HLS&#xff08;HTTP Live Streaming&#xff09;加密技术作为一种先进的流媒体加密手段&#xff0c;凭借其高效性和安全性&#xff0c;在直播、点播等场景中得到了广泛应…

隐性行为克隆——机器人的复杂行为模仿学习的新表述

介绍 论文地址&#xff1a;https://arxiv.org/pdf/2109.00137.pdf 源码地址&#xff1a;https://github.com/opendilab/DI-engine.git 近年来&#xff0c;人们对机器人学习进行了大量研究&#xff0c;并取得了许多成果。其中&#xff0c;模仿学习法尤其受到关注。这是一种从人…

JavaEE初阶 - IO、存储、硬盘、文件系统相关常识 (二)

&#x1f387;&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳&#xff0c;欢迎大佬指点&#xff01; 人生格言: 当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友…

appium2.0 执行脚本遇到的问题

遇到的问题&#xff1a; appium 上的日志信息&#xff1a; 配置信息 方法一 之前用1.0的时候 地址默认加的 /wd/hub 在appium2.0上&#xff0c; 服务器默认路径是 / 如果要用/wd/hub 需要通过启动服务时设置基本路径 appium --base-path/wd/hub 这样就能正常执行了 方法二…

HarmonyOS NEXT学习——@BuilderParam装饰器

初步理解&#xff0c;相当于VUE的插槽slot Builder function overBuilder() {}Component struct Child {label: string ChildBuilder customBuilder() {}Builder customChangeThisBuilder() {}BuilderParam customBuilderParam: () > void this.customBuilder; // 使用自定…

【TDA4板端部署】基于 Pytorch 训练并部署 ONNX 模型在 TDA4

1 将torch模型转onnx模型 Ti转换工具只支持以下格式&#xff1a; Caffe - 0.17 (caffe-jacinto in gitHub) Tensorflow - 1.12 ONNX - 1.3.0 (opset 9 and 11) TFLite - Tensorflow 2.0-Alpha 基于 Tensorflow、Pytorch、Caffe 等训练框架&#xff0c;训练模型&#xff1a;选择…

Hadoop3:HDFS存储优化之小文件归档

一、情景说明 我们知道&#xff0c;NameNode存储一个文件元数据&#xff0c;默认是150byte大小的内存空间。 那么&#xff0c;如果出现很多的小文件&#xff0c;就会导致NameNode的内存占用。 但注意&#xff0c;存储小文件所需要的磁盘容量和数据块的大小无关。 例如&#x…

【5G Sub-6GHz模块】专为IoT/eMBB应用而设计的RG520NNA、RG520FEB、RG530FNA、RG500LEU 5G模组

推出全新的5G系列模组&#xff1a; RG520NNADB-M28-SGASA RG520NNADA-M20-SGASA RG520FEBDE-M28-TA0AA RG530FNAEA-M28-SGASA RG530FNAEA-M28-TA0AA RG500LEUAA-M28-TA0AA ——明佳达 1、5G RG520N 系列——专为IoT/eMBB应用而设计的LGA封装模块 RG520N 系列是一款专为 IoT…