理解Golang的Time结构

在 golang 中创建并打印一个时间对象,会看到如下输出 2018-10-26 14:15:50.306558969 +0800 CST m=+0.000401093 前面表示的意义好理解,分别是年月日和时间时区,最后的 m=+xxxx 这部分代表什么呢? Monotonic Clocks 和 Wall Clocks 根据 golang 的 time 包的文档可以知道,golang 的 time 结构中存储了两种时钟,一种是 Wall Clocks,一种是 Monotonic Clocks。 Wall Clocks,顾名思义,表示墙上挂的钟,在这里表示我们平时理解的时间,存储的形式是自 1970 年 1 月 1 日 0 时 0 分 0 秒以来的时间戳,当系统和授时服务器进行校准时间时间操作时,有可能造成这一秒是 2018-1-1 00:00:00,而下一秒变成了 2017-12-31 23:59:59 的情况。Monotonic Clocks,意思是单调时间的,所谓单调,就是只会不停的往前增长,不受校时操作的影响,这个时间是自进程启动以来的秒数。 如果每隔一秒生成一个 Time 并打印出来,就会看到如下输出。 2018-10-26 14:15:50.306558969 +0800 CST m=+0.000401093 2018-10-26 14:15:51.310559881 +0800 CST m=+1.004425285 2018-10-26 14:15:52.311822486 +0800 CST m=+2.005711106 2018-10-26 14:15:53.314599457 +0800 CST m=+3.008511329 2018-10-26 14:15:54.31882248 +0800 CST m=+4.012757636 2018-10-26 14:15:55.320059921 +0800 CST m=+5.014018292 2018-10-26 14:15:56.323814998 +0800 CST m=+6.017796644 2018-10-26 14:15:57.324858749 +0800 CST m=+7.018863606 2018-10-26 14:15:58.325164174 +0800 CST m=+8.019192224 2018-10-26 14:15:59.329058535 +0800 CST m=+9.023109863 2018-10-26 14:16:00.329591268 +0800 CST m=+10.023665796 可以看到 m=+后面所显示的数字,就是文档中所说的 Monotonic Clocks。 ...

十月 26, 2018 · 2 分钟 · Zhiya

Go的栈空间管理

栈空间管理的基本逻辑 go 语言通过 goroutine 提供了并发编程支持,goroutine 是 go 运行库的功能,而不是操作系统线程实现的,goroutine 可以被理解成一个用户态的线程。 既然 goroutine 是由 go 运行库管理的,那么 go 运行库也需要为每个 goroutine 创建并管理相应的栈空间,为每个 goroutine 分配的栈空间不能太大,goroutine 开多时会浪费大量空间,也不能太小,会导致栈溢出。go 语言选择栈的栈空间管理的方式是,一开始给一个比较小的空间,随着需要自动增长。当 goroutine 不需要那么大的空间时,栈空间也要自动缩小。 分段栈 Segment Stacks 在 go 1.3 之前,go 使用分段栈。 分段栈实现了一种不连续但是可以持续增长的栈,开始时,栈只有一个段,当需要更多的栈空间时,会分配一个新的段,和上一个栈双向链接。这样,一个栈就是由多个双向链接的段所组成的。当新分配的段使用完毕后,新段会被释放掉。 分段栈实现了栈的按需收缩,在增加新分段时也不需要对原有分段中的数据进行拷贝,使得 goroutine 的使用代价非常低廉。 分段栈的好处是可以按需增长,空间利用率比较高,然而分段栈在某些情况下也存在一定的瑕疵。当一个段即将用尽,这时使用 for 循环执行一个比较耗空间的函数,会导致函数执行时 goroutine 进行段的分配,而执行完成返回时,进行段的销毁,这样就会导致在循环中出现多次栈的扩容和收缩,造成很大的性能损失,这种情况被称作栈分裂(Stack Split)。 连续栈 Contiguous Stacks go 1.3 推出了连续栈,连续栈使用了另外一种策略,不再把栈分成一段一段的,当栈空间不够时,直接 new 一个 2 倍大的栈空间,并将原先栈空间中的数据拷贝到新的栈空间中,而后销毁旧栈。这样当出现栈空间触及边界时,不会产生栈分裂的情况。 继续假设当前栈空间即将用尽,并且需要在 for 循环中执行一个比较消耗空间的函数。当该函数执行时,栈空间发生了扩容,变成原先 2 倍大小,函数执行完成一次后,栈空间的使用量缩小回执行前的大小,但是栈空间的使用量并没有小于栈大小的 1/4,不会触发栈收缩,所以在整个 for 循环执行过程中,不会反复触发栈空间的收缩扩容。 总结 相比于分段栈,连续栈避免了某些场景下栈空间的的频繁伸缩。有一点需要注意的是,连续栈的收缩也是需要重新申请一段空间(原先的 1/2 大小),并进行栈拷贝操作的。

