探索Go语言中的Goroutine并发机制

什么是Goroutine

在Go语言中,Goroutine 是程序中最基本的并发单位。事实上,每个Go程序都会自动创建一个goroutine,那就是主goroutine,程序启动时会立即执行。Goroutine是Go语言中处理并发问题的核心工具,因此理解它的工作原理至关重要。

简而言之,Goroutine是并发执行的函数,这些函数与其他代码并行运行(注意:并不是并行,而是并发!)。你可以通过在函数前面加上 go 关键字来启动一个Goroutine:

func main() {go sayHello()// 继续执行其他操作
}func sayHello() {fmt.Println("你好")
}

这个例子中,sayHello() 函数将在一个新的goroutine中执行,而主goroutine将继续执行其他代码。你会发现Goroutine非常简单,只需一个关键字就可以创建并发执行的逻辑块。

Goroutine不仅仅适用于命名函数,匿名函数同样可以使用Goroutine:

go func() {fmt.Println("你好")
}()
// 继续执行其他操作

在这个例子中,匿名函数立即被执行,并在一个新的Goroutine中并发运行。

这种简洁的语法让Go程序员能够轻松地处理并发任务,但更重要的是要理解其背后的机制。我们将深入探讨Goroutine的工作原理,包括它们如何调度和管理。

Goroutine的幕后工作

Goroutine并不是操作系统线程,也不是传统意义上的绿色线程(语言运行时管理的线程),它们是更高层次的抽象,称为协程。协程本质上是非抢占式的并发子程序(函数、闭包等),也就是说,它们不会在任意时刻被中断,而是在特定的挂起或重新进入点进行切换。

Go语言的运行时与Goroutine紧密结合,它会自动在Goroutine阻塞时暂停它们,待其解除阻塞后再恢复执行。这使得Goroutine在某种程度上是可抢占的,但这种抢占只发生在它们被阻塞的点上。这种机制使Goroutine成为一种特殊的协程类型。

Goroutine的调度由Go的M:N调度器处理,即将M个Goroutine映射到N个操作系统线程上。当Goroutine的数量超过可用的绿色线程时,调度器会合理地分配这些Goroutine,以确保系统资源的高效利用。

Fork-Join并发模型

Go的并发模型基于一种称为fork-join的模型。在程序运行的任何时刻,它都可以通过 go 语句分支出新的并发执行路径,这就是“fork”。而这些并发执行的路径最终会在某个点重新汇合,这就是“join”。

例如:

sayHello := func() {fmt.Println("你好")
}
go sayHello()
// 继续执行其他操作

在这个例子中,sayHello 函数会在它自己的Goroutine中执行,主程序则继续运行。然而,这个例子存在一个问题:我们无法保证 sayHello 函数会执行,因为主goroutine可能会在 sayHello 被调度之前就退出。为了保证程序的正确性,我们需要一个同步点来等待所有的Goroutine完成。

同步Goroutine

可以使用 sync.WaitGroup 来同步主goroutine和其他goroutine,确保主程序在其他并发任务完成之前不会退出:

var wg sync.WaitGroupsayHello := func() {defer wg.Done()fmt.Println("你好")
}wg.Add(1)
go sayHello()
wg.Wait()

在这个例子中,wg.Wait() 会阻塞主goroutine,直到 sayHello 所在的goroutine完成。通过这种方式,我们可以确保 sayHello 的执行结果输出为“你好”。

Goroutine和闭包

Goroutine可以捕获并操作闭包中的变量。例如:

var wg sync.WaitGroup
salutation := "你好"wg.Add(1)
go func() {defer wg.Done()salutation = "欢迎"
}()
wg.Wait()
fmt.Println(salutation)

在这个例子中,salutation 变量被goroutine修改,因此输出的结果是“欢迎”。

然而,在循环中使用闭包时,情况变得稍微复杂一些:

var wg sync.WaitGroupfor _, salutation := range []string{"你好", "问候", "早安"} {wg.Add(1)go func() {defer wg.Done()fmt.Println(salutation)}()
}
wg.Wait()

很多人会认为输出顺序是不确定的“你好”、“问候”和“早安”,但实际上输出可能是三次“早安”。这是因为goroutine捕获了循环变量 salutation,而在goroutine执行时,这个变量可能已经被赋值为最后一个值了。

正确的做法是传递每次循环中的变量副本:

for _, salutation := range []string{"你好", "问候", "早安"} {wg.Add(1)go func(salutation string) {defer wg.Done()fmt.Println(salutation)}(salutation)
}
wg.Wait()

通过这样传递参数,我们确保每个goroutine都操作自己独立的变量副本,输出结果会如我们预期的那样。

Goroutine的轻量级优势

Goroutine非常轻量化,创建一个新的Goroutine仅消耗几KB的内存,而Go的运行时会动态调整栈的大小,确保内存使用的最小化。因此,在同一地址空间中可以创建成千上万个Goroutine而不会耗尽系统资源。

