#75 Golang GC 与 STW [编辑中]

2022-04-11

相信 Golang 开发者可能前段时间见过两位大佬关于 Golang 性能的技术讨论(撕X),其中一个很重要的点就是涉及 Golang GC 对性能的影响。

我之前的开发经验几乎全部集中于 PHP,JS,Python 等脚本型语言,较少需要涉及 GC(只有几次涉及服务内存占用的时候检查过 GC)。
接触到 Golang 之后,如果不去研究 GC 可能很多疑问是无法解决的,很多时候的优化也和 GC 密切相关。

#74 Golang 版本

2022-04-10

2007 Robert Griesemer、Rob Pike 和 Ken Thompson 三位巨头都在谷歌任职,开始设计一门全新的语言。

Release Status Release date Maintenance end
go1 End-of-Life 2012-03-28 2013-12-01
go1.1 End-of-Life 2013-05-13 2014-06-18
go1.2 End-of-Life 2013-12-01 2014-12-10
go1.3 End-of-Life 2014-06-18 2015-08-19
go1.4 End-of-Life 2014-12-10 2016-02-17
go1.5 End-of-Life 2015-08-19 2016-08-15
go1.6 End-of-Life 2016-02-17 2017-02-16
go1.7 End-of-Life 2016-08-15 2017-08-24
go1.8 End-of-Life 2017-02-16 2018-02-16
go1.9 End-of-Life 2017-08-24 2018-08-24
go1.10 End-of-Life 2018-02-16 2019-02-25
go1.11 End-of-Life 2018-08-24 2019-09-03
go1.12 End-of-Life 2019-02-25 2020-02-25
go1.13 End-of-Life 2019-09-03 2020-08-11
go1.14 End-of-Life 2020-02-25 2021-02-16
go1.15 End-of-Life 2020-08-11 2021-08-16
go1.16 End-of-Life 2021-02-16 2022-03-15
go1.17 End-of-Life 2021-08-16 2022-08-02
go1.18 End-of-Life 2022-03-15 2023-02-01
go1.19 Maintenance 2022-08-02 Q3 2023
go1.20 Current 2023-02-01 Q1 2024
go1.21 Planned Q3 2023 Q3 2024

1.16 及以前的版本

