当前位置: 首页 > news >正文

一文读懂什么是Go语言goroutine

1. 进程、线程和协程的区别

  • 进程: 进程是具有一定独立功能的程序,进程是系统资源分配和调度的最小单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。

进程就是应用程序的启动实例。比如我们运行一个游戏,打开一个软件,就是开启了一个进程。

  • 线程: 线程是进程的一个实体,线程是内核态,而且是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。

线程从属于进程,是程序的实际执行者。一个进程至少包含一个主线程,也可以有更多的子线程。

  • 协程: 协程是一种用户态的轻量级线程,协程的调度完全是由用户来控制的。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

协程,是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。最重要的是,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态中执行)。

  • 线程和协程的区别

    1. 线程切换需要陷入内核,然后进行上下文切换,而协程在用户态由协程调度器完成,不需要陷入内核,这样代价就小了。
    2. 协程的切换时间点是由调度器决定,而不是由系统内核决定的,尽管它们的切换点都是时间片超过一定阈值,或者是进入 I/O 或睡眠等状态时。
    3. 基于垃圾回收的考虑,Go 实现了垃圾回收,但垃圾回收的必要条件是内存位于一致状态,因此就需要暂停所有的线程。如果交给系统去做,那么会暂停所有的线程使其一致。对于 Go 语言来说,调度器知道什么时候内存位于一致状态,所以也就没有必要暂停所有运行的线程。

2. 介绍一下 Goroutine

Goroutine 是一个与其他 goroutines 并行运行在同一地址空间的 Go 函数或方法。

协程(goroutine) 轻量级线程, goroutine 是由 Go 的运行时(runtime)调度和管理的。Go 程序会智能地将 goroutine 中的任务合理地分配给每个 CPU。它在语言层面已经内置了调度和上下文切换的机制。

goroutine 是 Go 并发设计的核心,也叫协程,它比线程更加轻量,因此可以同时运行成千上万个并发任务。在 Go 语言中,每一个并发的执行单元叫作一个 goroutine。我们只需要在调用的函数前面添加 go 关键字,就能使这个函数以协程的方式运行。

3. context 包结构原理和用途

Context(上下文)是 Golang 应用开发常用的并发控制技术 ,它可以控制一组呈树状结构的 goroutine,每个 goroutine 拥有相同的上下文。Context 是并发安全的,主要是用于控制多个协程之间的协作、取消操作。

Context 只定义了接口,凡是实现该接口的类都可称为是一种 context。contex理分析

  • 「Deadline」 方法:可以获取设置的截止时间,返回值 deadline 是截止时间,到了这个时间,Context 会自动发起取消请求,返回值 ok 表示是否设置了截止时间。
  • 「Done」 方法:返回一个只读的 channel ,类型为 struct {}。如果这个 chan 可以读取,说明已经发出了取消信号,可以做清理操作,然后退出协程,释放资源。
  • 「Err」 方法:返回 Context 被取消的原因。
  • 「Value」 方法:获取 Context 上绑定的值,是一个键值对,通过 key 来获取对应的值。

4. goroutine 调度

GMP 是 Go 语言运行时(runtime)层面的实现,是 go 语言自己实现的一套调度系统。区别于操作系统调度 OS 线程。

  1. G 很好理解,就是个 goroutine 的,里面除了存放本 goroutine 信息外 还有与所在 P 的绑定等信息。
  2. P 管理着一组 goroutine 队列,P 里面会存储当前 goroutine 运行的上下文环境(函数指针,堆栈地址及地址边界),P 会对自己管理的 goroutine 队列做一些调度(比如把占用 CPU 时间较长的 goroutine 暂停、运行后续的 goroutine 等等)当自己的队列消费完了就去全局队列里取,如果全局队列里也消费完了会去其他 P 的队列里抢任务。
  3. M(machine)是 Go 运行时(runtime)对操作系统内核线程的虚拟, M 与内核线程一般是一一映射的关系, 一个 groutine 最终是要放到 M 上执行的;

    P 与 M 一般也是一一对应的。他们关系是: P 管理着一组 G 挂载在 M 上运行。当一个 G 长久阻塞在一个 M 上时,runtime 会新建一个 M,阻塞 G 所在的 P 会把其他的 G 挂载在新建的 M 上。当旧的 G 阻塞完成或者认为其已经死掉时 回收旧的 M。

    P 的个数是通过 runtime.GOMAXPROCS 设定(最大 256),Go1.5 版本之后默认为物理线程数。 在并发量大的时候会增加一些 P 和 M,但不会太多,切换太频繁的话得不偿失。

    单从线程调度讲,Go 语言相比起其他语言的优势在于 OS 线程是由 OS 内核来调度的,goroutine 则是由 Go 运行时(runtime)自己的调度器调度的,这个调度器使用一个称为 m:n 调度的技术(复用 / 调度 m 个 goroutine 到 n 个 OS 线程)。 其一大特点是 goroutine 的调度是在用户态下完成的, 不涉及内核态与用户态之间的频繁切换,包括内存的分配与释放,都是在用户态维护着一块大的内存池, 不直接调用系统的 malloc 函数(除非内存池需要改变),成本比调度 OS 线程低很多。 另一方面充分利用了多核的硬件资源,近似的把若干 goroutine 均分在物理线程上, 再加上本身 goroutine 的超轻量,以上种种保证了 go 调度方面的性能。

