go语音进阶 Goroutine

什么是 Goroutine?

在Go语言中 是通过 ‘协程’ 实现并发 Goroutine 是 Go 语言特有的名词, 区别于进程 Process, 线程Thread, 协程 Coroutine, 因为 Go语言的作者们觉得是有所区别的,所有专门创造做 Goroutine.

Goroutine 是与其他函数或方法同时运行的函数或方法。 Goroutine 可以被认为是 轻量级的线程,于线程相比 创建 Goroutine 的成本很小,他就是一段代码,一个函数入口 以及在堆上分配的一个堆栈(初始大小为 4k, 会随着程序的执行 自动增长删除)。 因此它非常廉价。GO应用程序可以并发运行着数千个 Goroutine.

Goruotine在线程上的优势:
  1. 与线程相比,Goroutine非常便宜,它只是堆栈大小的几个KB, 堆栈可以根据应用程序的需要增长或删除。而在线程的情况下。堆栈大小必须指定 并且固定。
  2. Goroutine 被多路复用 到较少的OS线程。在一个程序中可能只有一个线程和数百个Goroutines。如果线程中的任何 Goroutine 都表示等待用户输入,则会创建另一个OS线程,剩下的Goroutines 被转移到新的 OS线程。所有这些都是运行时进行处理的。我们作为程序编写者从这些复杂的 细节中抽象出来, 并得到一个只与并发工作相关的 干净API
  3. 当使用Goroutine 访问共享内存时,通过设计好的通道 可以防止竞态条件的发生,通道可以被认为是 Goroutine 的通信管道
主Goroutine:

封装main函数的Goroutine 称为 主Goroutine,

主Goroutine 所做的事情并不是执行main函数这么简单,它首先要做的是:假设每一个Goroutine 所能申请的栈控件的最大尺寸,在32位计算机系统中 次尺寸为 256MB,而64位计算机中的尺寸为1GB, 如果有某个 goroutine 的栈空间尺寸大于这个限制,那么运行时系统就会引发一个 栈溢出(stack overflow)的运行时错误,随后 这个Go 程序也会终止。

此后 主goroutine 会进行一系列的初始化工作, 涉及的工作内容大致如下:

  1. 创建一个特殊的 defer语句,用于在主 goroutine 退出时要做的必要处理。应为主 goroutine 也可能非正常的结束
  2. 创建专用于在后台清扫垃圾内存的 gorontine ,并设置 GC 可用的标识
  3. 执行main 包中的 init 函数
  4. 执行main 函数,执行main 函数之后,它还会检查主 goroutine 是否引发了 运行时错误,并进行必要的处理。最后主 gorontine 才会结束自己以及当前运行的进程
 如何使用 goruntine:

在函数 或者方法调用前 加上关键字go, 你将会同时运行一个新的 goroutine.

package mianimport("fmt"
)func hello(){fmt.Println("hello world goroutine")
}func main(){go hello()fmt.Println("hello main func")
}

运行结果: 可能会输出:hello main func

Goroutine 执行的规则:

  1. 当新的 goroutine 开始时, goroutine 调用调用立即返回。 与函数不同 go 关键字 不等待 gorontine执行结束。 当goroutine 调用时 goroutine 的人哈返回值都会被忽略。 公会立即执行到下一行代码。
  2. main 中的 goroutine 应该为其他 goroutine 执行。如果main的 goroutine 终止了,程序也将被终止。 而其他 Goroutine 将不会被运行。

 Go 语言的并发模型(原理)

Go  语言相比 java c# 语言的一个很大优势 就是可以很方便的编写 并发程序,Go语言内置了 Goroutine 机制,使用 goroutine 可以快速编写出优秀的并发程序。 更好的利用处理器资源,接下来我们来了解 Go语言的并发原理。