十月 11, 2018 · 1 分钟 · Zhiya

Go语言中defer的一些坑

defer 语句是 Go 中一个非常有用的特性,可以将一个方法延迟到包裹该方法的方法返回时执行,在实际应用中,defer 语句可以充当其他语言中 try…catch…的角色,也可以用来处理关闭文件句柄等收尾操作。 defer 触发时机 A “defer” statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking. Go 官方文档中对 defer 的执行时机做了阐述,分别是。 包裹 defer 的函数返回时 包裹 defer 的函数执行到末尾时 所在的 goroutine 发生 panic 时 defer 执行顺序 当一个方法中有多个 defer 时, defer 会将要延迟执行的方法“压栈”,当 defer 被触发时,将所有“压栈”的方法“出栈”并执行。所以 defer 的执行顺序是 LIFO 的。 ...

九月 14, 2018 · 2 分钟 · Zhiya

使用gofmt格式化代码

对于一门编程语言来说,代码格式化是最容易引起争议的一个问题,不同的开发者可能会有不同的编码风格和习惯,但是如果所有开发者都能使用同一种格式来编写代码,开发者就可以将宝贵的时间专注在语言要解决的问题上。 gofmt 介绍 Golang 的开发团队制定了统一的官方代码风格,并且推出了 gofmt 工具(gofmt 或 go fmt)来帮助开发者格式化他们的代码到统一的风格。gofmt 是一个 cli 程序,会优先读取标准输入,如果传入了文件路径的话,会格式化这个文件,如果传入一个目录,会格式化目录中所有.go 文件,如果不传参数,会格式化当前目录下的所有.go 文件。 gofmt 默认不对代码进行简化,使用-s 参数可以开启简化代码功能,具体来说会进行如下的转换: 去除数组、切片、Map 初始化时不必要的类型声明: 如下形式的切片表达式: []T{T{}, T{}} 将被简化为: []T{{}, {}} 去除数组切片操作时不必要的索引指定 如下形式的切片表达式: s[a:len(s)] 将被简化为: s[a:] 去除迭代时非必要的变量赋值 如下形式的迭代: for x, _ = range v {...} 将被简化为: for x = range v {...} 如下形式的迭代: for _ = range v {...} 将被简化为: for range v {...} gofmt 命令参数列表如下: usage: gofmt [flags] [path ...] -cpuprofile string write cpu profile to this file -d display diffs instead of rewriting files -e report all errors (not just the first 10 on different lines) -l list files whose formatting differs from gofmt's -r string rewrite rule (e.g., 'a[b:len(a)] -> a[b:]') -s simplify code -w write result to (source) file instead of stdout 可以看到,gofmt 命令还支持自定义的重写规则,使用-r 参数,按照 pattern -> replacement 的格式传入规则。 ...

七月 17, 2018 · 2 分钟 · Zhiya

Golang环境安装和依赖管理

2015 年,Go 1.5 加入了一个试验性的 vendor 机制(到 2016 年的 Go 1.6 版变为默认开启),vendor 机制就是在项目中加入了 vendor 文件夹,用于存放依赖,这样就可以将不同项目的依赖隔离开。 Golang 一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。Golang 提供了方便的安装包,支持 Windows、Linux、Mac 系统。 下载安装包 Golang 的官网是https://golang.org/,如果官网打不开,可以访问https://golang.google.cn/这个域名。在官网点击 Download Go 会进入下载页,可以看到这里提供了针对各个系统的安装包,也提供了源码,可以下载源码编译安装。 下载运行安装包后,在 terminal 中执行 go env 命令,如果出现下面的输出说明已经安装成功。 GOROOT 与 GOPATH 仔细看上面的输出,会发现其中有一个 GOPATH,又有一个 GOROOT,那么到底哪个才是 Golang 的运行环境呢。 首先访问一下 GOROOT 这个路径,会发现其中包含 bin、lib 等文件夹。GOROOT 就是 Golang 的安装路径,其中包含 Golang 编译、工具、标准库等,在安装后就会存在。 和 GOROOT 不同,GOPATH 是工作空间路径,从 go 1.8 开始,如果 GOPATH 没有被设置,会有一个默认值,在 Unix 上为$HOME/go,在 Windows 上为%USERPROFILE%/go,当调用 go build 时,它会在 GOPATH 中寻找源码。访问一下 GOPATH 这个路径,会发现其中只有 pkg、bin、src 三个文件夹,并且里面基本是空的,这是一个约定的目录结构,src 文件夹用来存放源码、pkg 存放编译后生成的文件,bin 存放编译后生成的可执行文件。项目代码需要在 GOPATH/src 路径下。 ...

七月 10, 2018 · 1 分钟 · Zhiya