例如,下面的代码可以测量Goroutine的内存消耗:

memConsumed := func() uint64 {runtime.GC()var s runtime.MemStatsruntime.ReadMemStats(&s)return s.Sys
}var c <-chan interface{}
var wg sync.WaitGroup
noop := func() { wg.Done(); <-c }
const numGoroutines = 1e4wg.Add(numGoroutines)
before := memConsumed()for i := numGoroutines; i > 0; i-- {go noop()
}wg.Wait()
after := memConsumed()fmt.Printf("%.3fkb", float64(after-before)/numGoroutines/1000)

结果显示,每个Goroutine的平均内存消耗大约为2.8KB。

Goroutine的上下文切换

在操作系统层面,线程之间的上下文切换非常昂贵,因为需要保存寄存器值、内存映射等。而在Go的调度器中,Goroutine的上下文切换成本相对较低,调度器只需要保存必要的状态。

下图展示了Goroutine与操作系统线程之间的上下文切换速度比较:

go test -bench=. -cpu=1
BenchmarkContextSwitch 5000000 225 ns/op PASS

在这个基准测试中,Goroutine的上下文切换速度比操作系统线程快了92%。

总结

通过本文的讨论,你应该了解了如何启动Goroutine及其工作原理。Goroutine作为Go语言中处理并发的核心工具,其轻量级和高效的特性使得我们可以轻松地创建成千上万个并发任务。同时,利用 sync.WaitGroup 等工具进行Goroutine的同步,可以确保程序的正确性和稳定性。

Goroutine是Go语言中非常强大的并发构件,既适合简单的并发任务,也能应对复杂的大规模并发场景。在实际开发中,只要需求合适,不妨大胆使用Goroutine!

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

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

相关文章

【Linux】Image、zImage与uImage的区别

1、Image 1.1 什么是 Image Image 是一种未压缩的 Linux 内核镜像文件&#xff0c;包含了内核的所有代码、数据和必要的元信息。它是 Linux 内核在编译过程中生成的一个原始的二进制文件&#xff0c;未经过任何压缩或额外的封装处理。由于未压缩&#xff0c;Image 文件相对较…

C语言刷题日记(附详解)(5)

一、选填部分 第一题: 下面代码在64位系统下的输出为( ) void print_array(int arr[]) {int n sizeof(arr) / sizeof(arr[0]);for (int i 0; i < n; i)printf("%d", arr[i]); } int main() {int arr[] { 1,2,3,4,5 };print_array(arr);return 0; } A . 1…

Arcgis实现面空间位置从东至西从南至北排序

效果 背景 工作项目中经常会遇到需要对网格进行编号,而编号是有一定原则的,比如空间位置从上到下从左到右,或者其它原则,那么都可以通过下面的方式来实现 1、准备数据 点shp文件,查看初始FID字段标注,目前是一个无序的状态 2、排序 字段选择空间字段,空间排序方法…

02请求响应(简单参数)

一、操作目的 前端通过post/get请求&#xff0c;传递给后端简单的数据&#xff0c;后端接收后在控制台打印出来&#xff0c;并将结果返回给前端页面展示出来。&#xff08;这里我们用postman来模拟前端页面&#xff0c;而非真实的通过编写前端代码&#xff0c;通过浏览器来展示…

k8s /apis/batch/v1beta1 /apis/policy/v1beta1 接口作用

在 Kubernetes 中&#xff0c;/apis/batch/v1beta1 和 /apis/policy/v1beta1 是两个 API 组的版本化接口&#xff0c;它们用于处理不同类型的资源和操作。 1. /apis/batch/v1beta1: API 组: batch版本: v1beta1资源类型: 此接口通常用于处理 Kubernetes 中的批量资源&#xf…

STM32与ESP8266的使用

串口透传 “透传”通常指的是数据的透明传输&#xff0c;意思是在不对数据进行任何处理或修改的情况下&#xff0c;将数据从一个接口转发到另一个接口。值得注意的是要避免串口之间无限制的透明&#xff0c;可以采用互斥锁的方式进行限制使用方法 对USART1和USART3(用他俩举例…

【ArcGIS Pro实操第一期】研究区域制图-以粤港澳GBA地区为例

ArcGIS Pro实操第一期&#xff1a;研究区域制图-以粤港澳GBA地区为例 数据准备1 ArcGIS Pro绘制研究区域图1.1 基本处理1.2 导入数据并制图1.3 添加整饰要素 参考 数据准备 DEM高程数据市区边界文件&#xff08;.shp文件&#xff09; 目标成图如下&#xff1a; 1 ArcGIS Pr…

TalkSphere项目介绍

TalkSphere项目介绍 文章目录 TalkSphere项目介绍一、前言二、技术栈及开发环境三、主要功能&#xff08;一&#xff09;用户登录与注册&#xff08;二&#xff09;用户历史消息展示&#xff08;三&#xff09;在线用户实时聊天 四、结语 一、前言 在线聊天室作为一个虚拟社交…

