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"
February 11, 2021
g0 特殊的goroutine
"\u003cp\u003e在上篇 \u003ca href=\"https://blog.haohtml.com/archives/21010\"\u003e《golang中G、P、M 和 sched 三者的数据结构》\u003c/a\u003e文章中,我们介绍了\u003ccode\u003eG\u003c/code\u003e、\u003ccode\u003eM\u003c/code\u003e 和 \u003ccode\u003eP\u003c/code\u003e 的数据结构,其中M结构体中第一个字段是 \u003ccode\u003eg0\u003c/code\u003e,这个字段也是一个 \u003ccode\u003egoroutine\u003c/code\u003e,但和普通的 \u003ccode\u003egoroutine\u003c/code\u003e 有一些区别,它主要用来实现对 goroutine 进行调度,下面我们将介绍它是如何实现调度goroutine的。\u003c/p\u003e\n\u003cp\u003e另外还有一个 \u003ccode\u003em0\u003c/code\u003e , 它是一个全局变量,与 \u003ccode\u003eg0\u003c/code\u003e 的区别如下\u003cimg src=\"https://blogstatic.haohtml.com/uploads/2021/03/8e229b7806870bf4f17da207665b8a43.jpg\" alt=\"\"\u003eM0 与 g0的区别\u003c/p\u003e\n\u003cp\u003e本文主要翻译自 \u003ca href=\"https://medium.com/a-journey-with-go/go-g0-special-goroutine-8c778c6704d8\"\u003eGo: g0, Special Goroutine\u003c/a\u003e 一文,有兴趣的可以查阅原文,作者有一系列高质量的文章推荐大家都阅读一遍。ℹ️ 本文基于 Go 1.13。\u003c/p\u003e\n\u003cp\u003e我们知道在Golang中所有的\u003ccode\u003egoroutine\u003c/code\u003e的运行都是由\u003ccode\u003e调度器\u003c/code\u003e来负责管理的,go调度器尝试为所有的\u003ccode\u003egoroutine\u003c/code\u003e来分配运行时间,当有\u003ccode\u003egoroutine\u003c/code\u003e被阻塞或终止时,调度器会通过对\u003ccode\u003egoroutine\u003c/code\u003e 进行调度以此来保证所有CPU都处于忙碌状态,避免有CPU空闲状态浪费时间。\u003c/p\u003e\n\u003ch2 id=\"goroutine-切换规则\"\u003egoroutine 切换规则\u003c/h2\u003e\n\u003cp\u003e在此之前我们需要记住一些goroutine切换规则。runtime源码\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e// …\u003c/code\u003e\u003c/pre\u003e"
January 26, 2021
Golang环境变量之GODEBUG
"\u003cp\u003e\u003ccode\u003eGODEBUG\u003c/code\u003e 是 golang中一个控制runtime调度变量的变量,其值为一个用逗号隔开的 name=val对列表,常见有以下几个命名变量。\u003c/p\u003e\n\u003ch2 id=\"allocfreetrace\"\u003eallocfreetrace\u003c/h2\u003e\n\u003cp\u003e设置\u003ccode\u003eallocfreetrace = 1\u003c/code\u003e会导致对每个分配进行概要分析,并在每个对象的分配上打印堆栈跟踪并释放它们。\u003c/p\u003e\n\u003ch2 id=\"clobberfree\"\u003eclobberfree\u003c/h2\u003e\n\u003cp\u003e设置 \u003ccode\u003eclobberfree=1\u003c/code\u003e会使垃圾回收器在释放对象的时候,对象里的内存内容可能是错误的。\u003c/p\u003e\n\u003ch2 id=\"cgocheck\"\u003ecgocheck\u003c/h2\u003e\n\u003cp\u003ecgo相关。\u003c/p\u003e\n\u003cp\u003e设置 \u003ccode\u003ecgocheck=0\u003c/code\u003e 将禁用当包使用cgo非法传递给go指针到非go代码的检查。如果值为1(默认值)会启用检测,但可能会丢失有一些错误。如果设置为2的话,则不会丢失错误。但会使程序变慢。\u003c/p\u003e\n\u003ch2 id=\"efence\"\u003eefence\u003c/h2\u003e\n\u003cp\u003e设置 \u003ccode\u003eefence=1\u003c/code\u003e会使回收器运行在一个模式。每个对象都在一个唯一的页和地址,且永远也不会被回收。\u003c/p\u003e\n\u003ch2 id=\"gccheckmark\"\u003egccheckmark\u003c/h2\u003e\n\u003cp\u003eGC相关。\u003c/p\u003e\n\u003cp\u003e设置 \u003ccode\u003egccheckmark=1\u003c/code\u003e 启用验证垃圾回收器的并发标记,通过在STW时第二个标记阶段来实现,如果在第二阶段的时候,找到一个可达对象,但未找到并发标记,则GC会发生Panic。\u003c/p\u003e\n\u003ch2 id=\"gcpacertrace\"\u003egcpacertrace …\u003c/h2\u003e"
January 26, 2021
Golang中MemStats的介绍
"\u003cp\u003e平时在开发中,有时间需要通过查看内存使用情况来分析程序的性能问题,经常会使用到 MemStats 这个结构体。但平时用到的都是一些最基本的方法,今天我们全面认识一下MemStas。\u003c/p\u003e\n\u003cp\u003e相关文件为 \u003ccode\u003esrc/runtime/mstats.go\u003c/code\u003e ,本文章里主要是与内存统计相关。\u003c/p\u003e\n\u003ch2 id=\"memstats-结构体\"\u003eMemStats 结构体\u003c/h2\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e// MemStats记录有关内存分配器的统计信息\ntype MemStats struct {\n\t// General statistics.\n\tAlloc uint64\n\tTotalAlloc uint64\n\tSys uint64\n\tLookups uint64\n\tMallocs uint64\n\tFrees uint64\n\n\t// Heap memory statistics.\n\tHeapAlloc uint64\n\tHeapSys uint64\n\tHeapIdle uint64\n\tHeapInuse uint64\n\tHeapReleased uint64\n\tHeapObjects uint64\n\n\t// Stack memory statistics.\n\tStackInuse uint64 …\u003c/code\u003e\u003c/pre\u003e"
January 25, 2021
Golang中Stack的管理
"\u003ch1 id=\"栈的演变\"\u003e栈的演变\u003c/h1\u003e\n\u003cp\u003e在 Go1.13之前的版本,Golang 栈管理是使用的\u003ccode\u003e分段栈(Segment Stacks)\u003c/code\u003e机制来实现的,由于sgement stack 存在 \u003ccode\u003e热分裂(hot split\u003c/code\u003e)的问题,后面版本改为采用\u003ccode\u003e连续栈( [Contiguous stacks](https://docs.google.com/document/d/1wAaf1rYoM4S4gtnPh0zOlGzWtrZFQ5suE8qr2sD8uWQ/pub))\u003c/code\u003e机制( \u003ca href=\"https://golang.org/doc/go1.3#stacks\"\u003e说明\u003c/a\u003e)。\u003c/p\u003e\n\u003ch2 id=\"分段栈segment-stack\"\u003e分段栈(Segment Stack)\u003c/h2\u003e\n\u003cp\u003e分段栈是指开始时只有一个stack,当需要更多的 stack 时,就再去申请一个,然后将多个stack 之间用双向链接连接在一起。当使用完成后,再将无用的 \u003ccode\u003estack\u003c/code\u003e 从链接中删除释放内存。\u003cimg src=\"https://blogstatic.haohtml.com/uploads/2021/01/d2b5ca33bd970f64a6301fa75ae2eb22-3.png\" alt=\"\"\u003esegment stack\u003c/p\u003e\n\u003cp\u003e可以看到这样确实实现了stack 按需增长和收缩,在增加新stack时不需要拷贝原来的数据,系统使用率挺高的。但在一定特别的情况下会存在 \u003ccode\u003e热分裂(hot split)\u003c/code\u003e 的问题。\u003c/p\u003e\n\u003cp\u003e当一个 stack 即将用完的时候,任意一个函数都会导致堆栈的扩容,当函数执行完返回后,又要触发堆栈的收缩。如果这个操作 …\u003c/p\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 21, 2021
golang中G、P、M 和 sched 三者的数据结构
"\u003cp\u003eG、P、M 三者是golang实现高并发能的最为重要的概念,\u003ccode\u003eruntime\u003c/code\u003e 通过 \u003ccode\u003e调度器\u003c/code\u003e 来实现三者的相互调度执行,通过 \u003ccode\u003ep\u003c/code\u003e 将用户态的 \u003ccode\u003eg\u003c/code\u003e 与内核态资源 \u003ccode\u003em\u003c/code\u003e 的动态绑定来执行,以减少以前通过频繁创建内核态线程而产生的一系列的性能问题,充分发挥服务器最大有限资源。\u003cimg src=\"https://blogstatic.haohtml.com/uploads/2021/01/7c68d000148bf601267b43631c795bfd.png\" alt=\"\"\u003eGPM 协作\u003c/p\u003e\n\u003cp\u003e调度器的工作是将一个 G(需要执行的代码)、一个 M(代码执行的地方)和一个 P(代码执行所需要的权限和资源)结合起来。\u003c/p\u003e\n\u003cp\u003e所有的 g、m 和 p 对象都是分配在\u003ccode\u003e堆\u003c/code\u003e上且永不释放的,所以它们的内存使用是很稳定的。得益于此,runtime 可以在调度器实现中避免写屏障。当一个G执行完成后,可以放入pool中被再次使用,避免重复申请资源。\u003c/p\u003e\n\u003cp\u003e本节主要通过阅读runtime源码来认识这三个组件到底长的是什么样子,以此加深对 GPM 的理解。go version go1.15.6\u003c/p\u003e\n\u003cp\u003e理解下文前建议先阅读一下 \u003ccode\u003esrc/runtime/HACKING.md\u003c/code\u003e 文件,中文可阅读 \u003ca href=\"https://www.purewhite.io/2019/11/28/runtime-hacking-translate/\"\u003e这里\u003c/a\u003e,这个文件内容是面向开发者理解\u003ccode\u003eruntime\u003c/code\u003e的很值得看一看。\u003c/p\u003e\n\u003cp\u003e本文若没有指定源码文件路径,则默认为 \u003ccode\u003esrc/runtime/runtime2.go\u003c/code\u003e。\u003c/p\u003e\n\u003ch1 id=\"g\"\u003eG\u003c/h1\u003e\n\u003cp\u003eG …\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"
December 26, 2020
Golang并发模式之扇入FAN-IN和扇出FAN-OUT
"\u003cp\u003e在现实世界中,经常有一些工作是属于流水线类型的,它们的每一个步骤都是紧密关联的,第一步先做什么,再做什么,最后做什么。特别是制造业这个行业,基本全是流水线生产车间。在我们开发中也经常遇到这类的业务场景。\u003c/p\u003e\n\u003cp\u003e假如我们有个流水线共分三个步骤,分别是 job1、job2和job3。代码: \u003ca href=\"https://play.golang.org/p/e7ZlP9ofXB3\"\u003ehttps://play.golang.org/p/e7ZlP9ofXB3\u003c/a\u003e\u003c/p\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:#f92672\"\u003epackage\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\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:#f92672\"\u003eimport\u003c/span\u003e (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;fmt\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;time\u0026#34;\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\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ejob1\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ecount\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003echan\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eoutCh\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e make(\u003cspan style=\"color:#66d9ef\"\u003echan\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e2\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\t\u003cspan style=\"color:#66d9ef\"\u003ego\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003edefer\u003c/span\u003e close(\u003cspan style=\"color:#a6e22e\"\u003eoutCh\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e; \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e \u0026lt; \u003cspan style=\"color:#a6e22e\"\u003ecount\u003c/span\u003e; \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSleep\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSecond\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;job1 finish:\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003eoutCh\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}()\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\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eoutCh\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\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ejob2\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003einCh …\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/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/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e"
December 24, 2020
重新认识Golang中的空结构体
"\u003col\u003e\n\u003cli\u003e认识空结构体\u003c/li\u003e\n\u003cli\u003e低层实现原理\u003c/li\u003e\n\u003cli\u003e空结构体之内存对齐\u003c/li\u003e\n\u003cli\u003e应用场景\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e在golang中,如果我们想实现一个set集合的话,一般会使用map来实现,其中将set的值作为map的键,对于map的值一般使用一个空结构体来实现,当然对map值也可以使用一个bool类型或者数字类型等,只要符合一个键值对应关系即可。但我们一般推荐使用struct{}来实现,为什么呢?\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003epackage main\n\nimport \u0026#34;fmt\u0026#34;\n\nfunc main() {\n\tm := make(map[int]struct{})\n\tm[1] = struct{}{}\n\tm[2] = struct{}{}\n\n\tif _, ok := m[1]; ok {\n\t\tfmt.Println(\u0026#34;exists\u0026#34;)\n\t}\n\n}\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e上面这段代码是一个很简单的使用map实现的set功能,这里是采用空结构体struct{}来实现。\u003c/p\u003e\n\u003cp\u003e在分析为什么使用struct{}以前,我看先认识一个struct。\u003c/p\u003e\n\u003ch2 id=\"认识空结构体-struct\"\u003e认识空结构体 struct\u003c/h2\u003e\n\u003cp\u003e我们先看一个这段代码\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003epackage main\n\nimport ( …\u003c/code\u003e\u003c/pre\u003e"
December 17, 2020
Golang中的内存重排(Memory Reordering)
"\u003ch2 id=\"什么是内存重排\"\u003e什么是内存重排\u003c/h2\u003e\n\u003cp\u003e内存重排指的是内存的读/写指令重排。\u003c/p\u003e\n\u003ch2 id=\"为什么要内存重排\"\u003e为什么要内存重排\u003c/h2\u003e\n\u003cp\u003e为了提升程序执行效率,减少一些IO操作,一些硬件或者编译器会对程序进行一些指令优化,优化后的结果可能会导致程序编码时的顺序与代码编译后的先后顺序不一致。\u003c/p\u003e\n\u003cp\u003e就拿做饭场景来说吧,是先蒸米还是先炒菜,这两者是没有冲突的,编译器在编译时有可能与你要求的顺序不一样。\u003c/p\u003e\n\u003ch2 id=\"编译器重排\"\u003e编译器重排\u003c/h2\u003e\n\u003cp\u003e如下面这段代码\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eX = 0\nfor i in range(100):\n X = 1\n print X\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e要实现打印100次1,很显示在for里面每次都执行X=1语句有些浪费资源,如果将初始变量值修改为1,是不是要快的多。编译器也分析到了这一点,于是在编译时对代码做了以下优化\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eX = 1\nfor i in range(100):\n print X\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e最终输出结果是一样的,两段代码功能也一样。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e但是\u003c/strong\u003e如果此时有另一个线程里执行了一个 X=0 的赋值语句的话(两个线程同时运行),那么输出结果就可能与我们想要的不一样了。\u003c/p\u003e\n\u003cp\u003e优化前情况:第一个线程执行到了第3次print X 后,第二个线程执行了X=0,把X 的值进行了修改,结果就有可能是1110 …\u003c/p\u003e"
November 19, 2020
Golang中的并发原语 Singleflight
"\u003cp\u003e在Golang中有一个并发原语是 \u003ca href=\"https://pkg.go.dev/golang.org/x/sync/singleflight\"\u003eSingleflight\u003c/a\u003e,好像知道的开发者并不多。其中著名的 \u003ca href=\"https://github.com/golang/groupcache\"\u003ehttps://github.com/golang/groupcache\u003c/a\u003e 就用到了这个并发原语。\u003c/p\u003e\n\u003ch2 id=\"golang版本\"\u003eGolang版本\u003c/h2\u003e\n\u003cp\u003ego1.15.5\u003c/p\u003e\n\u003ch2 id=\"相关知识点\"\u003e相关知识点\u003c/h2\u003e\n\u003cp\u003emap、Mutex、channel、\u003c/p\u003e\n\u003ch2 id=\"使用场景\"\u003e使用场景\u003c/h2\u003e\n\u003cp\u003e一般用在对指定资源频繁操作的情况下,如高并发下的“缓存击穿”问题。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e缓存击穿:一个存在的key,在缓存过期的瞬间,同时有大量的请求过来,造成所有请求都去DB读取数据,这些请求都会击穿缓存到DB,造成瞬时DB请求量大、压力瞬间骤增,导致数据库负载过高,影响整个系统正常运行。(缓存击穿不同于 缓存雪崩 和 缓存穿透)\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003e怎么理解这个原语呢,简单的讲就是将对同一个资源的多个请求合并为一个请求。\u003c/p\u003e\n\u003cp\u003e举例说明,假如当有10万个请求来获取同一个key的值的时候,正常情况下会执行10万次get操作。而使用singleflight并发语后,只需要首次的地个请求执行一次get操作就可以了,其它请求再过来时,只需要只需要等待即可。待执行结果返回后,再把结果分别返回给等待中的请求,每个请求再返回给客户端,由此看看,在一定的高 …\u003c/p\u003e"
September 27, 2020
Protobuf协议实现原理
"\u003cp\u003e\u003ccode\u003eprotobuf\u003c/code\u003e是Google开源的一款支持跨平台、语言中立的结构化数据描述和高性能序列化协议,此协议完全基于二进制,所以性能要远远高于JSON/XML。由于出色的传输性能因此常见于微服务之间的通讯,其中最为著名的是Google开源的 \u003ca href=\"https://grpc.io/\"\u003egRPC\u003c/a\u003e 框架。\u003c/p\u003e\n\u003cp\u003e那么protobuf是如何实现高性能的,又是如何实现数据的编码和解码的呢?\u003c/p\u003e\n\u003ch2 id=\"protobuf协议原理\"\u003eprotobuf协议原理\u003c/h2\u003e\n\u003cp\u003e基于128bits的数据存储方式(Base 128 Varints)\u003c/p\u003e\n\u003cp\u003eVarint 是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。\u003c/p\u003e\n\u003cp\u003e比如对于 int32 类型的数字,一般需要 4 个 byte 来表示。但是采用 Varint,对于很小的 int32 类型的数字,则可以用 1 个 byte 来表示。当然凡事都有好的也有不好的一面,采用 Varint 表示法,大的数字则需要 5 个 byte 来表示。从统计的角度来说,一般不会所有的消息中的数字都是大数,因此大多数情况下,采用 Varint 后,可以用更少的字节数来表示数字信息\u003c/p\u003e\n\u003cp\u003eVarint 中的每个 byte 的最 …\u003c/p\u003e"