我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Golang在runtime中的知识点有哪些

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Golang在runtime中的知识点有哪些

这篇文章主要讲解了“Golang在runtime中的知识点有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Golang在runtime中的知识点有哪些”吧!

调度器结构

调度器管理三个在 runtime 中十分重要的类型:G、M和P。哪怕你不写 scheduler 相关代码,你也应当要了解这些概念。

G、M 和 P

一个G就是一个 goroutine,在 runtime 中通过类型g来表示。当一个 goroutine  退出时,g对象会被放到一个空闲的g对象池中以用于后续的 goroutine 的使用(译者注:减少内存分配开销)。

一个M就是一个系统的线程,系统线程可以执行用户的 go 代码、runtime 代码、系统调用或者空闲等待。在 runtime  中通过类型m来表示。在同一时间,可能有任意数量的M,因为任意数量的M可能会阻塞在系统调用中。(译者注:当一个M执行阻塞的系统调用时,会将M和P解绑,并创建出一个新的M来执行P上的其它G。)

最后,一个P代表了执行用户 go 代码所需要的资源,比如调度器状态、内存分配器状态等。在 runtime  中通过类型p来表示。P的数量精确地(exactly)等于GOMAXPROCS。一个P可以被理解为是操作系统调度器中的 CPU,p类型可以被理解为是每个 CPU  的状态。在这里可以放一些需要高效共享但并不是针对每个P(Per P)或者每个M(Per M)的状态(译者注:意思是,可以放一些以P级别共享的数据)。

调度器的工作是将一个G(需要执行的代码)、一个M(代码执行的地方)和一个P(代码执行所需要的权限和资源)结合起来。当一个M停止执行用户代码的时候(比如进入阻塞的系统调用的时候),就需要把它的P归还到空闲的P池中;为了继续执行用户的  go 代码(比如从阻塞的系统调用退出的时候),就需要从空闲的P池中获取一个P。

所有的g、m和p对象都是分配在堆上且永不释放的,所以它们的内存使用是很稳定的。得益于此,runtime  可以在调度器实现中避免写屏障(译者注:垃圾回收时需要的一种屏障,会带来一些性能开销)。

用户栈和系统栈

每个存活着的(non-dead)G都会有一个相关联的用户栈,用户的代码就是在这个用户栈上执行的。用户栈一开始很小(比如  2K),并且动态地生长或者收缩。

每一个M都有一个相关联的系统栈(也被称为g0栈,因为这个栈也是通过g实现的);如果是在 Unix 平台上,还会有一个  signal栈(也被称为gsignal栈)。系统栈和signal栈不能生长,但是足够大到运行任何 runtime 和 cgo 的代码(在纯 go 二进制中为  8K,在 cgo 情况下由系统分配)。

runtime  代码经常通过调用systemstack、mcall或者asmcgocall临时性的切换到系统栈去执行一些特殊的任务,比如:不能被抢占的、不应该扩张用户栈的和会切换用户  goroutine 的。在系统栈上运行的代码隐含了不可抢占的含义,同时垃圾回收器不会扫描系统栈。当一个M在系统栈上运行时,当前的用户栈是没有被运行的。

getg()和getg().m.curg

如果想要获取当前用户的g,需要使用getg().m.curg。

getg()虽然会返回当前的g,但是当正在系统栈或者signal栈上执行的时候,会返回的是当前M的g0或者gsignal,而这很可能不是你想要的。

如果要判断当前正在系统栈上执行还是用户栈上执行,可以使用getg() == getg().m.curg。

错误处理和上报

在用户代码中,有一些可以被合理地(reasonably)恢复的错误可以像往常一样使用panic,但是有一些情况下,panic可能导致立即的致命的错误,比如在系统栈中调用或者当执行mallocgc时。

大部分的 runtime  的错误是不可恢复的,对于这些不可恢复的错误应该使用throw,throw会打印出traceback并立即终止进程。throw应当被传入一个字符串常量以避免在该情况下还需要为  string 分配内存。根据约定,更多的信息应当在throw之前使用print或者println打印出来,并且应当以runtime.开头。