线程模型:

        在现在操作系统中,线程是处理调度分配计算机资源的基本单位进程则作为资源拥有基本单位。每个进程是由虚拟的私有地址空间 代码 数据 和其他各种系统资源组成。 线程是进程内部的一个执行单元。每一个进程至少有一个主执行线程,它无需由用户主动去创建,是由系统自动创建。 用户根据需要在应用程序中创建其他线程。多个线程并发的运行同一个进程中。

        我们先从线程讲起无论语言层面何种并发模型。到了操作系统层面 一定是以线程的形态曾在。而操作系统根据资源的访问权限不同体系架构可以分为 用户空间 内核空间 内核空间主要操作访问 CPU 资源, I/O资源,内存资源等硬件资源, 为上层应用程序提供最基本的基础资源用户空间 就是上层应用程序固定活动的空间, 用户空间不可以直接访问资源。必须通过 系统调用, 函数库, 或者 shell 脚本 来调用内核空间提供的资源

        我们现在计算机语言 可以狭义的认为是一种 软件,他们中所谓的 线程 往往是 用户态的线程, 和 操作系统本身内核态的线程(简称KES)还是有很大区别的

        Go 并发编程模型在 底层由操作系统提供的线程库支撑的,因此还是得用线程实现模型说起:

        线程可以视为进程中的控制流:一个进程至少会包含一个线程,因此其中至少有一个控制流 持续运行,因而 一个进程的 第一个线程会随着这个进程启动而创建。这个线程称为改进程的主线程。当然一个进程也可以包含多个线程。这些线程都是当前进程已经存在的线程创建的。创建的方法就是调用系统调用,更确切的说就是调用 pthread create函数,拥有多个线程的进程可以并发执行多任务,并且计时某个 或 某些任务被阻塞,也不会影响其他线程的执行,这可以大大改善程序的响应时间和吞吐量 另一方面线程不可以独立于进程的存在。他的生命周期不可能逾越其所在的进程生命周期

线程的实现模型主要有三个分别是:用户级线程模型内核级线程模型两级线程模型他们之间最大的差异就在于 线程与内核调度实体(Kerner Scheduling Entity 简称KSE)之间的对应关系上。顾名思义 内核调度实体就是可以内内核调用调度的对象。很多文献和书中被称为 内核级线程,也就是操作系统上最小调度单元

 内核级线程模型:

        用户线程与KSE是1对1关系(1:1)大部分编程语言的线程库(如 linux的 pthread, java 的 java.lang.Thread ,C++11 的 std::Thread 等等)都是对操作系统线程(内核线程)的一层封装,创建出来的每一个线程与一个不同的 KSE静态关联,因此其调度完全有OS 调度器来操作这种方式实现简单 直接借助OS提供的线程能力,并且不同用户线程之间一般也不会相互影响。但其创建 销毁 以及多个线程之间的上下文切换等操作 都是由OS 层面来做得在需要使用大量线程的情况下对 OS 的性能影响会比较大每个线程都由内核调度器独立的调度,所有如果一个线程阻塞 则不影响其他的线程

 

优点:

        在多核处理器的硬件支持下,OS内核空间线程模型支持了真正并行,当一个线程被阻塞后,容许另一个线程继续执行,所有并发能力较强

缺点:

        每创建一个用户级线程都需要创建一个内核级别线程与之对应,这样线程开销会比较大,会影响到应用程序的性能

 

 

用户级线程模型:

        用户线程模型 与 KSE 是多对1 关系(M:1), 这种线程的创建,销毁以及多个线程之间的协调等操作都需要用户自己实现的线程库来负责,对于内核透明 一个进程中的所有创建的线程都与 KSE 在运行时动态关联现在有许多语言实现的 协程 基本上都属于这种方式这种实现方式相比内核级别线程可以做到很轻量级, 对系统资源的消耗会小很多。因此可以创建的数量与上下文切花所 花费的资料也会小很多,但是这个模型有一个致命的缺点如果我们的用户线程上调用了阻塞调用(read 网络 IO操作等等),那么一旦 KSE 因阻塞被内核调度出 CPU 的话,剩下的所有对用户的线程都会变为阻塞状态(整个进程挂起)

所以这些语言的协程库 会把自己一些阻塞的操作重新封装为完全的非阻塞形式,然后在自己阻塞的点上,主动让出自己,并通过某种方式通知或唤醒其他待执行的线程在改SKE上运行,从而避免内核调度器由于 SKE 阻塞而做上下文切换,这样整个进程也不会被阻塞

 

 

