当前位置:首页 > 服务端 > golang的并发不等于并行

golang的并发不等于并行

2022年08月05日 19:16:32服务端8

转自个人博客 chinazt.cc

先看下面一道面试题:

func main() {
	runtime.GOMAXPROCS(1)
	wg := sync.WaitGroup{}
	wg.Add(20)
	for i := 0; i < 10; i++ {
		go func() {
			fmt.Println("go routine 1 i: ", i)
			wg.Done()
		}()
	}
	for i := 0; i < 10; i++ {
		go func(i int) {
			fmt.Println("go routine 2 i: ", i)
			wg.Done()
		}(i)

	}
	wg.Wait()
}

在不执行代码的前提下,脑补一下输出结果应该是什么。

我再看到这道题时,首先想到输出应该是0 -- 9 依次输出。 但执行后才大跌眼镜,错的不是一点半点。首先看一下,在我本地执行的结果:

go routine 2 i: 9
go routine 1 i: 10
go routine 1 i: 10
go routine 1 i: 10
go routine 1 i: 10
go routine 1 i: 10
go routine 1 i: 10
go routine 1 i: 10
go routine 1 i: 10
go routine 1 i: 10
go routine 1 i: 10
go routine 2 i: 0
go routine 2 i: 1
go routine 2 i: 2
go routine 2 i: 3
go routine 2 i: 4
go routine 2 i: 5
go routine 2 i: 6
go routine 2 i: 7
go routine 2 i: 8

意不意外? 惊不惊喜?

为什么会是这样的结果, 再翻阅了google官方出品的golang文档之后,总算搞到了一些头绪。

并发不等于并行

golang的核心开发人员Rob Pike专门提到了这个话题(有兴趣可以看这个视频或者看原文PPT)

虽然我们在for循环中使用了go 创建了一个goroutine,我们想当然会认为,每次循环变量时,golang一定会执行这个goroutine,然后输出当时的变量。 这时,我们就陷入了思维定势。 默认并发等于并行。

诚然,通过go创建的goroutine是会并发的执行其中的函数代码。 但一定会按照我们所设想的那样每次循环时执行吗? 答案是否定的!

Rob Pike专门提到了golang中并发指的是代码结构中的某些函数逻辑上可以同时运行,但物理上未必会同时运行。而并行则指的就是在物理层面也就是使用了不同CPU在执行不同或者相同的任务。

golang的goroutine调度模型决定了,每个goroutine是运行在虚拟CPU中的(也就是我们通过runtime.GOMAXPROCS(1)所设定的虚拟CPU个数)。 虚拟CPU个数未必会和实际CPU个数相吻合。每个goroutine都会被一个特定的P(虚拟CPU)选定维护,而M(物理计算资源)每次回挑选一个有效P,然后执行P中的goroutine。

每个P会将自己所维护的goroutine放到一个G队列中,其中就包括了goroutine堆栈信息,是否可执行信息等等。默认情况下,P的数量与实际物理CPU的数量相等。因此当我们通过循环来创建goroutine时,每个goroutine会被分配到不同的P队列中。而M的数量又不是唯一的,当M随机挑选P时,也就等同随机挑选了goroutine。

在本题中,我们设定了P=1。所以所有的goroutine会被绑定到同一个P中。 如果我们修改runtime.GOMAXPROCS的值,就会看到另外的顺序。 如果我们输出goroutine id,就可以看到随机挑选的效果:

func main() {
	wg := sync.WaitGroup{}
	wg.Add(20)
	for i := 0; i < 10; i++ {
		go func() {
			var buf [64]byte
			n := runtime.Stack(buf[:], false)
			idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
			id, err := strconv.Atoi(idField)
			if err != nil {
				panic(fmt.Sprintf("cannot get goroutine id: %v", err))
			}
			fmt.Println("go routine 1 i: ", i, id)
			wg.Done()
		}()
	}
	for i := 0; i < 10; i++ {
		go func(i int) {
			var buf [64]byte
			n := runtime.Stack(buf[:], false)
			idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
			id, err := strconv.Atoi(idField)
			if err != nil {
				panic(fmt.Sprintf("cannot get goroutine id: %v", err))
			}
			fmt.Println("go routine 2 i: ", i, id)
			wg.Done()
		}(i)

	}
	wg.Wait()
}

输出如下:

go routine 2 i: 9 24
go routine 1 i: 10 11
go routine 1 i: 10 5
go routine 1 i: 10 6
go routine 2 i: 3 18
go routine 1 i: 10 9
go routine 1 i: 10 10
go routine 1 i: 10 8
go routine 2 i: 0 15
go routine 2 i: 4 19
go routine 2 i: 6 21
go routine 1 i: 10 7
go routine 1 i: 10 14
go routine 2 i: 7 22
go routine 2 i: 8 23
go routine 1 i: 10 13
go routine 2 i: 5 20
go routine 1 i: 10 12
go routine 2 i: 1 16
go routine 2 i: 2 17