为了进行 runtime 的错误调试,有一个很实用的方法是设置GOTRACEBACK=system 或 GOTRACEBACK=crash。

同步

runtime 中有多种同步机制,这些同步机制不仅是语义上不同,和 go 调度器以及操作系统调度器之间的交互也是不一样的。

最简单的就是mutex,可以使用lock和unlock来操作。这种方法主要用来短期(长期的话性能差)地保护一些共享的数据。在mutex上阻塞会直接阻塞整个M,而不会和  go 的调度器进行交互。因此,在 runtime 中的最底层使用  mutex是安全的,因为它还会阻止相关联的G和P被重新调度(M都阻塞了,无法执行调度了)。rwmutex也是类似的。

如果是要进行一次性的通知,可以使用note。note提供了notesleep和notewakeup。不像传统的 UNIX  的sleep/wakeup,note是无竞争的(race-free),所以如果notewakeup已经发生了,那么notesleep将会立即返回。note可以在使用后通过noteclear来重置,但是要注意noteclear和notesleep、notewakeup不能发生竞争。类似mutex,阻塞在note上会阻塞整个M。然而,note提供了不同的方式来调用sleep:notesleep会阻止相关联的G和P被重新调度;notetsleepg的表现却像一个阻塞的系统调用一样,允许P被重用去运行另一个G。尽管如此,这仍然比直接阻塞一个G要低效,因为这需要消耗一个M。

如果需要直接和 go 调度器交互,可以使用gopark和goready。gopark挂起当前的  goroutine——把它变成waiting状态,并从调度器的运行队列中移除——然后调度另一个 goroutine  到当前的M或者P。goready将一个被挂起的 goroutine 恢复到runnable状态并将它放到运行队列中。

总结起来如下表:

Golang在runtime中的知识点有哪些

原子性

runtime  使用runtime/internal/atomic中自有的一些原子操作。这个和sync/atomic是对应的,除了方法名由于历史原因有一些区别,并且有一些额外的  runtime 需要的方法。

总的来说,我们对于 runtime 中 atomic  的使用非常谨慎,并且尽可能避免不需要的原子操作。如果对于一个变量的访问已经被另一种同步机制所保护,那么这个已经被保护的访问一般就不需要是原子的。这么做主要有以下原因:

  • 合理地使用非原子和原子操作使得代码更加清晰可读,对于一个变量的原子操作意味着在另一处可能会有并发的对于这个变量的操作。

  • 非原子的操作允许自动的竞争检测。runtime  本身目前并没有一个竞争检测器,但是未来可能会有。原子操作会使得竞争检测器忽视掉这个检测,但是非原子的操作可以通过竞争检测器来验证你的假设(是否会发生竞争)。

  • 非原子的操作可以提高性能。

当然,所有对于一个共享变量的非原子的操作都应当在文档中注明该操作是如何被保护的。

有一些比较普遍的将原子操作和非原子操作混合在一起的场景有:

  • 大部分操作都是读,且写操作被锁保护的变量。在锁保护的范围内,读操作没必要是原子的,但是写操作必须是原子的。在锁保护的范围外,读操作必须是原子的。

  • 仅仅在 STW 期间发生的读操作,且 STW 期间不会有写操作。那么这个时候,读操作不需要是原子的。

话虽如此,Go Memory Model给出的建议仍然成立Don't be [too] clever。runtime  的性能固然重要,但是鲁棒性(robustness)却更加重要。

堆外内存(Unmanaged memory)

一般情况下,runtime 会尝试使用普通的方法来申请内存(堆上内存,gc 管理的),然而在某些情况 runtime 必须申请一些不被 gc  所管理的堆外内存(unmanaged  memory)。这是很必要的,因为有可能该片内存就是内存管理器自身,或者说调用者没有一个P(译者注:比如在调度器初始化之前,是不存在P的)。

有三种方式可以申请堆外内存:

  • sysAlloc直接从操作系统获取内存,申请的内存必须是系统页表长度的整数倍。可以通过sysFree来释放。

  • persistentalloc将多个小的内存申请合并在一起为一个大的sysAlloc以避免内存碎片(fragmentation)。然而,顾名思义,通过persistentalloc申请的内存是无法被释放的。

  • fixalloc是一个SLAB风格的内存分配器,分配固定大小的内存。通过fixalloc分配的对象可以被释放,但是内存仅可以被相同的fixalloc池所重用。所以fixalloc适合用于相同类型的对象。