优点:

        这种模型的好处是 线程上下文切换都发生在用户空间,避免模态切换(mode switch),从而对于性能有积极的影响

缺点:

        所有的线程基于一个内核调度器实体,这意味着只有一个处理器可以被利用,在多处理器环境下 这种是不能接受的。本质上 用户线程只能解决并发的问题,但是没有解决并行问题。如果线程应为 I/O 操作陷入了内核态,内核态线程阻塞等待 I/O 数据,则所有的线程都会被阻塞,用户空间也可以使用非阻塞  I/O 但是不能避免性能以及复杂问题

 

 

两级线程模型: 

        用户线程与 KSE 是多对多关系 (M:N), 这种实现综合了前两种模型的优点,为一个进程中创建多个KSE,并且线程可以与不同的KSE在运行时进行动态关联,当某个KSE由于其上工作的线程的阻塞操作被内核调度出CPU 时当前与其关联的其余用户线程可以重新与其KSE建立关系。当然这种动态关联的实现很复杂,也需要用户自己去实现。这算一种缺点吧,Go 语言中的并发就是使用这种方式实现Go为了实现该模型自己实现了一个运行时调度器来负责Go中的 “线程” 与KSE的动态关联此模型有时也被成为 混合型线程模型,即用户调度器实现用户线程 到 KSE的 “调度”,内核调度器实现KSE到 CPU上的调度

 

 

Go并发调度器:G-P-M模型

 在操作系统提供的内核线程之上Go搭建了一个特有的两级线程模型goroutine 机制实现了 M:N 的 线程模型, gorouttine 机制协程 coroutine 的一种实现,golang 内置调度器,可以让多核CPU 中的每一个 CPU 执行一个协程

 

调度器是如何工作的

有了上面的知识,我们可以开始真正的介绍Go的并发机制,先用一段代码展示一下再 Go 语言中新建一个 “线程”(Go 语言中称为 goroutine)的样子

// 用 go 关键字加上一个函数  (这里是匿名函数)
// 调用就做到了在 一个新的 “线程” 并发执行任务go func(){// do something in on new goroutine
}{}

功能等价于 Java8 的代码:

new java.lang.Thread(()->{//do something in one new thread
}).start();

理解 goroutine 机制的原理, 关键是理解 Go 语言 scheduler 的实现。

Go语言中支撑整个 scheduler 实现的主要有四个重要结构,分辨是M ,G , P , Sched, 前三个定义在 runtime.h 中, Sched 定义在 proc.c 中。

  • Sched 结构就是 调度器,它维护有存储 M 和 G的列队以及调度器的一些状态信息等。
  • M 结构就是 Machine, 系统线程,它由操作系统管理, goroutine 就是跑在M之上的,M是一个很大的结构,里面维护小对象 cache (mcache), 当前执行的 goroutine, 随机数发生器等等 非常多的信息
  • P 结构是 Processor, 处理器 它的 主要用途就是用来执行 goroutine的, 它维护了一个 goroutine 列队,即 runqueue, Processor 是让我们冲 N:1 调度到 M:N 的重要部分
  • G 是 goroutine 实现的核心结构,它包含了栈,指令指针,以及其他对调度goroutine重要信息,例如阻塞的 channel

Processor 的数量实在启动时被设置为环境变量的 GOMAXPROCS 的值,或者通过运行时调用函数 GOMAXPROCS() 进行设置。Processor 数量固定意味着任意时刻只有GOMAXPROCS 个线程在运行 go 代码。

 

我们分布使用三角形, 矩形, 圆形 表示 Machine  Processor 和  goroutine

 

  • 在单核处理器场景下:所有的 goroutine 运行在同一个M系统线程中每一个M系统线程维护一个 processor,  任何时刻 一个 processor 中只有一个 goroutine. 其他 grountine 在 runqueque 中等待。 一个 goroutine 运行完自己的时间片之后, 让出上下文。 回到 runqueue中,
  • 多核处理器场景下:为了运行goroutine 每个M系统线程都会持有一个 processor