5. 如何避免 Goroutine 泄露和泄露场景

gorouinte 里有关于 channel 的操作,如果没有正确处理 channel 的读取,会导致 channel 一直阻塞住,goroutine 不能正常结束

  • channel 操作不当:
package mainimport ("fmt""time"
)func main() {// 问题场景: 在 Goroutine 中创建 channel,但没有相应的 channel 读取操作go func() {ch := make(chan int)ch <- 1 // 这里会导致 Goroutine 永久阻塞}()time.Sleep(5 * time.Second)fmt.Println("Program ended")
}

在上面的代码中,我们创建了一个 Goroutine,它向一个无人接收的 channel 写入数据。这会导致该 Goroutine 永久阻塞,从而造成 Goroutine 泄露。

解决方法如下:

package mainimport ("fmt""time"
)func main() {// 解决方法: 确保 channel 的读取操作与写入操作相匹配ch := make(chan int)go func() {x := <-ch // 从 channel 中读取数据fmt.Println("Received value:", x)}()ch <- 1 // 向 channel 中写入数据time.Sleep(5 * time.Second)fmt.Println("Program ended")
}

在这个解决方案中,我们在创建 Goroutine 之前先创建了 channel,并在 Goroutine 中读取 channel 中的数据。这样可以确保 Goroutine 能够正常退出,避免泄露。

  • context 使用不当:
package mainimport ("context""fmt""time"
)func main() {// 错误示例: 在 Goroutine 中使用 context,但没有正确地取消 contextctx, _ := context.WithTimeout(context.Background(), 2*time.Second)go func() {doSomething(ctx)}()time.Sleep(5 * time.Second)fmt.Println("Program ended")
}func doSomething(ctx context.Context) {for {// 不检查 ctx.Done() 就一直执行time.Sleep(time.Second)fmt.Println("Doing something...")}
}

在上面的代码中,我们创建了一个 Goroutine,并向其传递了一个 context。但是,我们没有在 doSomething 函数中正确地处理 context 的取消。这意味着,即使 context 已经超时,Goroutine 仍然会一直执行下去,而不会退出。,从而造成 Goroutine 泄露。

解决方法如下:

package mainimport ("context""fmt""time"
)func main() {// 解决方法: 在 Goroutine 中正确地处理 context 的取消ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)defer cancel()go func() {doSomething(ctx)}()time.Sleep(5 * time.Second)fmt.Println("Program ended")
}func doSomething(ctx context.Context) {for {select {case <-ctx.Done():fmt.Println("Context canceled, exiting Goroutine")returndefault:// 执行一些操作time.Sleep(time.Second)fmt.Println("Doing something...")}}
}

在解决方案中,我们在 doSomething 函数中使用 select 语句来检查 context 是否已被取消。如果 context 已被取消,Goroutine 就会安全地退出,避免 Goroutine 泄露。

6. waitgroup 用法和原理

waitgroup 内部维护了一个计数器,当调用 wg.Add(1) 方法时,就会增加对应的数量;当调用 wg.Done() 时,计数器就会减一。直到计数器的数量减到 0 时,就会调用
runtime_Semrelease 唤起之前因为 wg.Wait() 而阻塞住的 goroutine。

使用方法:

  1. main 协程通过调用 wg.Add (delta int) 设置 worker 协程的个数,然后创建 worker 协程;
  2. worker 协程执行结束以后,都要调用 wg.Done ();
  3. main 协程调用 wg.Wait () 且被 block,直到所有 worker 协程全部执行结束后返回。

    实现原理:

    • WaitGroup 主要维护了 2 个计数器,一个是请求计数器 v,一个是等待计数器 w,二者组成一个 64bit 的值,请求计数器占高 32bit,等待计数器占低 32bit。

    • 每次 Add 执行,请求计数器 v 加 1,Done 方法执行,请求计数器减 1,v 为 0 时通过信号量唤醒 Wait ()。

相关文章:

一文读懂什么是Go语言goroutine

1. 进程、线程和协程的区别 进程: 进程是具有一定独立功能的程序&#xff0c;进程是系统资源分配和调度的最小单位。每个进程都有自己的独立内存空间&#xff0c;不同进程通过进程间通信来通信。由于进程比较重量&#xff0c;占据独立的内存&#xff0c;所以上下文进程间的切换…...

计算机毕业设计 农家乐管理平台 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…...

Spring Boot优缺点

Spring Boot 是一款用于简化Spring应用开发的框架&#xff0c;它集成了大量常用的框架和工具&#xff0c;大大简化了Spring项目的配置和部署。下面是Spring Boot的优缺点&#xff1a; 优点&#xff1a; 简化配置&#xff1a;Spring Boot自动配置功能可以根据应用的依赖自动配…...

Android Studio中创建apk签名文件

本文以macOS中Android Studio 2021.1.1版本为例介绍创建apk签名文件的操作步骤&#xff1a; 1.启动Android Studio&#xff0c;并打开一个Android项目。 2.依次点击菜单&#xff1a;Build -> Generate Signed Bundle / APK...。 3.在弹出的"Generate Signed Bundle or …...

CRC32 JAVA C#实现

项目中用到CRC32进行校验得地方&#xff0c;需要用到C#和java进行对比&#xff1a; 一、C#实现&#xff1a; class CRC32Cls { protected ulong[] Crc32Table; //生成CRC32码表 public void GetCRC32Table() { ulong Crc; …...

本科阶段最后一次竞赛Vlog——2024年智能车大赛智慧医疗组准备全过程——5Webscoket节点的使用

本科阶段最后一次竞赛Vlog——2024年智能车大赛智慧医疗组准备全过程——5Webscoket节点的使用 ​ 有了前面几篇文章的铺垫&#xff0c;现在已经可以实现我到手测试那一步的 1.解读usb_websocket_display.launch.py ​ 首先进入这个目录/root/dev_ws/src/origincar/originca…...

深入学习小程序第二天:事件处理与用户交互

一、概念 1. 事件绑定与类型 在小程序中&#xff0c;通过在组件上添加特定的属性&#xff08;如 bind 开头的属性&#xff09;来绑定事件处理函数&#xff0c;以响应用户的交互操作。常见的事件类型包括触摸事件、表单事件和系统事件&#xff1a; 触摸事件&#xff1a;用于响…...

操作系统快速入门(一)

&#x1f600;前言 本篇博文是关于操作系统的&#xff0c;希望你能够喜欢 &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家&#xff0c;您的满意是我的动力&#x1f609;&…...

Spring Cloud微服务性能优化:策略、实践与未来趋势

标题&#xff1a;Spring Cloud微服务性能优化&#xff1a;策略、实践与未来趋势 摘要 在微服务架构中&#xff0c;服务调用链路的性能优化是确保系统高效运行的关键。Spring Cloud作为微服务架构的主流实现之一&#xff0c;提供了多种工具和方法来优化服务间的调用。本文将深…...

秒懂C++之多态

目录 一. 多态的概念 二. 多态的定义及实现 多态的构成条件 虚函数重写的例外 协变(基类与派生类虚函数返回值类型不同) 析构函数的重写(基类与派生类析构函数的名字不同) 练习例题 final override 重载、覆盖(重写)、隐藏(重定义)的对比 三. 抽象类 四. 多态的原理…...

C语言:求最大数不用数组

&#xff08;1&#xff09;题目&#xff1a; 输入一批正数用空格隔开&#xff0c;个数不限&#xff0c;输入0时结束循环&#xff0c;并且输出这批整数的最大值。 &#xff08;2&#xff09;代码&#xff1a; #include "stdio.h" int main() {int max 0; // 假设输入…...

零门槛成为HelpLook推荐官,邀好友加入,奖励享不停!

什么&#xff01;&#xff1f; 还有谁不知道HelpLook推荐官计划&#xff01; 只需要简单地注册加入 在好友成功订阅套餐之后 可一次性获得20%的丰厚现金返佣 HelpLook是一款快速搭建AI知识库的系统&#xff0c;并帮助企业0代码搭建帮助中心、FAQs、SOPs、产品文档、说明书和…...

基于python的图书馆大数据可视化分析系统设计与实现

博主介绍&#xff1a; 大家好&#xff0c;本人精通Java、Python、C#、C、C编程语言&#xff0c;同时也熟练掌握微信小程序、Php和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我有丰富的成品Java、Python、C#毕设项目经验&#xff0c;能够为学生提供各类…...

利用formdata自动序列化和xhr上传表单到后端

//FormData对象是XMLHTTPRequest level2新增的类型&#xff0c;它可以自动序列化表单内容&#xff0c;不再需要我们去写序列化表单方法&#xff1b; FormData()即可以直接把整个表单给它&#xff0c;也可以分别使用append(‘字段’,‘值’)方法给FormData()&#xff1b; 现在就…...

视频号小店大地震?还好我看了原文

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 我X&#xff0c;如果不是看了原文&#xff0c;我差点也上当了。虽然视频号小店关闭了450个类目&#xff0c;但又重新开放了412个类目啊。 昨天&#xff08;8月9日&#xff09;&#xff0c;视频号一口气发了10个公…...

Genymotion adb shell

Genymotion 账户是 qq邮箱 参考 Ubuntu 20.04 安装 Android 模拟器 Genymotion https://www.zzzmh.cn/post/553cd96d4e47490a90b3302a76a93c0d Genymotion adb shell adb shell C:\Program Files\Genymobile\Genymotion\tools>adb shell lsusb Bus 001 Device 001: ID …...

探索AI与社交的交汇点:看Facebook如何引领智能化革命

在当今数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;正成为各大科技公司变革的重要驱动力。作为全球领先的社交媒体平台&#xff0c;Facebook&#xff08;现Meta Platforms&#xff09;正处于这一智能化革命的前沿。通过不断创新和应用AI技术&#xff0c;Facebook…...

JVM 加载阶段 Class对象加载位置是在 堆中还是方法区?

在JVM&#xff08;Java虚拟机&#xff09;的类加载过程中&#xff0c;Class对象的加载位置涉及到堆&#xff08;Heap&#xff09;和方法区&#xff08;Method Area&#xff09;两个关键区域。具体来说&#xff0c;类的加载阶段涉及到将类的.class文件中的二进制数据读入到内存中…...

Android 获取短信验证

Android 获取短信验证 Android 获取短信验证 输入发短信的手机号&#xff0c;点击获取验证码&#xff0c;等接收到验证码后就会自动获取 SmsReceiver.Java import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; impor…...

负载均衡详细概念介绍之(四层和七层实现)

目录 一、负载均衡介绍 1.1什么是负载均衡 ​编辑 1.2 为什么要用负载均衡 二、负载均衡的类型 2.1 通过一些硬件实现 2.2 四层负载均衡 2.3 七层负载均衡 三、四层和七层的区别 及特点 一、负载均衡介绍 1.1什么是负载均衡 负载均衡:Load Balance&#xff0c;简称LB&a…...

【网络安全产品大调研系列】2. 体验漏洞扫描

前言 2023 年漏洞扫描服务市场规模预计为 3.06&#xff08;十亿美元&#xff09;。漏洞扫描服务市场行业预计将从 2024 年的 3.48&#xff08;十亿美元&#xff09;增长到 2032 年的 9.54&#xff08;十亿美元&#xff09;。预测期内漏洞扫描服务市场 CAGR&#xff08;增长率&…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

ios苹果系统,js 滑动屏幕、锚定无效

现象&#xff1a;window.addEventListener监听touch无效&#xff0c;划不动屏幕&#xff0c;但是代码逻辑都有执行到。 scrollIntoView也无效。 原因&#xff1a;这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作&#xff0c;从而会影响…...

在WSL2的Ubuntu镜像中安装Docker

Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包&#xff1a; for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题

在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件&#xff0c;这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下&#xff0c;实现高效测试与快速迭代&#xff1f;这一命题正考验着…...

AI,如何重构理解、匹配与决策?

AI 时代&#xff0c;我们如何理解消费&#xff1f; 作者&#xff5c;王彬 封面&#xff5c;Unplash 人们通过信息理解世界。 曾几何时&#xff0c;PC 与移动互联网重塑了人们的购物路径&#xff1a;信息变得唾手可得&#xff0c;商品决策变得高度依赖内容。 但 AI 时代的来…...

【Go语言基础【13】】函数、闭包、方法

文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数&#xff08;函数作为参数、返回值&#xff09; 三、匿名函数与闭包1. 匿名函数&#xff08;Lambda函…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf

FTP 客服管理系统 实现kefu123登录&#xff0c;不允许匿名访问&#xff0c;kefu只能访问/data/kefu目录&#xff0c;不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...

4. TypeScript 类型推断与类型组合

一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式&#xff0c;自动确定它们的类型。 这一特性减少了显式类型注解的需要&#xff0c;在保持类型安全的同时简化了代码。通过分析上下文和初始值&#xff0c;TypeSc…...