普遍来说,使用以上三种方法分配内存的类型都应该被标记为//go:notinheap(见后文)。

在堆外内存所分配的对象不应该包含堆上的指针对象,除非同时遵守了以下的规则:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 所有在堆外内存指向堆上的指针都必须是垃圾回收的根(garbage collection  roots)。也就是说,所有指针必须可以通过一个全局变量所访问到,或者显式地使用runtime.markroot来标记。

  3. 如果内存被重用了,堆上的指针在被标记为 GC 根并且对 GC 可见前必须 以 0 初始化(zero-initialized,见后文)。不然的话,GC  可能会观察到过期的(stale)堆指针。可以参见下文Zero-initialization versus zeroing.

Zero-initialization versus zeroing

在 runtime 中有两种类型的零初始化,取决于内存是否已经初始化为了一个类型安全的状态。

如果内存不在一个类型安全的状态,意思是可能由于刚被分配,并且第一次初始化使用,会含有一些垃圾值(译者注:这个概念在日常的 Go 代码中是遇不到的,如果学过  C  语言的同学应该能理解什么意思),那么这片内存必须使用memclrNoHeapPointers进行zero-initialized或者无指针的写。这不会触发写屏障(译者注:写屏障是  GC 中的一个概念)。

内存可以通过typedmemclr或者memclrHasPointers来写入零值,设置为类型安全的状态。这会触发写屏障。

Runtime-only 编译指令(compiler directives)

除了go doc compile中注明的//go:编译指令外,编译器在 runtime 包中支持了额外的一些指令。

go:systemstack

go:systemstack表明一个函数必须在系统栈上运行,这个会通过一个特殊的函数前引(prologue)动态地验证。

go:nowritebarrier

go:nowritebarrier告知编译器如果以下函数包含了写屏障,触发一个错误(这不会阻止写屏障的生成,只是单纯一个假设)。

一般情况下你应该使用go:nowritebarrierrec。go:nowritebarrier当且仅当“最好不要”写屏障,但是非正确性必须的情况下使用。

go:nowritebarrierrec 与 go:yeswritebarrierrec

go:nowritebarrierrec告知编译器如果以下函数以及它调用的函数(递归下去),直到一个go:yeswritebarrierrec为止,包含了一个写屏障的话,触发一个错误。

逻辑上,编译器会在生成的调用图上从每个go:nowritebarrierrec函数出发,直到遇到了go:yeswritebarrierrec的函数(或者结束)为止。如果其中遇到一个函数包含写屏障,那么就会产生一个错误。

go:nowritebarrierrec主要用来实现写屏障自身,用来避免死循环。

这两种编译指令都在调度器中所使用。写屏障需要一个活跃的P(getg().m.p !=  nil),然而调度器相关代码有可能在没有一个活跃的P的情况下运行。在这种情况下,go:nowritebarrierrec会用在一些释放P或者没有P的函数上运行,go:yeswritebarrierrec会用在重新获取到了P的代码上。因为这些都是函数级别的注释,所以释放P和获取P的代码必须被拆分成两个函数。

go:notinheap

go:notinheap适用于类型声明,表明了一个类型必须不被分配在 GC  堆上。特别的,指向该类型的指针总是应当在runtime.inheap判断中失败。这个类型可能被用于全局变量、栈上变量,或者堆外内存上的对象(比如通过sysAlloc、persistentalloc、fixalloc或者其它手动管理的span进行分配)。特别的:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. new(T)、make([]T)、append([]T, ...)和隐式的对于T的堆上分配是不允许的(尽管隐式的分配在 runtime  中是从来不被允许的)。

  3. 一个指向普通类型的指针(除了unsafe.Pointer)不能被转换成一个指向go:notinheap类型的指针,就算它们有相同的底层类型(underlying  type)。

  4. 任何一个包含了go:notinheap类型的类型自身也是go:notinheap的。如果结构体和数组包含go:notinheap的元素,那么它们自身也是go:notinheap类型。map  和 channel  不允许有go:notinheap类型。为了使得事情更加清晰,任何隐式的go:notinheap类型都应该显式地标明go:notinheap。

  5. 指向go:notinheap类型的指针的写屏障可以被忽略。