正常情况下,Scheduler 会按照上面的流程进行调度,但是线程会发生阻塞等情况,看一下 goroutine 对线程阻塞的处理:

 

线程阻塞

当正在运行的 goroutine 发送阻塞时:例如进行系统调用时,会创建一个(或者直接使用空闲的)系统线程 M1, 当前的 M0 线程放弃了它的 processorP 转到新的线程M1中去运行M0 继续执行阻塞任务 g0,这样就不会 阻塞后面的 goroutine。 这样 所有的 goroutine 都会得到执行

 

runqueue执行完成

 当其中一个 processor 的 runqueue 的列队执行完毕,列队为 空,没有 goroutine 可以调度,他会从另一个上下文字获取一半的 goroutine。 

图中的G,  P, 和 M 都是 Go 语言运行时系统, 其中包括内存分配 并发调度器 垃圾收集等组件, 可以想象为 Java中的 VM, 抽象出来概念和 数据结构对象:

  • G : goroutine 的简称,上面用 go 关键字加函数调用代码 就是创建一个 G 对象,是对一个重要并发的任务封装, 也可以称为用户态线程。 属于用户级资源, 对 OS 透明,具备轻量级, 可以大量创建 上下文切换成本低等优点

 

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

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

相关文章

python-绝对值排序(赛氪OJ)

[题目描述] 输入 n 个整数,按照绝对值从大到小排序后输出。保证所有整数的绝对值不同。输入格式: 输入数据有多组,每组占一行,每行的第一个数字为 n ,接着是 n 个整数, n0 表示输入数据的结束,不做处理。输…

Ruoyi-WMS本地运行

所需软件 1、JDK:8 安装包:https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.htmlopen in new window 安装文档:https://cloud.tencent.com/developer/article/1698454open in new window 2、Redis 3.0 安装包&a…

[Vulnhub] Acid-Reloaded SQLI+图片数据隐写提取+Pkexec权限提升+Overlayfs权限提升

