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中的迭代器(Iterator)

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

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 基础的原理 光热发电系统包括太阳能集热、传储热、发电三大模…

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;关注…

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

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

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

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

数据分析01——系统认识数据分析

1.数据分析的全貌 1.1观测 1.1.1 观察 &#xff08;1&#xff09;采集数据 a.采集数据&#xff1a;解析系统日志 当你在看视频的时候———就会产生日志———解析日志———得到数据 b.采集数据&#xff1a;埋点获取新数据&#xff08;自定义记录新的信息&#xff09; 日志…

【Vue】Vue3 安装 Tailwind CSS 入门

初始化 Vue 3 项目 npm install -g vue/cli vue create my-project安装 Tailwind CSS 进入你的项目目录&#xff0c;然后安装 Tailwind CSS 和其依赖项&#xff1a; npm install -D tailwindcss postcss autoprefixer配置 PostCSS Tailwind CSS 需要通过 PostCSS 进行处理。…

Python酷库之旅-第三方库Pandas(029)

目录 一、用法精讲 74、pandas.api.interchange.from_dataframe函数 74-1、语法 74-2、参数 74-3、功能 74-4、返回值 74-5、说明 74-6、用法 74-6-1、数据准备 74-6-2、代码示例 74-6-3、结果输出 75、pandas.Series类 75-1、语法 75-2、参数 75-3、功能 75-4…

牛客 7.13 月赛(留 C逆元 Ddp)

B-最少剩几个&#xff1f;_牛客小白月赛98 (nowcoder.com) 思路 奇数偶数 奇数&#xff1b;奇数*偶数 奇数 所以在既有奇数又有偶数时&#xff0c;两者结合可以同时删除 先分别统计奇数&#xff0c;偶数个数 若偶个数大于奇个数&#xff0c;答案是偶个数-奇个数 若奇个数…

六边形动态特效404单页HTML源码

源码介绍 动态悬浮的六边形,旁边404文字以及跳转按钮,整体看着像科技二次元画风,页面简约美观,可以做网站错误页或者丢失页面,将下面的代码放到空白的HTML里面,然后上传到服务器里面,设置好重定向即可 效果预览 完整源码 <!DOCTYPE html> <html><head…

PyCharm查看文件或代码变更记录

背景&#xff1a; Mac笔记本上有一个截图的定时任务在运行&#xff0c;本地Python使用的是PyCharm IDE&#xff0c;负责的同事休假&#xff0c;然后定时任务运行的结果不符合预期&#xff0c;一下子不知道问题出现在哪里。 定位思路&#xff1a; 1、先确认网络、账号等基本的…

Qt 快速保存配置的方法

Qt 快速保存配置的方法 一、概述二、代码1. QFileHelper.cpp2. QSettingHelper.cpp 三、使用 一、概述 这里分享一下&#xff0c;Qt界面开发时&#xff0c;快速保存界面上一些参数配置的方法。 因为我在做实验的时候&#xff0c;界面上可能涉及到很多参数的配置&#xff0c;我…

实战打靶集锦-31-monitoring

文章目录 1. 主机发现2. 端口扫描3. 服务枚举4. 服务探查4.1 ssh服务4.2 smtp服务4.3 http/https服务 5. 系统提权5.1 枚举系统信息5.2 枚举passwd文件5.3 枚举定时任务5.4 linpeas提权 6. 获取flag 靶机地址&#xff1a;https://download.vulnhub.com/monitoring/Monitoring.o…

django学习入门系列之第四点《BootStrap依赖》

文章目录 往期回顾 BootStrap依赖于JavaScript的类库&#xff0c;JQuery下载 下载JQuery&#xff0c;在界面上应用JQuery 在页面上应用BootStrap和avaScript的类库【JQuery是avaScript的类库】 JQuery的官网&#xff1a; jQuery 如果要应用JQuery 则要在body里面导入文件…

MySQL(7)内外连接+索引

目录 1.内外连接; 2. 索引; 1.内外连接: 1.1内连接: 语法: select 字段 from 表名 inner join 表名 on 字段限制; 1.2 外连接: 分为左右外连接; (1)左外连接: 语法: select * from 表名 left join 表名 on 字段限制. &#x1f330;查询所有学生的成绩&#xff0c;如果这个学生…