Below you will find pages that utilize the taxonomy term “Runtime”
March 29, 2021
Runtime: Golang 定时器实现原理及源码解析
"\u003cp\u003e定时器作为开发经常使用的一种数据类型,是每个开发者需要掌握的,对于一个高级开发很有必要了解它的实现原理,今天我们runtime源码来学习一下它的底层实现。\u003c/p\u003e\n\u003cp\u003e定时器分两种,分别为 Timer 和 Ticker,两者差不多,这里重点以Timer为例。\u003c/p\u003e\n\u003cp\u003e源文件位于 \u003ccode\u003e[src/time/sleep.go](https://github.com/golang/go/blob/go1.16.2/src/time/sleep.go)\u003c/code\u003e 和 \u003ccode\u003e[src/time/tick.go](https://github.com/golang/go/blob/go1.16.2/src/time/tick.go)\u003c/code\u003e 。 go version 1.16.2\u003c/p\u003e\n\u003ch1 id=\"数据结构\"\u003e数据结构\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003eTimer\u003c/code\u003e 数据结构\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e// src/runtime/sleep.go\n\n// The Timer type represents a single event.\n// When the Timer expires, the current time will be sent on C,\n// unless the Timer was created by …\u003c/code\u003e\u003c/pre\u003e"
March 20, 2021
Runtime: Golang 之 sync.Pool 源码分析
"\u003cp\u003ePool 指一组可以单独保存和恢复的 \u003ccode\u003e临时对象\u003c/code\u003e。Pool 中的对象随时都有可能在没有收到任何通知的情况下被GC自动销毁移除。\u003c/p\u003e\n\u003cp\u003e多个goroutine同时操作Pool是\u003ccode\u003e并发安全\u003c/code\u003e的。\u003c/p\u003e\n\u003cp\u003e源文件为 \u003ccode\u003e[src/sync/pool.go](https://github.com/golang/go/blob/master/src/sync/pool.go)\u003c/code\u003e go version: 1.16.2\u003c/p\u003e\n\u003ch1 id=\"为什么使用pool\"\u003e为什么使用Pool\u003c/h1\u003e\n\u003cp\u003e在开发高性能应用时,经常会有一些完全相同的对象需要频繁的创建和销毁,每次创建都需要在堆中分配对象,等使用完毕后,这些对象需要等待GC回收。我们知道在Golang中使用三色标记法进行垃圾回收的,在回收期间会有一个短暂\u003ccode\u003eSTW\u003c/code\u003e(stop the world)的时间段,这样就会导致程序性能下降。\u003c/p\u003e\n\u003cp\u003e那么能否实现类似数据库连接池这种效果,用来避免对象的频繁创建和销毁,达到尽可能的资源复用呢?为了实现这种需求,标准库中有了\u003ccode\u003esync.Pool\u003c/code\u003e 这个数据结构。看名字很知道它是一个池。但是它和我们想象中的数据库连接池还是有些差别的。对于数据库连接池这种资源只要不手动释放就可以一直利用, …\u003c/p\u003e"
March 1, 2021
Golang 的调度策略之G的窃取
"\u003cp\u003e我们上篇文章( \u003ca href=\"https://blog.haohtml.com/archives/21411\"\u003eGolang 的底层引导流程/启动顺序\u003c/a\u003e)介绍了一个golang程序的启动流程,在文章的最后对于最重要的一点“\u003ccode\u003e调度\u003c/code\u003e“ (函数 \u003ccode\u003e[schedule()](https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go#L2607-L2723)\u003c/code\u003e) 并没有展开来讲,今天我们继续从源码来分析一下它的调度机制。\u003c/p\u003e\n\u003cp\u003e在此之前我们要明白golang中的调度主要指的是什么?在 \u003ccode\u003esrc/runtime/proc.go\u003c/code\u003e 文件里有一段注释这样写到\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e// Goroutine scheduler\u003c/p\u003e\n\u003cp\u003e// The scheduler’s job is to distribute ready-to-run goroutines over worker threads.\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003e这里指如何找一个已准备好运行的 G 关联到PM 让其执行。对于G 的调度可以围绕三个方面来理解:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e时机:什么时候关联(调度)。对于调度时机一般是指有空闲P的时候都会去找G执行\u003c/li\u003e\n\u003cli\u003e对象:选择哪个G进行调度。这是我们本篇要讲的内容\u003c/li\u003e\n\u003cli\u003e机制:如何调度。\u003ccode\u003eexecute()\u003c/code\u003e 函数\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e理解 …\u003c/p\u003e"
February 27, 2021
Runtime: Golang是如何处理系统调用阻塞的?
"\u003cp\u003e我们知道在Golang中,当一个Goroutine由于执行 \u003ccode\u003e系统调用\u003c/code\u003e 而阻塞时,会将M从GPM中分离出去,然后P再找一个G和M重新执行,避免浪费CPU资源,那么在内部又是如何实现的呢?今天我们还是通过学习Runtime源码的形式来看下他的内部实现细节有哪些?\u003c/p\u003e\n\u003cp\u003ego version 1.15.6\u003c/p\u003e\n\u003cp\u003e我们知道一个P有四种运行状态,而当执行系统调用函数阻塞时,会从 \u003ccode\u003e_Prunning\u003c/code\u003e 状态切换到 \u003ccode\u003e_Psyscall\u003c/code\u003e,等系统调用函数执行完毕后再切换回来。\u003cimg src=\"https://blogstatic.haohtml.com/uploads/2021/01/0d20dfce0e3dd6968aebe84535b853c6.png\" alt=\"P的状态切换\"\u003eP的状态切换\u003c/p\u003e\n\u003cp\u003e从上图我们可以看出 \u003ccode\u003eP\u003c/code\u003e 执行系统调用时会执行 \u003ccode\u003e[entersyscall()](https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go#L3134-L3142)\u003c/code\u003e 函数(另还有一个类似的阻塞函数 \u003ca href=\"https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go#L3171-L3212\"\u003e\u003ccode\u003eentersyscallblock()\u003c/code\u003e\u003c/a\u003e ,注意两者的区别)。当系统调用执行完毕切换回去会执行 \u003ca href=\"https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go#L3222-L3305\"\u003e\u003ccode\u003eexitsyscall()\u003c/code\u003e\u003c/a\u003e 函数,下面我们看一下这两个函数的实现。\u003c/p\u003e\n\u003ch1 id=\"进入系统调用\"\u003e进入系统调用\u003c/h1\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e// Standard syscall entry used by the go syscall …\u003c/code\u003e\u003c/pre\u003e"
February 17, 2021
Runtime: 创建一个goroutine都经历了什么?
"\u003cp\u003e我们都知道goroutine的在golang中发挥了很大的作用,那么当我们创建一个新的goroutine时,它是怎么一步一步创建的呢?都经历了哪些操作呢?今天我们通过源码来剖析一下创建goroutine都经历了些什么?go version 1.15.6\u003c/p\u003e\n\u003cp\u003e对goroutine最关键的两个函数是 \u003ccode\u003e[newproc()](https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go#L3535-L3564)\u003c/code\u003e 和 \u003ccode\u003e[newproc1()](https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go#L3566-L3674)\u003c/code\u003e,而 \u003ccode\u003enewproc1()\u003c/code\u003e 函数是我们最需要关注的。\u003c/p\u003e\n\u003ch2 id=\"函数-newproc\"\u003e函数 newproc()\u003c/h2\u003e\n\u003cp\u003e我们先看一个简单的创建goroutine的例子,找出来创建它的函数。\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003epackage main\n\nfunc start(a, b, c int64) {\n\t_ = a + b + c\n}\n\nfunc main() {\n\tgo start(7, 2, 5)\n}\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e输出结果: …\u003c/p\u003e"
February 15, 2021
Runtime: 理解Golang中接口interface的底层实现
"\u003cp\u003e接口类型是Golang中是一种非常非常常见的\u003ccode\u003e数据类型\u003c/code\u003e,每个开发人员都很有必要知道它到底是如何使用的,如果了解了它的底层实现就对开发就更有帮助了。\u003c/p\u003e\n\u003ch1 id=\"接口的定义\"\u003e接口的定义\u003c/h1\u003e\n\u003cp\u003e在Golang中 \u003ccode\u003einterface\u003c/code\u003e 通常是指实现了一 组抽象方法的集合,它提供了一种无侵入式的方式。当你实现了一个接口中指定的所有方法的时候,那么就实现了这个接口,在Golang中对它的实现并不需要 \u003ccode\u003eimplements\u003c/code\u003e 关键字。\u003c/p\u003e\n\u003cp\u003e有时候我们称这种模型叫做鸭子模型(Duck typing),维基百科对鸭子模型的定义是\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e”If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.“\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003e翻译过来就是 ”如果它看起来像鸭子,像鸭子一样游泳,像鸭子一样嘎嘎叫,那他就可以认为是鸭子“。\u003c/p\u003e\n\u003cp\u003eGo 不同版本之间interface的结构可能不太一样,但整体都差不多,这里使用的Go版本为 1.15.6。\u003c/p\u003e\n\u003ch1 id=\"数据结构\"\u003e数据结构\u003c/h1\u003e\n\u003cp\u003eGo 中 \u003ccode\u003einterface\u003c/code\u003e 在运行时可分 \u003ccode\u003eeface\u003c/code\u003e 和 \u003ccode\u003eiface\u003c/code\u003e 两种数据结构,我们先看一下对它们的 …\u003c/p\u003e"
February 13, 2021
认识Golang中的sysmon监控线程
"\u003cp\u003eGo Runtime 在启动程序的时候,会创建一个独立的 \u003ccode\u003eM\u003c/code\u003e 作为监控线程,称为 \u003ccode\u003esysmon\u003c/code\u003e,它是一个系统级的 \u003ccode\u003edaemon\u003c/code\u003e 线程。这个\u003ccode\u003esysmon\u003c/code\u003e 独立于 GPM 之外,也就是说不需要P就可以运行,因此官方工具 \u003ccode\u003ego tool trace\u003c/code\u003e 是无法追踪分析到此线程( \u003ca href=\"https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go#L4639-L4760\"\u003e源码\u003c/a\u003e)。\u003cimg src=\"https://blogstatic.haohtml.com/uploads/2021/02/6ad0cfb3df2281110cf60630fcfb0e96.png\" alt=\"\"\u003esysmon\u003c/p\u003e\n\u003cp\u003e在程序执行期间 \u003ccode\u003esysmon\u003c/code\u003e 每隔 \u003ccode\u003e20us~10ms\u003c/code\u003e 轮询执行一次( \u003ca href=\"https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go#L4652-L4659\"\u003e源码\u003c/a\u003e),监控那些长时间运行的 G 任务, 然后设置其可以被强占的标识符,这样别的 \u003ccode\u003eGoroutine\u003c/code\u003e 就可以抢先进来执行。\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e// src/runtime/proc.go\n\n// forcegcperiod is the maximum time in nanoseconds between garbage\n// collections. If we go this long without a garbage collection, one\n// is forced to run.\n//\n// This is a variable for testing purposes. It normally doesn\u0026#39;t …\u003c/code\u003e\u003c/pre\u003e"
January 22, 2021
Golang 的底层引导流程/启动顺序
"\u003cp\u003e在Golang中,程序的执行入口为 \u003ccode\u003emain()\u003c/code\u003e 函数,那么底层又是如何工作的呢? 这个问题的答案我们可以在runtime源码找到。对它的解释主要在 \u003ccode\u003e[src/runtime/proc.go](https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go)\u003c/code\u003e 文件,下面我们看一下它是如何一步一步开始执行的。go version 1.15.6\u003c/p\u003e\n\u003cp\u003e在文件头部有一段对 \u003ccode\u003e[Goroutine scheduler](https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go#L19)\u003c/code\u003e 的介绍,我们先了解一下。\u003c/p\u003e\n\u003cp\u003e调度器的工作是分发\u003ccode\u003egoroutines\u003c/code\u003e到工作线程让其运行。一句话指明了调度器的存在意义,就是指挥协调GPM干活。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e主要包含三部分\u003c/strong\u003e\n\u003ccode\u003eG\u003c/code\u003e 指的是 goroutine\n\u003ccode\u003eM\u003c/code\u003e 工作线程,也叫\u003ccode\u003emachine\u003c/code\u003e\n\u003ccode\u003eP\u003c/code\u003e 处理器(逻辑CPU),执行 \u003ccode\u003eGo code\u003c/code\u003e 的一种资源。这里的Go code 其实就是 goroutine里的代码。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eM\u003c/code\u003e必须被指派给\u003ccode\u003eP\u003c/code\u003e去执行 \u003ccode\u003eGo code\u003c/code\u003e, 但可以被 …\u003c/p\u003e"
January 18, 2021
Runtime: Golang中channel实现原理源码分析
"\u003cp\u003echannel是golang中特有的一种数据结构,通常与goroutine一起使用,下面我们就介绍一下这种数据结构。\u003c/p\u003e\n\u003ch2 id=\"channel数据结构\"\u003echannel数据结构\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003echannel\u003c/code\u003e 是Golang 中最重要的一个数据结构,源码里对应的结构体是\u003ccode\u003ehchan\u003c/code\u003e,当我们创建一个\u003ccode\u003echannel\u003c/code\u003e 的时候,实际上是创建了一个\u003ccode\u003ehchan\u003c/code\u003e结构体。\u003c/p\u003e\n\u003ch3 id=\"hchan结构体\"\u003ehchan结构体\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// src/runtime/chan.go\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ehchan\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eqcount\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003euint\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e// total data in the queue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003edataqsiz\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003euint\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e// size of the circular queue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003ebuf\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eunsafe\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePointer\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e// points to an array of dataqsiz elements\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eelemsize\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003euint16\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eclosed\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003euint32\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eelemtype\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003e_type\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e// element type\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003esendx\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003euint\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e// send index\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003erecvx\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003euint\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e// …\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e"
January 11, 2021
Runtime:源码解析Golang 的map实现原理
"\u003cp\u003ego version 1.15.6\u003c/p\u003e\n\u003cp\u003emap作为一种常见的 \u003ccode\u003ekey-value\u003c/code\u003e 数据结构,不同语言的实现原理基本差不多。首先在系统里分配一段连接的内存地址作为数组,然后通过对map键进行\u003ccode\u003ehash算法\u003c/code\u003e(最终将键转换成了一个整型数字)定位到不同的桶bucket(数组的索引位置),然后将值存储到对应的bucket里\u003cimg src=\"https://blogstatic.haohtml.com/uploads/2021/01/7614fd6c619c3d1b07a787a82b19cad0.png\" alt=\"map hash算法\"\u003e\u003c/p\u003e\n\u003cp\u003e理想的情况下是一个\u003ccode\u003ebucket\u003c/code\u003e存储一个值,即数组的形式,时间复杂度为O(1)。\u003c/p\u003e\n\u003cp\u003e如果存在键值碰撞的话,可以通过 \u003ccode\u003e链表法\u003c/code\u003e 或者 \u003ccode\u003e开放寻址法\u003c/code\u003e 来解决。\u003c/p\u003e\n\u003cp\u003e链表法\u003cimg src=\"https://blogstatic.haohtml.com/uploads/2021/01/57c223fe323d13755f7b47d3ad427fe1-1.png\" alt=\"d2b5ca33bd970f64a6301fa75ae2eb22-1\"\u003e\u003c/p\u003e\n\u003cp\u003e开放寻址法\u003c/p\u003e\n\u003cp\u003e对于开放寻址法有多种算法,常见的有线性探测法,线性补偿探测法,随机探测法等,这里不再介绍。\u003c/p\u003e\n\u003ch2 id=\"map基本数据结构\"\u003emap基本数据结构\u003c/h2\u003e\n\u003ch3 id=\"hmap结构体\"\u003ehmap结构体\u003c/h3\u003e\n\u003cp\u003emap的核心数据结构定义在 \u003ccode\u003e/runtime/map.go\u003c/code\u003e\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e// A header for a Go map.\ntype hmap struct {\n\t// Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.\n\t// Make sure this stays in sync …\u003c/code\u003e\u003c/pre\u003e"