信息收集 IP AddressOpening Ports192.168.101.158TCP:22,33447 $ nmap -p- 192.168.101.158 --min-rate 1000 -sC -sV Not shown: 65534 closed tcp ports (conn-refused) PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 6.7p1 Ubuntu 5ubuntu1.3 (Ubuntu Lin…

C语言玩一下标准输出——颜色、闪烁、加粗、下划线属性

文章目录 C语言玩一下标准输出——颜色、闪烁、加粗、下划线属性转换Tip切换内容介绍显示方式字体色背景色 常用光标控制附示例和运行结果 C语言玩一下标准输出——颜色、闪烁、加粗、下划线属性 标准输出格式其属性可控制,控制由一系列的控制码指定。标准输出函数可…

【微信小程序实战教程】之微信小程序 WXS 语法详解

WXS语法 WXS是微信小程序的一套脚本语言,其特性包括:模块、变量、注释、运算符、语句、数据类型、基础类库等。在本章我们主要介绍WXS语言的特性与基本用法,以及 WXS 与 JavaScript 之间的不同之处。 1 WXS介绍 在微信小程序中&#xff0c…

【Socket编程】了解应用层协议HTTP

HTTP协议 HTTP又叫超文本传输协议。它定义了客户端和服务端之间该如何通信,以交换或者传输超文本(如HTML文档)。HTTP协议是一个无连接、无状态的协议,即每次请求都需要建立新的连接,且服务器不会保存客户端的状态信息…

智谱OpenDay“大有可玩”:30秒将任意文字生成视频

Sora毫无疑问带来AI大模型的全新玩法,大模型可基于任意文字生成视频,这也是这个“大家庭”若干努力(包括Runway的Gen系列、微软的Nuwa、Meta的Emu、谷歌的Phenaki/VideoPoet、CogVideo等)的一个全新高度。 7月26日,这…

FastAPI(七十七)实战开发《在线课程学习系统》接口开发-- 课程编辑和查看评论

源码见:"fastapi_study_road-learning_system_online_courses: fastapi框架实战之--在线课程学习系统" 课程编辑 先来看下课程编辑 1.判断是否登录 2.判断课程是否存在 3.是否有权限(只有自己可以修改自己的课程) 4.名称是否重复…

Docker(十)-Docker运行elasticsearch7.4.2容器实例以及分词器相关的配置

1.下载镜像 1.1存储和检索数据 docker pull elasticsearch:7.4.2 1.2可视化检索数据 docker pull kibana:7.4.22.创建elasticsearch实例 创建本地挂载数据卷配置目录 mkdir -p /software/elasticsearch/config 创建本地挂载数据卷数据目录 mkdir -p /software/elasticse…

【LeetCode:3098. 求出所有子序列的能量和 + 记忆化缓存】

🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,…

h264/h265编解码专题讲解

一、编码器和解码器的架构讲解 1、编码层次: 一般来说,对于像h264、h265编解码器,一般会采用块级编码,也就是预先将一幅图像切割为多个像素块,一次对块内的部分或者所有的像素进行预测和编码;所以对编码器…

【Qt】QLCDNumber和QProgressBar

目录 QLCDNumber 倒计时小程序 相关属性 QProgressBar 进度条小程序 相关设置 QLCDNumber QLCDNumber是Qt框架中用于显示数字或计数值的小部件。通常用于显示整数值,例如时钟、计时器、计数器等 常用属性 属性说明intValueQLCDNumber显示的初始值(int类型)va…

会话存储、本地存储,路由导航守卫、web会话跟踪、JWT生成token、axios请求拦截、响应拦截

1、会话存储、本地存储 前端浏览器中存储用户信息,会话存储、本地存储、cookie 会话存储(sessionStorage):会话期间存储,关闭浏览器后,数据就会销毁 sessionStorage.setItem("account",resp.d…

x-cmd AI | x mistral - Mistral AI 的命令行实现

目录 简介首次用户子命令help && TLDR 简介 mistral 模块是 Mistral 大模型的命令行客户端工具,由 x-cmd 驱动,主要使用 posix shell, awk 和 curl 来实现。 首次用户 在终端运行 eval "$(curl https://get.x-cmd.com)" 即可完成 x…

7月24日JavaSE学习笔记

序列化版本控制 序列化:将内存对象转换成序列(流)的过程 反序列化:将对象序列读入程序,转换成对象的方式;反序列化的对象是一个新的对象。 serialVersionUID 是一个类的序列化版本号 private static fin…

架构设计面试经验总结

文章收录在网站:http://hardyfish.top/ 文章收录在网站:http://hardyfish.top/ 文章收录在网站:http://hardyfish.top/ 文章收录在网站:http://hardyfish.top/ 学习架构设计知识的思路总结为以下几点: 想要学习架构…

抖音广告投放定向技巧大揭秘:精准触达,引爆流量新高度!

抖音作为头部平台,汇聚了海量用户与无限商机。对于企业而言,如何在抖音这片蓝海中精准投放广告,实现品牌曝光与销售转化,成为了一门必修课。今天,就让抖音广告代运营 www.zoboo.cn 一起揭开抖音广告投放定向技巧的神秘…

PYTHON学习笔记(八、字符串及的使用)

目录 1、字符串 1.1、字符串的常用操作 1.2、格式化字符串 1.2.1、占位符格式化字符串 1.2.2、f-string格式化字符串 1.2.3、str.format( )格式化字符串 1.3、数据的验证 1.4、正则表达式 1.5.1元字符 1.5.2限定符 1.5.3其他字符 1.5.4re模块 1、字符串 1.1、字符…

【HarmonyOS】应用推送使用个推SDK如何实现?

【HarmonyOS】应用推送使用个推SDK如何实现? 前言 个推和极光都是市面上很成熟的推送第三方SDK了。今天讲讲个推SDK在鸿蒙中如何集成使用。 存在即合理,三方SDK推送给我们带来了极大的好处,首先在服务器后台处理一套API就可搞定&#xff0…

Android 去掉顶部默认导航栏 修改状态栏背景颜色

在 Android 新建项目后,存在顶部的默认导航栏,我们可以通过下面方式去掉;同时,也可以修改状态栏的背景颜色(状态栏:顶部显示时间、电池电量那一栏) 一、去掉顶部默认导航栏 1. 方式一&#xff…