【golang】go语句执行规则(goroutine)(上)

news2024/9/29 23:24:09

Don’t communicate by sharing memory;share memory by communicating.

从Go语言编程的角度解释,这句话的意思就是:不要通过共享数据来通讯,恰恰相反,要以通讯的方式共享数据。

进程和线程

进程,描述的就是程序的执行过程,是运行着的程序的代表。换句话说,一个进程其实就是某个程序运行时的一个产物。如果说静静地躺在那里的代码就是程序的话,那么奔跑着的、正在发挥着既有功能的代码就可以被称为进程。

我们的电脑为什么可以同时运行那么多应用程序?我们的手机为什么可以有那么多App同时在后台刷新?这都是因为它们的操作系统之上有多个代表着不同应用程序或App的进程同时运行。

线程,线程总是在进程之内的,它可以被视为进程中运行着的控制流(或者代码执行的流程)。

一个进程至少会包含一个线程。如果一个进程只包含了一个线程,那么它里面的所有代码都只会被串行的执行。每个进程的第一个线程都会随着该进程的启动而被创建,它们可以被称为其所属进程的主线程。

相对应的,如果一个进程中包含了多个线程,那么其中的代码就可以被并发地执行。

也就是说,主线程之外的其他线程都只能由代码显式地创建和销毁。这需要我们在编写程序的时候进行手动控制,操作系统以及进程本身并不会帮我们下达这样的命令,它们只会忠实地执行我们的指令。

不过,在 Go 程序当中,Go 语言的运行时(runtime)系统会帮助我们自动地创建和销毁系统级的线程。这里的系统级线程指的就是我们刚刚说过的操作系统提供的线程。

而对应的用户级线程指的是架设在系统级线程之上的,由用户(或者说我们编写的程序)完全控制的代码执行流程。用户级线程的创建、销毁、调度、状态变更以及其中的代码和数据都完全需要我们的程序自己去实现和处理。

这带来了很多优势,比如,因为它们的创建和销毁并不用通过操作系统去做,所以速度会很快,又比如,由于不用等着操作系统去调度它们的运行,所以往往会很容易控制并且可以很灵活。

但是,劣势也是有的,最明显也最重要的一个劣势就是复杂。如果我们只使用了系统级线程,那么我们只要指明需要新线程执行的代码片段,并且下达创建或销毁线程的指令就好了,其他的一切具体实现都会由操作系统代劳。

如果使用用户级线程,我们就不得不既是指令下达者,又是指令执行者。我们必须全权负责与用户级线程有关的所有具体实现。

操作系统不但不会帮忙,还会要求我们的具体实现必须与它正确地对接,否则用户级线程就无法被并发地,甚至正确地运行。毕竟我们编写的所有代码最终都需要通过操作系统才能在计算机上执行。这听起来就很麻烦,不是吗?

不过,Go语言不但有着独特的并发编程模型,以及用户级线程goroutine,还拥有强大的用于调度goroutine、对接系统级线程的调度器。

这个调度器是Go语言运行时系统的重要组成部分,它主要负责统筹调配Go并发编程模型中的三个主要元素,即:G(goroutine的缩写)P(processor的缩写)M(machine的缩写)

其中的M指代的就是系统级线程。而P指的是一种可以承载若干个G,且能够使这些G适时地与M进行对接,并得到真正运行的中介。

从宏观上说,GM 由于 P 的存在可以呈现出多对多的关系。当一个正在与某个 M 对接并运行着的G,需要因某个事件(比如等待 I/O 或锁的解除)而暂停运行的时候,调度器总会及时地发现,并把这个 G 与那个 M 分离开,以释放计算资源供那些等待运行的 G 使用。

而当一个 G 需要恢复运行的时候,调度器又会尽快地为它寻找空闲的计算资源(包括 M)并安排运行。另外,当 M 不够用时,调度器会帮我们向操作系统申请新的系统级线程,而当某个 M 已无用时,调度器又会负责把它及时地销毁掉。

正因为调度器帮助我们做了很多事,所以我们的 Go 程序才总是能高效地利用操作系统和计算机资源。程序中的所有 goroutine 也都会被充分地调度,其中的代码也都会被并发地运行,即使这样的 goroutine 有数以十万计,也仍然可以如此。

image.png

什么是goroutine,它与我们启用的其他goroutine有什么不同?

package main
import "fmt"
func main() {
    for i := 0; i < 10; i++ {
        go func() {
                fmt.Println(i)
            }()
}
}

问题:这个命令源码文件被执行后会打印出什么内容?

回答:不会有任何内容被打印出来。

问题解析

与一个进程总会有一个主线程类似,每一个独立的Go程序在运行时总会有一个主goroutine。这个主goroutine会在Go程序的运行准备工作完成后被自动地启用,并不需要我们做任何手动的操作。

每条go语句一般都会携带一个函数调用,这个被调用的函数常常被称为go函数。而主goroutinego函数就是那个程序入口的main函数。