我开始学习 Golang 的时候,已经是 1.5 和 1.6 版本了,更早的版本只需要对重大特性引入时间做一个简单了解。
现在主要的开发版本已经升级到 1.18 和 1.20。

  • 1.0
  • 第一个正式版本,核心特性都已经包含在里面,承诺以后语法不会发生不兼容变化
  • 至今为止,承诺都实现了。后面的版本几乎很少有激动人心的功能,绝大多数时候都是各种改进
  • 1.1
  • 1.2
  • 支持 slice[low:high:max] 语法
  • 1.4
  • for range 语法引入
  • 1.5
  • 实现自举
  • 1.9
  • type alias 语法引入
  • 1.11
  • Go Modules 引入
  • 1.12
  • 开始支持 TLS 1.3
    • 需要通过 GODEBUG=tls13=1 开启(不能通过 tls Config MaxVersion 限制版本)
    • 0-RTT 不被支持(到 2023 年,依然没有提供支持
  • 1.13
  • TLS 1.3 默认开启
  • 1.14
  • TLS 1.3 成为默认选项,并且无法通过 GODEBUG 关闭
  • 异步可抢占 goroutine
  • 1.16
  • Go Modules 成为默认(GO111MODULE = on
  • 支持将静态文件打包进可执行文件(//go:embed
  • 弃用 io/ioutil

参考:

1.17

  • Module graph pruning(依赖图修剪)
  • 切片转数组指针

参考:

1.18

  • 泛型(Golang 发布以来最大的语法变更)
  • fuzzing(模糊测试)
  • TLS 客户端默认版本改为 1.2

参考:

1.19

没有特别值得关注的的改动。

参考:

1.20

  • 切片转数组

参考:

1.21

  • min, maxclear
  • 循环变量捕获 (loop variable capture)
    参见:2023/07/05,Go 1.21 for 语义变更
  • 标准库:log/slog, slices, maps, cmp

参考:

#72 Go 语言有什么优势

2022-04-02
  • 语法简单
  • 确实没有很多语言特性,容易上手,但说什么大道至简,Less is more 等价值观就有点扯了。
  • 静态类型、强类型、编译型语言
  • 性能
  • 并发:自带的协程实现(goroutine + channel)在开发效率和性能之间达成了一个不错的平衡,非常优秀
  • 跨平台
  • 接口,反射,GC
  • C 嵌入
  • 便于工程化:工具链齐全,代码规范严格
  • 没有历史负担:但是向前兼容的承诺也是
  • Go1 兼容性承诺(最重要特性之一):但是也令人担忧,时间一长,可能就有太重的包袱
  • 基于消息传递的通信机制
  • 核心开发团队名气非常大
  • robert griesemer
  • rob pike
  • ken thompson
  • russ cox
  • 生态:有大公司站台,有杀手级项目(Docker, K8S 等)

存在的问题

  1. 异常处理的设计
  2. 标准库相对太薄弱
  3. 生态:缺乏主流框架
  4. 包管理机制上存在的问题
  5. 没有泛型(1.18 以前)

场景

  • 云原生
  • 基础设施项目
  • Web 开发
  • 网络编程

#71 转载:一道正确率只有15%的命名返回值和闭包的问题

2022-03-01

哈喽,大家好,我是asong。今天新注册了twitter,在里面没事瞎逛的时候,发现了一道有意思的题,他是由Redhat的首席工程师、Prometheus开源项目维护者 Bartłomiej Płotka 发出的,经调查显示,这道题的正确率只有15.2%,惨目忍睹,接下来我们就一起来看一下这道题~
原文地址:https://twitter.com/bwplotka/status/1495002204163678211

题目:下面这段代码输出结果是多少?

func aaa() (done func(), err error) {
    return func() { print("aaa: done") }, nil
}

func bbb() (done func(), _ error) {
    done, err := aaa()
    return func() { print("bbb: surprise!"); done() }, err
}

func main() {
    done, _ := bbb()
    done()
}
  • A. aaa: done
  • B. bbb: surprise!aaa: done
  • C. 永远不会结束
  • D. 编译错误

解析

答案:C 永远不会结束

这道题考查的点就是命名返回值 + 闭包,把上面的代码换成等效的匿名返回值代码你就明白了:

func aaa() (func(), error) {
    var done func()
    done = func() {
        print("aaa: done")
    }
    return done, nil
}

func bbb() (func(), error) {
    var done func()
    done, err := aaa()
    done = func() {
        print("bbb: surprise!");
        done()
    }
    return done, err
}

func main() {
    done, _ := bbb()
    done()
}

这其实是 Go 语言设计上一个 feature,当 Go 语言的返回值赋给我们特殊的"返回参数"时,如果它们被命名了,在 return 之后,我们可以在函数主体完成后的任何执行过程中引用那些带有这些名称的值,在 defer 或闭包中一样。

我们在说回这道题,在 bbb() 函数内我们使用了命名返回值 done func(), _ error,使用短变量声明 done, err := aaa() 接收 aaa() 的返回值,这里变量 done 并不是一个新变量,这就要说到Go语言的短变量声明的语法糖了,在多变量声明中,如果其中一个变量是新的,可以使用 := 声明,编译器会进行类型推断和赋值,已经声明的变量不会重新声明,直接在原变量上赋值;之后我们return的是一个闭包函数,闭包里的done值并不会被提前解析,在bbb()函数结束后,实际对应的代码就成了这样,变成了递归。

done = func() {
    print("bbb: surprise!");
    done()
}

如果我们把代码在改成这样:

func bbb() (func(), error) {
    var done func()
    done, err := aaa()
    return func() {
        print("bbb: surprise!");
        done()
    }, err
}

答案就是【B】:bbb: surprise!aaa: done

总结

一道看似简单的题,其中蕴涵的知识点确有很多,这就说明了解设计原理是多么的重要,Go语言资深工程师的路上任重道远呀~。

#70 Go 枚举

2022-02-07

编程语言中一般都有枚举类型。可以用来替换代码中的那些有一定范围的常量,减少幻数的使用,提升代码可读性。
有些语言的枚举支持遍历等操作,有一些语言的枚举还支持枚举值和枚举名字的映射。

Go 没有在语言层面实现枚举,只能通过定义一组变量。

比如(src/runtime/time.go):

const (
        timerNoStatus = iota
        timerWaiting
        timerRunning
        timerDeleted
        timerRemoving
        timerRemoved
        timerModifying
        timerModifiedEarlier
        timerModifiedLater
        timerMoving
)

官方代码(src/time/time.go)中有些地方会给这些变量加一个自定义类型:

// A Weekday specifies a day of the week (Sunday = 0, ...).
type Weekday int

const (
        Sunday Weekday = iota
        Monday
        Tuesday
        Wednesday
        Thursday
        Friday
        Saturday
)

参考资料与拓展阅读

#69 Go HTTP 客户端

2022-01-29

原生

之前的文章:Golang HTTP 以及 HTML/XML 解析

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

func main() {
    resp, err := http.Get("https://www.baidu.com")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer resp.Body.Close()
    fmt.Printf("%#v\n", resp.Status)           // string, "200 OK"
    fmt.Printf("%#v\n", resp.StatusCode)       // int, 200
    fmt.Printf("%#v\n", resp.Header)           // http.Header, map[string][]string
    fmt.Printf("%#v\n", resp.Request)          // *http.Request
    fmt.Printf("%#v\n", resp.ContentLength)    // int64
    fmt.Printf("%#v\n", resp.TransferEncoding) // []string(nil)
    fmt.Printf("%#v\n", resp.Trailer)          // http.Header(nil)
    fmt.Printf("%#v\n", resp.Uncompressed)     // bool
    fmt.Printf("%#v\n", resp.TLS)              // *tls.ConnectionState
    fmt.Printf("%#v\n", resp.Body)             // *http.bodyEOFSignal => io.ReadCloser => io.Reader

    body, err := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}

multipart

admin@victus:~$ cd /C/Program\ Files/Go/src/mime/multipart
nosch@victus:/C/Program Files/Go/src/mime/multipart$ grep -ER 'func.+\) [A-Z]\w+' .
./formdata.go:func (r *Reader) ReadForm(maxMemory int64) (*Form, error) {
./formdata.go:func (f *Form) RemoveAll() error {
./formdata.go:func (fh *FileHeader) Open() (File, error) {
./formdata.go:func (rc sectionReadCloser) Close() error {
./formdata_test.go:func testFile(t *testing.T, fh *FileHeader, efn, econtent string) File {
./formdata_test.go:func (r *failOnReadAfterErrorReader) Read(p []byte) (n int, err error) {
./multipart.go:func (p *Part) FormName() string {
./multipart.go:func (p *Part) FileName() string {
./multipart.go:func (r *stickyErrorReader) Read(p []byte) (n int, _ error) {
./multipart.go:func (p *Part) Read(d []byte) (n int, err error) {
./multipart.go:func (pr partReader) Read(d []byte) (int, error) {
./multipart.go:func (p *Part) Close() error {
./multipart.go:func (r *Reader) NextPart() (*Part, error) {
./multipart.go:func (r *Reader) NextRawPart() (*Part, error) {
./multipart_test.go:func (mr *maliciousReader) Read(b []byte) (n int, err error) {
./multipart_test.go:func (s *slowReader) Read(p []byte) (int, error) {
./multipart_test.go:func (s *sentinelReader) Read([]byte) (int, error) {
./writer.go:func (w *Writer) Boundary() string {
./writer.go:func (w *Writer) SetBoundary(boundary string) error {
./writer.go:func (w *Writer) FormDataContentType() string {
./writer.go:func (w *Writer) CreatePart(header textproto.MIMEHeader) (io.Writer, error) {
./writer.go:func (w *Writer) CreateFormFile(fieldname, filename string) (io.Writer, error) {
./writer.go:func (w *Writer) CreateFormField(fieldname string) (io.Writer, error) {
./writer.go:func (w *Writer) WriteField(fieldname, value string) error {
./writer.go:func (w *Writer) Close() error {
./writer.go:func (p *part) Write(d []byte) (n int, err error) {

第三方库

GitHub: http client stars:>1000

  1. go-resty/resty shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Simple HTTP and REST client library for Go
  2. parnurzeal/gorequest shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    GoRequest -- Simplified HTTP client ( inspired by nodejs SuperAgent )
  3. gojek/heimdall shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    An enhanced HTTP client for Go
  4. imroc/req shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Simplified Golang HTTP client library with Black Magic, Less Code and More Efficiency
  5. dghubble/sling shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    A Go HTTP client library for creating and sending API requests
  6. hashicorp/go-retryablehttp shields.io:github/stars shields.io:github/languages/code-size shields.io:github/commit-activity/w shields.io:github/license
    Retryable HTTP client in Go

简单的了解:

  1. resty 看起来确实不错,链式调用,清晰明了,而且有不错的调试信息。
  2. gorequest 是在原生库上做了一点简单的封装,优化调用体验。有篇中文文档可以参考:gorequest中文文档(非官方)
    需要学习一下他的设计。官方文档说是借鉴 Node.js 的 SuperAgent。
  3. sling 也挺有特色的,使 API 变得结构化,调用变得像普通的 Go 函数一样。
  4. go-retryablehttp 在原生库上加了一个自动重试机制。
  5. heimdall, req, 简单一看,还看不出来有什么特别的地方。

#68 Golang 观察

2022-01-18

InfoQ 的公众号文章《解读Go语言的2021:稳定为王》中引用 TIOBE Index 和 Google Trends 的数据来分析 Go 语言的发展趋势。
非常有意思,我找他的思路,直接去源头研究了一番。

Go 在 2009 年和 2016 年得了 TIOBE 年度语言。
PS: Go 是 2009 年 11 月发布。TIOBE 也是够捧场了,刚一出来就得了年度语言。

根据 TIOBE 的数据,Go 在 2016 年下半年大火了一波,随后热度消退,从 2018 年到现在一直比较稳定。

然后 Google Trends 的数据(2009/10/01 至今,全球)显示:

  • Go 在 2009 年诞生之后就慢慢攀升
  • 2012 年达到一个阶段
  • 2013 年 10 月又上一个台阶
  • 2014 年 5 月达到巅峰
  • 然后一直持续下跌到 2020 年底
  • 2021 年稍有复苏的迹象

最后再上这张图,看看 Go 在各个地区的火爆程度:

那篇文章,包括我之前在网上看到的其他文章,都在这个地方得出了一个结论:Go 在国内的热度远超国外,甚至有地方说 Go 语言的热度全靠国内开发者支撑。
我认为这是非常不严谨的。

其实 Google Trends 的数据是根据这个地方搜索相关词条的比例来计算的。而 Google 的中国用户已经被过滤过一道,应该是以开发者为主。所以这个热度数据的对比是不能说明什么问题的。而且由于一些上网方式的问题,国内的这部分统计数据应该是非常不可靠的。