最后一点是go:notinheap类型真正的好处。runtime  在底层结构中使用这个来避免调度器和内存分配器的内存屏障以避免非法检查或者单纯提高性能。这种方法是适度的安全(reasonably safe)的并且不会使得  runtime 的可读性降低。

感谢各位的阅读,以上就是“Golang在runtime中的知识点有哪些”的内容了,经过本文的学习后,相信大家对Golang在runtime中的知识点有哪些这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Golang在runtime中的知识点有哪些

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Golang中关于defer的知识点有哪些

这篇文章主要介绍“Golang中关于defer的知识点有哪些”,在日常操作中,相信很多人在Golang中关于defer的知识点有哪些问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Golang中关于defer的
2023-07-05

iOS开发中runtime的基本知识有哪些

今天就跟大家聊聊有关iOS开发中runtime的基本知识有哪些,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。1. 运行时1.1 基本概念: 运行时Runtime 的概念Runtime
2023-06-04

HttpServlet在Servlet程序中的知识点有哪些

本篇内容主要讲解“HttpServlet在Servlet程序中的知识点有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“HttpServlet在Servlet程序中的知识点有哪些”吧!Http
2023-06-03

Python中的PEP知识点有哪些

本篇内容介绍了“Python中的PEP知识点有哪些”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!PEP是什么?PEP的全称是Python E
2023-06-02

Go中Sync.Map的知识点有哪些

这篇文章主要讲解了“ Go中Sync.Map的知识点有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“ Go中Sync.Map的知识点有哪些”吧!sync.Map 优势在 Go 官方文档中
2023-06-15

SAP中的BOPF知识点有哪些

这篇文章主要介绍“SAP中的BOPF知识点有哪些”,在日常操作中,相信很多人在SAP中的BOPF知识点有哪些问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”SAP中的BOPF知识点有哪些”的疑惑有所帮助!接下来
2023-06-04

Python中的pandas知识点有哪些

本篇内容主要讲解“Python中的pandas知识点有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python中的pandas知识点有哪些”吧!前言pandas 是基于 Numpy 的一种
2023-06-27

Android中LayoutInflater的知识点有哪些

这篇文章主要介绍“Android中LayoutInflater的知识点有哪些”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Android中LayoutInflater的知识点有哪些”文章能帮助大家解
2023-06-29

git中merge的知识点有哪些

这篇“git中merge的知识点有哪些”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“git中merge的知识点有哪些”文章吧
2023-06-29

java中锁的知识点有哪些

今天小编给大家分享一下java中锁的知识点有哪些的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。乐观锁和悲观锁悲观锁悲观锁对应
2023-06-30

JVM中的GC知识点有哪些

这篇文章主要介绍了JVM中的GC知识点有哪些的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇JVM中的GC知识点有哪些文章都会有所收获,下面我们一起来看看吧。GC简介何为GCGC(Garbage Collecti
2023-06-30

SwiftUI的知识点有哪些

这篇文章主要讲解了“SwiftUI的知识点有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“SwiftUI的知识点有哪些”吧!一、背景苹果于2019年度WWDC全球开发者大会上,发布了基于
2023-06-04

React的知识点有哪些

这篇文章主要介绍了React的知识点有哪些的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇React的知识点有哪些文章都会有所收获,下面我们一起来看看吧。  组件的数据挂载方式,属性(props)props是正常
2023-06-03

ECharts的知识点有哪些

本文小编为大家详细介绍“ECharts的知识点有哪些”,内容详细,步骤清晰,细节处理妥当,希望这篇“ECharts的知识点有哪些”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。简介ECharts(Enterpris
2023-06-27

yolov5的知识点有哪些

这篇文章主要讲解了“yolov5的知识点有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“yolov5的知识点有哪些”吧!一、yolo中txt文件的说明:二、yolo跑视频、图片文件的格式
2023-07-02

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录