注意:go函数真正被执行的时间,总会与其所属的go语句被执行的时间不同。当程序执行到一条go语句的时候,Go 语言的运行时系统,会先试图从某个存放空闲的 G 的队列中获取一个 G(也就是goroutine),它只有在找不到空闲 G 的情况下才会去创建一个新的 G。

然而,创建 G 的成本也是非常低的。创建一个 G 并不会像新建一个进程或者一个系统级线程那样,必须通过操作系统的系统调用来完成,在 Go 语言的运行时系统内部就可以完全做到了,更何况一个 G 仅相当于为需要并发执行代码片段服务的上下文环境而已。

在拿到了一个空闲的 G 之后,Go 语言运行时系统会用这个 G 去包装当前的那个go函数(或者说该函数中的那些代码),然后再把这个 G 追加到某个存放可运行的 G 的队列中。这类队列中的 G 总是会按照先入先出的顺序,很快地由运行时系统内部的调度器安排运行。虽然这会很快,但是由于上面所说的那些准备工作还是不可避免的,所以耗时还是存在的。

因此,go函数的执行时间总是会明显滞后于它所属的go语句的执行时间。当然了,这里所说的“明显滞后”是对于计算机的 CPU 时钟和 Go 程序来说的。我们在大多数时候都不会有明显的感觉。

只要go语句本身执行完毕,Go 程序完全不会等待go函数的执行,它会立刻去执行后边的语句。这就是所谓的异步并发地执行。

for i := 0; i < 10; i++ {
        go func() {
                fmt.Println(i)
            }()
}

这里“后边的语句”指的一般是for语句中的下一个迭代。然而,当最后一个迭代运行的时候,这个“后边的语句”是不存在的。
在上述代码中的那条for语句会以很快的速度执行完毕。当它执行完毕时,那 10 个包装了go函数的 goroutine 往往还没有获得运行的机会。

go函数中的那个对fmt.Println函数的调用是以for语句中的变量i作为参数的。你可以想象一下,如果当for语句执行完毕的时候,这些go函数都还没有执行,那么它们引用的变量i的值将会是什么?

它们都会是10,对吗?那么这道题的答案会是“打印出 10 个10”,是这样吗?

在确定最终的答案之前,你还需要知道一个与主 goroutine 有关的重要特性,即:一旦主goroutine 中的代码(也就是main函数中的那些代码)执行完毕,当前的 Go 程序就会结束运行。

如果在 Go 程序结束的那一刻,还有 goroutine 未得到运行机会,那么它们就真的没有运行机会了,它们中的代码也就不会被执行了。

严谨地讲,Go 语言并不会去保证这些 goroutine 会以怎样的顺序运行。由于主goroutine 会与我们手动启用的其他 goroutine 一起接受调度,又因为调度器很可能会在goroutine 中的代码只执行了一部分的时候暂停,以让所有的 goroutine 有更公平的运行机会。

所以哪个 goroutine 先执行完、哪个 goroutine 后执行完往往是不可预知的,除非我们使用了某种 Go 语言提供的方式进行了人为干预。然而,在这段代码中,我们并没有进行任何人为干预。

文章学习自郝林老师的《Go语言36讲》

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

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

相关文章

如何使用CSS实现一个无限循环滚动的图片轮播效果?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐HTML 结构⭐ CSS 样式⭐ JavaScript 控制⭐ 注意事项&#xff1a;⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff0…

ModaHub魔搭社区:AI Agent在数字卡牌游戏场景下的AgentBench基准测试

近日,来自清华大学、俄亥俄州立大学和加州大学伯克利分校的研究者设计了一个测试工具——AgentBench,用于评估LLM在多维度开放式生成环境中的推理能力和决策能力。研究者对25个LLM进行了全面评估,包括基于API的商业模型和开源模型。 他们发现,顶级商业LLM在复杂环境中表现出…

IO多路转接 ——— select、poll、epoll

select初识 select是系统提供的一个多路转接接口。 select系统调用可以让我们的程序同时监视多个文件描述符的上的事件是否就绪。 select的核心工作就是等&#xff0c;当监视的多个文件描述符中有一个或多个事件就绪时&#xff0c;select才会成功返回并将对应文件描述符的就绪…

kingbase(人大金仓)数据库的常用知识点与简单巡检

查看服务是否已设为开机自启 systemctl list-dependencies |grep kingbasehttps://blog.csdn.net/gyqailxj/article/details/127290687

Dynamic CRM开发 - 实体字段(二)字段安全性

在上一篇文章 Dynamic CRM开发 - 实体字段(一)中提到了创建实体字段时,有个“字段安全性”属性,主要用于字段的权限控制,本篇文章专门对此做详细说明。 需求:产品的折扣价格,只对有权限的用户可见。 首先创建一个“折扣价”字段,勾选“字段安全性”属性,如下图: 启…

网络安全—黑客技术(学习笔记)

1.网络安全是什么 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 2.网络安全市场 一、是市场需求量高&#xff1b; 二、则是发展相对成熟…

基于QCC_BES 平台的LMS自适应滤波算法实现

