Go语言中的并发

简单介绍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 := 1return func() int {tmp++return tmp}
}func main() {test1 := closure()test2 := closure()test1()  // 2test1()  // 3test2()  // 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))  // 0defer 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 forTagcase 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 somethingfinish <- 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 somethingwg.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.mzph.cn/news/872923.shtml

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

相关文章

rust编译安卓各个平台so库

安卓studio 安装SDK 和 NDK 所有操作是mac m1 上操作的 NDK 可以在 Android studio 设置里面,搜索sdk &#xff0c;然后看下SDK 位置例如我下面的位置: /Users/admin/Library/Android/sdk/ndkAndroid NDK&#xff08;Native Development Kit&#xff09;生成一个独立的工具链…

Java中锁的全面详解(深刻理解各种锁)

一.Monitor 1. Java对象头 以32位虚拟机位例 对于普通对象,其对象头的存储结构为 总长为64位,也就是8个字节, 存在两个部分 Kclass Word: 其实也就是表示我们这个对象属于什么类型,也就是哪个类的对象.而对于Mark Word.查看一下它的结构存储 64位虚拟机中 而对于数组对象,我…

Java面试题(企业真题)

01.泛型的理解 泛型是Java 5引入的一种特性,它允许程序员在定义类、接口或方法时指定一个或多个类型参数,从而可以在运行时处理各种不同的数据类型,同时保持类型安全。泛型提供了编译时类型检查,消除了强制类型转换的需要,减少了运行时ClassCastException异常的可能性。02…

设计模式使用场景实现示例及优缺点(行为型模式——状态模式)

在一个遥远的国度中&#xff0c;有一个被称为“变幻之城”的神奇城堡。这座城堡有一种特殊的魔法&#xff0c;能够随着王国的需求改变自己的形态和功能。这种神奇的变化是由一个古老的机制控制的&#xff0c;那就是传说中的“状态宝石”。 在变幻之城中&#xff0c;有四颗宝石&…

神经网络中的激活函数举例,它们各自的特点,以及哪个激活函数效果更好,为什么

sigmoid&#xff1a; \(\sigma(x)1/(1e^{-x})\)&#xff1b; 优&#xff1a;将数值压缩到 0 1&#xff0c;导数为 \(\sigma(x)(1-\sigma(x))\) 好算。劣&#xff1a;输出均值非0&#xff08;0.5&#xff09;&#xff0c;梯度消失&#xff08;Gradient vanishing&#xff09;每次…

Java中的迭代器(Iterator)

Java中的迭代器&#xff08;Iterator&#xff09; 1、 迭代器的基本方法2、 迭代器的使用示例3、注意事项4、克隆与序列化5、结论 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Java中&#xff0c;迭代器&#xff08;Iterator&#xff0…

langchain 入门指南(三)- token的计算

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 在上一篇文章中&#xff0c;我们知道了&#xff0c;ChatOpenAI 对象调用 invoke 方法返回的的信息中&#xff0c; 包含了输入的 token 数…

Web开发:四角线框效果(HTML、CSS、JavaScript)

目录 一、实现效果 二、完整代码 三、页面准备 1、页面结构 2、初始样式 3、现有效果 三、线框实现 1、需求分析 2、线框结构 3、线框大小 4、线框位置 5、线框样式 6、移动线框 7、添加过渡效果 8、使用CSS变量 一、实现效果 如下图所示&#xff0c;当鼠标移动…

html 单页面引用vue3和element-plus

引入方式&#xff1a; element-plus基于vue3.0&#xff0c;所以必须导入vue3.0的js文件&#xff0c;然后再导入element-plus自身所需的js以及css文件&#xff0c;导入文件有两种方法&#xff1a;外部引用、下载本地使用 通过外部引用ElementPlus的css和js文件 以及Vue3.0文件 …

光热熔盐储能

长时储能的新赛道上&#xff0c;多种技术正在加速竞逐&#xff0c;谁最有可能成为其中的王者&#xff1f; 液流电池、压缩空气储能、重力储能&#xff1f;储能行业的玩家们通常不会想到的答案是光热熔盐储能。 1 基础的原理 光热发电系统包括太阳能集热、传储热、发电三大模…

DP讨论——适配器、桥接、代理、装饰器模式通用理解

学而时习之&#xff0c;温故而知新。 共性 适配器、桥接、代理和装饰器模式&#xff0c;实现上基本没啥区别&#xff0c;怎么区分&#xff1f;只能从上下文理解&#xff0c;看目的是啥。 它们&#xff0c;我左看上看下看右看&#xff0c;发现理解可以这么简单:都是A类调用B/…

MK米客方德推出新一代工业级SD NAND

--更长寿命、更高速度、更优功耗 目录 --更长寿命、更高速度、更优功耗 1.LGA-8封装&#xff1a; 2.工业级SLC存储颗粒&#xff1a; 3.高IOPS性能&#xff1a; 4.健康状态侦测(Smart Function)&#xff1a; 5.内嵌ECC校验、坏块管理、垃圾回收、磨损平均算法等功能。 6…

大厂面试官问我:Redis为什么使用哈希槽的方式进行数据分片?为什么不适用一致性哈希的方式?【后端八股文十三:Redis 集群哈希八股文合集(1)】

本文为【Redis 集群哈希 八股文合集&#xff08;1&#xff09;】初版&#xff0c;后续还会进行优化更新&#xff0c;欢迎大家关注交流~ hello hello~ &#xff0c;这里是绝命Coding——老白~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注…

whisper-api语音识别语音翻译高性能兼容openai接口协议的开源项目

whisper-api 介绍 使用openai的开源项目winsper语音识别开源模型封装成openai chatgpt兼容接口 软件架构 使用uvicorn、fastapi、openai-whisper等开源库实现高性能接口 更多介绍 [https://blog.csdn.net/weixin_40986713/article/details/138712293](https://blog.csdn.n…

leetcode-46. 全排列

题目描述 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]示例 2&#xff1a; 输入&#xf…

【ELK+Kafka+filebeat分布式日志收集】分布式日志收集详解

分布式日志收集是一种用于在分布式系统中收集、聚合、存储和分析日志数据的技术。随着系统规模的扩大和分布式架构的普及,单节点的日志收集和处理方案已经难以满足需求。因此,分布式日志收集系统应运而生。以下是分布式日志收集的详细讲解: 一、背景 1、为什么需要分布式日…

百日筑基第二十三天-23种设计模式-创建型总汇

百日筑基第二十三天-23种设计模式-创建型总汇 前言 设计模式可以说是对于七大设计原则的实现。 总体来说设计模式分为三大类&#xff1a; 创建型模式&#xff0c;共五种&#xff1a;单例模式、简单工厂模式、抽象工厂模式、建造者模式、原型模式。结构型模式&#xff0c;共…

防洪墙的安全内容检测+http请求头

1、华为的IAE引擎&#xff1a;内部工作过程 IAE引擎主要是针对2-7层进行一个数据内容的检测 --1、深度检测技术 (DPI和DPF是所有内容检测都必须要用到的技术) ---1、DPI--深度包检测&#xff0c;针对完整的数据包&#xff0c;进行内容的识别和检测 1、基于特征子的检…

Windows图形界面(GUI)-DLG-C/C++ - 列表视图(ListView)

公开视频 -> 链接点击跳转公开课程博客首页 -> ​​​​​​链接点击跳转博客主页 目录 列表视图(ListView) 控件类型 初始化控件环境 示例代码 列表视图(ListView) 控件类型 详细信息视图&#xff08;Report View&#xff09;&#xff1a;数据以列的形式显示&…

Collections.unmodifiableList

Collections.unmodifiableList 是 Java Collections Framework 提供的一个方法&#xff0c;用于创建一个不可修改的视图&#xff08;unmodifiable view&#xff09;列表。这个方法返回的列表是对原始列表的一个包装&#xff0c;任何对这个包装列表的修改操作都会抛出 Unsupport…