《网络协议 - HTTP传输协议及状态码解析》

文章目录 一、HTTP协议结构图二、HTTP状态码解读1xx: 信息响应类2xx: 成功响应类3xx: 重定向类4xx: 客户端错误类5xx: 服务器错误类 一、HTTP协议结构图 二、HTTP状态码解读 HTTP状态码&#xff08;英语&#xff1a;HTTP Status Code&#xff09;是用以表示网页服务器超文本传…

安卓玩机工具-----无需root权限 卸载 禁用 删除当前机型app应用 ADB玩机工具

ADB玩机工具 ADB AppControl是很实用的安卓手机应用管理工具&#xff0c;无需root权限&#xff0c;通过usb连接电脑后&#xff0c;可以很方便的进行应用程序安装与卸载&#xff0c;还支持提取手机应用apk文件到电脑上&#xff0c;此外还有手机系统垃圾清理、上传文件等…

面试题总结(四) -- STL与算法篇

面试题总结(四) – STL与算法篇 文章目录 面试题总结(四) -- STL与算法篇<1> 请列举 C STL 中常用的容器&#xff08;如 vector、list、map 等&#xff09;及其特点。<2> 如何在 C 中使用 STL 算法&#xff08;如排序、查找等&#xff09;&#xff1f;<3> 解…

QUIC的loss detection学习

PTO backoff backoff 补偿 /ˈbkɒf/PTO backoff 是QUIC&#xff08;Quick UDP Internet Connections&#xff09;协议中的一种机制&#xff0c;用于处理探测超时&#xff08;Probe Timeout, PTO&#xff09;重传策略 它逐步增加探测超时的等待时间&#xff0c;以避免网络拥塞…

[网络]http请求中的URL,方法,header 和 http响应中的状态码

文章目录 一. http请求1. 认识URLurlencode 2. 认识方法应用场景构造http请求 2. 认识请求报头header 二. http响应1. 状态码 一. http请求 1. 认识URL 我们所说的"网址", 其实就是URL(Uniform Resource Locator 统⼀资源定位符) 1.协议方案名 常见的有http和http…

警惕!甲状腺病的早期“信号弹”,你捕捉到了吗?

在快节奏的现代生活中&#xff0c;健康问题往往被忽视&#xff0c;尤其是那些初期症状不明显、容易被误解或忽视的疾病。甲状腺&#xff0c;这个位于颈部下方、形状如蝴蝶的小腺体&#xff0c;虽然不起眼&#xff0c;却是人体内分泌系统中的重要一员&#xff0c;负责调节新陈代…

AIGC:python 文生图代码(python + stable-diffusion+ cuda)

解决什么问题 搭建一个文生图环境 + python文生图代码,通过输入prompt,生成图片 解决方法 文生图的代码比较简易,主要是搭建环境+下载文生图大模型 步骤一: 创建虚拟环境(用python 3.10版本,起得名字最好可以一看就知道Python的版本) conda create -n text2img3.10 py…

【RAG】RAG再进化?基于长期记忆的检索增强生成新范式-MemoRAG

前言 RAG现在工作很多&#xff0c;进化的也很快&#xff0c;再来看看一个新的RAG工作-MemoRAG。 文章提出&#xff0c;RAG在减少大模型对于垂类知识的问答幻觉上取得了不错的效果&#xff0c;也成为私域知识问答的一种范式。然而&#xff0c;传统RAG系统主要适用于明确信息需…

HTML5超酷炫的水果蔬菜在线商城网站源码系列模板1

文章目录 1.设计来源1.1 主界面1.2 商品列表界面1.3 商品详情界面1.4 其他界面 2.效果和源码2.1 动态效果2.2 源代码 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/142059238 HTML5超酷炫的水果蔬菜在线商城网…

LSS中的BevEncoder

LSS中的BevEncoder其实很简单,主要是使用了resnet18来提取生成的(200x200)BEV图中的bev特征。 class BevEncode(nn.Module):def __init__(self, inC, outC):super(BevEncode

深入理解Go语言中的接口定义与使用

在Go语言的编程实践中&#xff0c;接口&#xff08;Interface&#xff09; 是一个强大而灵活的特性&#xff0c;它允许我们定义一组方法&#xff0c;而不需要指定这些方法的具体实现。通过接口&#xff0c;我们可以将不同类型的值组合在一起&#xff0c;只要它们实现了接口中定…

软件工程毕业设计开题汇总

文章目录 &#x1f6a9; 1 前言1.1 选题注意事项1.1.1 难度怎么把控&#xff1f;1.1.2 题目名称怎么取&#xff1f; 1.2 开题选题推荐1.2.1 起因1.2.2 核心- 如何避坑(重中之重)1.2.3 怎么办呢&#xff1f; &#x1f6a9;2 选题概览&#x1f6a9; 3 项目概览题目1 : 大数据电商…