⋊> ~/S/g/g/s/t/C/goroutine ./goroutine
go routine 1 i: 10 11
go routine 2 i: 9 24
go routine 1 i: 10 6
go routine 1 i: 10 14
go routine 1 i: 10 9
go routine 1 i: 10 10
go routine 1 i: 10 12
go routine 2 i: 0 15
go routine 1 i: 10 13
go routine 1 i: 10 5
go routine 2 i: 1 16
go routine 2 i: 5 20
go routine 1 i: 10 7
go routine 2 i: 7 22
go routine 2 i: 3 18
go routine 2 i: 2 17
go routine 2 i: 4 19
go routine 1 i: 10 8
go routine 2 i: 8 23
go routine 2 i: 6 21

我们再回到这道题中,虽然在循环中通过go定义了一个goroutine。但我们说到了,并发不等于并行。因此虽然定义了,但此刻不见得就会去执行。需要等待M选择P之后,才能去执行goroutine。 关于golang中goroutine是如何进行调度的(GPM模型),可以参考Scalable Go Scheduler Design Doc或者LearnConcurrency

这时应该就可以理解为什么会先输出goroutine2然后再输出goroutine1了吧。

下面我们来解释为什么goroutine1中输出的都是10.

goroutine如何绑定变量

在golang的for循环中,golang每次都使用相同的变量实例(也就是题中所使用的i)。 而golang之间是共享环境变量的。

当调度到这个goroutine时,它就直接读取所保存的变量地址,此时就会出现一个问题:goroutine保存的只是变量地址,所以变量是有可能被修改的

再结合题中的for循环,每次使用的都是同一个变量地址,也就是说i每次都在变化,到循环结束之时,i就变成了10. 而goroutine中保存的也只有i的内存地址而已,所以当goroutine1执行时,毫不犹豫的就把i的内容读了出来,多少呢? 10!

但为什么goroutine2不是10呢?

反过来看goroutine2,就容易理解了。因为在每次循环中都重新生成了一个新变量,然后每个goroutine保存的是各自新变量的地址。 这些变量相互之间互不干扰,不会被任何人所篡改。因此在输出时,会从0 - 9依次输出。

其实这些问题,golang官方已经发过预警提示。 只管自己看官方文档的习惯,所以直接栽坑里了。

好在及时发现了自己的不足,亡羊补牢,为时未晚吧。

来源链接:https://www.cnblogs.com/vikings-blog/p/7111679.html

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。


本文链接:https://www.javaclub.cn/server/18029.html

分享给朋友:

“golang的并发不等于并行” 的相关文章

实习第一周(Golang)

实习第一周(Golang)

目录 一,序 二,基础学习 三,环境安装 四,项目操练 GO111MODULE...

go系列之利用Gin框架获取form参数

go系列之利用Gin框架获取form参数

利用Gin框架获取form参数 除了通过URL查询参数提交数据到服务器外,常用的还有通过Form表单的方式。Form表单相比URL查询参数,用户体验好,可以承载更多的数据,尤其是文件上传,所以也更为方便。Form 表单对于Form表单,我们不会陌生,比如...

Spring Cloud面试问题

Spring Cloud面试问题

问:什么是Spring Cloud?     答: Spring Cloud Stream App Starters是基于Spring Boot的Spring Integration应用程序,提供与外部系统的集成。Spring Cloud Task。...

MySQL面试有这一篇就够了

MySQL面试有这一篇就够了

MySQL面试常见知识点 1、 MySQL常用的存储引擎有什么?它们有什么区别? InnoDB InnoDB是MySQL的默认存储引擎,支持事务、行锁和外键等操作。 MyISAM MyISAM是M...

学习go语言国内最全资料链接

就最近和各位大佬认识下来,以前觉得学习go语言,可能资料比较少,可是后来才发现,原来资料并不少,甚至可以说通过大家的努力,go社区已经非常包容且完善了 接下来会推荐一些资料,以及大佬 就最近和各位大佬认识下来,以前觉得学习go语言,可能资料比...

什么是软件实施?软件实施前景几何?软件实施的面试题有那些?

事情是这样的,由于自己目前还没有对象,就想着在兰州找一份还不错的工作,于是投了一家在我的家乡还算不错的公司,对方却说有可能是软件实施岗位,于是趁机了解了一下, 什么是软件实施? 软件实施掌握的基础知识有哪些? 软件实施前景几何?...

Hugo LoveIt主题配置与使用

我的博客地址luckly 1. 安装主题 把这个主题克隆到 themes 目录: git clone https://github.com/dillonzq/LoveIt.git themes/LoveIt 2. 配置主题...

并发编程|说完AQS,面试官为何不淡定了?

并发编程|说完AQS,面试官为何不淡定了?

你能说下什么是AQS AQS是队列同步器AbstractQueueSynchronizer的简写,它是用来构建锁和其他同步组件的基础框架,它定义了一个全局的int 型的state变量,通过内置的FIFO(先进先出)队列来完成资源竞...

Flutter设置App的应用名字和应用logo图标的方法(android ios web)

Flutter设置App的应用名字和应用logo图标的方法(android ios web)

在前面的几期给大家介绍了flutter的安装以及一些简单的配置,还运行了helloword 那么接下来就带领大家了解如何设置应用名称以及图标 Flutter设置App的应用名字和应用logo图标的方法, 知识点虽然简单,但是不知道这个知...

Spring Boot 2 快速教程:WebFlux 集成 Mongodb(四)

Spring Boot 2 快速教程:WebFlux 集成 Mongodb(四)

摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第104篇原创 文章工程: * JDK 1.8 * Maven 3.5.2 * Spring Boot 2.1.3.R...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。