+我V hezkz17进数字音频系统研究开发交流答疑群(课题组) LMS算法是最小均方(Least Mean Square)算法的缩写。它是一种自适应滤波算法,常用于信号处理、系统辨识和自适应滤波等领域。 LMS算法的目标是通过对输入信号和期望输出信号之间的误差进行最小化,来调整滤波器的权重…

协议的分层结构

1.1TCP/IP 协议 为了使各种不同的计算机之间可以互联&#xff0c;ARPANet指定了一套计算机通信协议&#xff0c;即TCP/IP 协议(族). 注意TCP /IP 协议族指的不只是这两个协议 而是很多协议&#xff0c; 只要联网的都使用TCP/IP协议族 为了减少 协议设计的复杂度 &#xff0c;大…

python中的matplotlib画散点图(数据分析与可视化)

python中的matplotlib画散点图&#xff08;数据分析与可视化&#xff09; import numpy as np import pandas as pd import matplotlib.pyplot as pltpd.set_option("max_columns",None) plt.rcParams[font.sans-serif][SimHei] plt.rcParams[axes.unicode_minus]Fa…

LLMs参考资料第一周以及BloombergGPT特定领域的训练 Domain-specific training: BloombergGPT

1. 第1周资源 以下是本周视频中讨论的研究论文的链接。您不需要理解这些论文中讨论的所有技术细节 - 您已经看到了您需要回答讲座视频中的测验的最重要的要点。 然而&#xff0c;如果您想更仔细地查看原始研究&#xff0c;您可以通过以下链接阅读这些论文和文章。 1.1 Trans…

NoSuchModuleError: Can‘t load plugin: sqlalchemy.dialects:clickhouse解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

HDLBits-Verilog学习记录 | Verilog Language-Vectors

文章目录 11.vectors | vector012.vectors in more detail | vector113.Vector part select | Vector214.Bitwise operators | Vectorgates15.Four-input gates | Gates416.Vector concatenation operator | Vector317.Vector reversal 1 | Vectorr18. Replication operator | …

HTML常见标签详解

HTML 标签 一 . HTML 结构1. 认识 HTML 标签2. HTML 文件的基本结构3. 标签层次结构 二 . HTML常见标签注释标签标题标签: h1~h6段落标签: p换行标签 :br格式化标签图片标签: img超链接标签: a列表标签无语义标签: div & span 三 . 表格标签1. 基本使用2. 合并单元格 四 . …

轮转数组——C语言

题目&#xff1a; 解法1&#xff1a;把最后一位数字移动到数组的第一位&#xff0c;然后将第二位开始的每一位往前移动一位 这种方法的时间复杂度O&#xff08;N^2&#xff09; 空间复杂度O&#xff08;1&#xff09; rotate(int* arr, int n, int k) {k % n;int i 0;for (i …

实战项目 在线学院springcloud调用篇3

一 springcloud与springboot的关系 1.1 关系 1.2 版本关系 二 案例工程 2.1 工程结构 2.2 调用关系 2.3 注册的配置 1.nacos的搭建部署 2.vod&#xff0c;edu项目的注册nacos 3.查看

如何使用Wireshark进行网络流量分析?

如何使用Wireshark进行网络流量分析。Wireshark是一款强大的网络协议分析工具&#xff0c;可以帮助我们深入了解网络通信和数据流动。 1. 什么是Wireshark&#xff1f; Wireshark是一个开源的网络协议分析工具&#xff0c;它可以捕获并分析网络数据包&#xff0c;帮助用户深入…

11、vue3

一、为什么要学 Vue3 1.1 为什么要学 Vue3 1.2 Vue3的优势 1.3 Vue2 选项式 API vs Vue3 组合式API Vue3 组合式API vs Vue2 选项式 API 二、create-vue搭建Vue3项目 2.1 认识 create-vue 2.2 使用create-vue创建项目 前提环境条件 已安装 16.0 或更高版本的 Node.js node -…

iOS 如何对整张图分别局部磨砂,并完全贴合

官方磨砂方式 - (UIVisualEffectView *)effectView{if(!_effectView){UIBlurEffect *blur [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];_effectView [[UIVisualEffectView alloc] initWithEffect:blur];}return _effectView; }使用这种方式对一张图的上半部分和…

Andriod Studio不支持项目指定的Gradle插件版本

1&#xff0c;问题描述&#xff1a; 我之前的项目执行编译之类的是OK的&#xff0c;download下来最新的代码之后&#xff0c;发现很多编译错误&#xff0c;用gradle更新一下依赖包&#xff0c;发现出现下面的错误 The project is using an incompatible version (AGP 8.1.0) …

基于微信小程序的仓储进销存管理系统_r275i

随着科学研究的不断深入&#xff0c;有关仓储的各种信息量不断成倍增长。面对庞大的信息量&#xff0c;就需要有仓储管理系统来提高管理工作的效率。通过这样的系统&#xff0c;我们可以做到信息的规范管理和快速查询&#xff0c;从而减少了管理方面的工作量。 建立仓储管理系…