Goroutine Scheduler Overview

Goroutine 是 Golang 世界里的 Lightweight Thread 。 Golang 在语言层面支持多线程,代码可以通过 go 关键字来启动 Goroutine ,调用者不需要关心调用栈的大小,函数上下文等等信息就可以完成并发或者并行操作,加快了我们的开发速度。 分析 Goroutine 调度有利于了解和分析 go binary 的工作状况,所以接下来的内容将分析 runtime 中关于 Goroutine 调度的逻辑。 以下内容涉及到的代码是基于 go1.9rc2 版本。 1. Scheduler Structure 整个调度模型由 Goroutine/Processor/Machine 以及全局调度信息 sched 组成。 Global Runnable Queue runqueue ---------------------------- | G_10 | G_11 | G_12 | ... ---------------------------- P_0 Local Runnable Queue +-----+ +-----+ --------------- | M_3 | ---- | P_0 | <=== | G_8 | G_9 | +-----+ +-----+ --------------- | +-----+ | G_3 | Running +-----+ P_1 Local Runnable Queue +-----+ +-----+ --------------- | M_4 | ---- | P_1 | <=== | G_6 | G_7 | +-----+ +-----+ --------------- | +-----+ | G_5 | Running +-----+ 1....

February 6, 2018

Protobuf 3.0 编码

Protobuf 是 G 厂开源的序列化数据的方法,可用来通信或者存储数据。它采用 IDL 描述数据接口,使得不同语言编写的程序可以根据同一接口通信。不同编程语言也可以根据 IDL 的描述来生成对应数据结构,该数据结构用来编解码。为此,G 厂为主流开发语言都提供代码生成器(即 protoc )。 为了更好地了解一些细节,本文将主要描述 Protobuf 3.0 的编码规则。 Protobuf 里面主要采用 Varint 和 Zig-Zag 的方式来对整型数字进行编码。在理解 Protobuf 之前,需要先了解这两种编码方式。 Protobuf 采用是 Little Endian 的方式编码。 1. Varints int64, int32, uint64, uint32 都有固定的二进制位数。 如果将这些数字序列化成二进制流的时候,需要额外空间告知接收方数据的长度。对于采用 int64, uint64 这两种类型的数据而言,如果大部分时间都只是使用较小的数值,那么会极大地浪费传输带宽和存储空间。针对这两个问题,Protobuf 采用 Varints 的编码方式。 Varints 将源数据按照 7 bit 分组,每 7 bit 加 MSB (Most Significant Bit) 标识位来组成一个字节,其中 MSB 标识位用来判断是否存在后序分组。如果出现多组的情况,那么低有效位比特组优先。 64 有效位为 7 bit,不需要额外的字节,所以 MSB 比特位为 0。 64 = 0100 0000 => 0100 0000 16657 有效位为 15 bit,需要分成三组字节,前两组字节为了提示还存在后续字节,所以前两组字节的 MSB 比特位为 1。...

November 15, 2017

Go Interface & Duck Typing

Go 不需要像 Java 那样显式地使用 implement 说明某一数据类型实现了 interface,只要某一数据类型实现了 interface 所定义的方法名签,那么就称该数据类型实现了 interface。interface 的语言特性可以容易地做到接口定义和具体实现解耦分离,并将注意力转移到如何使用 interface ,而不是方法的具体实现,我们也称这种程序设计为 Duck Typing。文本将描述 Go 是如何通过 interface 来实现 Duck Typing。 本文提供的源代码都是基于 go1.7rc6 版本。 1. Duck Typing 了解实现原理之前,我们可以简单过一下 Go 的 Duck Typing 示例。 package main type Ducker interface { Quack() } type Duck struct {} func (_ Duck) Quack() { println("Quaaaaaack!") } type Person struct {} func (_ Person) Quack() { println("Aha?!") } func inTheForest(d Ducker) { d.Quack() } func main() { inTheForest(Duck{}) inTheForest(Person{}) } // result: // Quaaaaaack!...

June 5, 2017

让你的 shell 脚本变得可控

刚开始接触 shell 脚本的时候,最痛苦的地方在于出了问题,却不容易定位问题。 shell 脚本遇到错误,“大部分” 情况下都会继续执行剩下的命令,最后返回 Zero Exit Code 并不代表着结果正确。 这让人很难发现问题,它不像其他脚本语言,遇到 语法错误 和 typo 等错误时便会立即退出。 如果想要写出容易维护、容易 debug 的 shell 脚本,我们就需要让 shell 脚本变得可控。 set -e 默认情况下,shell 脚本遇到错误并不会立即退出,它还是会继续执行剩下的命令。 [[email protected] ~]# cat example #!/usr/bin/env bash # set -e sayhi # this command is not available. echo "sayhi" [[email protected] ~]# ./example ./example: line 4: sayhi: command not found sayhi 我们知道 Linux/Unix 用户等于系统的时候,内核会加载 .bashrc 或者 .bash_profile 里的配置。 不同 shell 版本会使用不同的 rc/profile 文件,比如 zsh 版本的 rc 文件名是 ....

March 20, 2017

shebang - #!

写脚本的时候通常会在脚本的开头加上 shebang, 系统会将这段内容作为解释器指令,比如 bash shell 脚本。 $> cat example #!/usr/bin/bash echo "HaHa" $> chmod +x ./example $> ./example HaHa 只要为脚本添加了可执行的属性,那么内核在执行脚本的时候,会调用 shebang 描述的解释器来执行脚本。 ./exmaple 其实等价于 /usr/bin/bash ./example。shebang 描述的解释器需要写其绝对路径或者相对路径,因为内核并不会在用户设置的 PATH 里找解释器。关于 shebang,讨论最多的应该是 兼容性 和 版本控制 问题。 兼容性 Linux 和 Unix 在存放解释器的具体路径不太一致,比如 Linux 会放到 /usr/bin/ 中,而 openBSD 会放到 /usr/local/bin/ 中。不同包管理器在安装解释器的时候,存放的位置也不尽相同。 当你在 Mac 上写了 shell 脚本,测试并提交到代码库。 结果等到部署的那一天,执行脚本的时候发现找不到解释器了。 为了解决这个问题,可以通过 env 来解决,因为它在 Linux 和 Unix 存放的位置相同。 $> cat exmaple #!/usr/bin/env bash echo "HaHa" env 会在用户设置的 PATH 中查找解释器第一次出现的具体路径。 虽然办法比较 tricky,但是这种方式能解决脚本解释器的兼容性问题。...

March 19, 2017