Go语言知识点记录

Go 语言知识点记录

sync包

sync 是 Golang 标准库中的同步包,Once 结构中的 Do 方法能够保证传入的参数 f 只执行一次,并且在 Do 返回时 f 已经执行完成。它的源码如下:

// Once is an object that will perform exactly one action.
type Once struct {
    m    Mutex
    done uint32
}

// Do calls the function f if and only if Do is being called for the
// first time for this instance of Once. In other words, given
//     var once Once
// if once.Do(f) is called multiple times, only the first call will invoke f,
// even if f has a different value in each invocation. A new instance of
// Once is required for each function to execute.
//
// Do is intended for initialization that must be run exactly once. Since f
// is niladic, it may be necessary to use a function literal to capture the
// arguments to a function to be invoked by Do:
//     config.once.Do(func() { config.init(filename) })
//
// Because no call to Do returns until the one call to f returns, if f causes
// Do to be called, it will deadlock.
//
// If f panics, Do considers it to have returned; future calls of Do return
// without calling f.
//
func (o *Once) Do(f func()) {
    if atomic.LoadUint32(&o.done) == 1 {
        return
    }
    // Slow-path.
    o.m.Lock()
    defer o.m.Unlock()
    if o.done == 0 {
        defer atomic.StoreUint32(&o.done, 1)
        f()
    }
}

它包含了一个 m 互斥锁和一个 done 原子变量,这样做的目的是为了保证在 Do 返回时,f 已经调用完成。(版本 1.15.8有对应说明)

相关文章:

atomic vs non-atomic

以下是使用示例代码:

package main

import (
    "fmt"
    "sync"
)

var _config *Config

var once sync.Once

// Config .
type Config struct {
    name string
}

// NewConfig .
func NewConfig() *Config {
    return _config
}

func main() {
    once.Do(func() {
        _config = &Config{
            name: "test",
        }
        fmt.Println("init finish ")
    })
    config := NewConfig()
    fmt.Println(config)
}

sync.WaitGroup 使用来等待协程完成的同步结构,它在第一次被使用后不能被拷贝,也就是说不能传递该结构。

package main

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

func main() {
    urls := []string{
        "http://www.reddit.com/r/aww.json",
        "http://www.reddit.com/r/funny.json",
        "http://www.reddit.com/r/programming.json",
    }
    jsonResponses := make(chan string)

    var wg sync.WaitGroup

    wg.Add(len(urls))

    for _, url := range urls {
        go func(url string) {
            defer wg.Done()
            res, err := http.Get(url)
            if err != nil {
                log.Fatal(err)
            } else {
                defer res.Body.Close()
                body, err := ioutil.ReadAll(res.Body)
                if err != nil {
                    log.Fatal(err)
                } else {
                    jsonResponses <- string(body)
                }
            }
        }(url)
    }

    go func() {
        for response := range jsonResponses {
            fmt.Println(response)
        }
    }()

    wg.Wait()
}

Go 编译过程

Go 语言程序运行之前需要先编译成二进制,在编译过程会生成中间代码。它具有静态单赋值(Static Single Assignment)的特性。

抽象语法树(abstract syntax tree)是源代码语法的结构的一种抽象表示。编译器在执行完语法分析之后会输出一个抽象语法树,这个抽象语法树会辅助编译器进行语义分析。

如果一个中间代码具有静态单赋值的特性,那么每个变量就只会被赋值一次。静态单赋值主要是对代码进行优化。

指令集对应了不同的机器的架构。可以使用 uname -m 进行查看。

编译器分为前端和后端,编译器的前端一般承担着词法分析、语法分析、类型检查和中间代码生成几部分工作,而编译器后端主要负责目标代码的生成和优化,也就是将中间代码翻译成目标机器能够运行的二进制机器码。

go buildgo install 的区别在于 go install 命令会将包安装到 $GOPATH/pkg 目录中。 编译减小文件大小,可以加上参数 go build -ldflags "-s -w" ,可以减少可执行文件的大小。详情见此

Go 中的 strings

在 Go 中的字符串本质上是一个只读的字节数组,所以它没有容量.

Go 性能测试

执行命令 go test -v -bench=. -benchmem :

Benchmarkf1-4     10000        3703 ns/op        2433 B/op         28 allocs/op
Benchmarkf2-4     10000        4342 ns/op        2288 B/op         26 allocs/op
  • -4 执行的函数 f1 和 f2 执行的次数.
  • 10000 执行迭代的次数 for i := 0; i < b.N; i++ {.
  • XXX ns/op 完成每次迭代需要的时间.

Go 包管理模式中注意项

go.mod 文件中第一行除以是包的导入路径外,它还关联了该包的下载地址。如使用 go get go install 命令下载。

go.mod 文件所在的子文件夹也会使用 module 模式。

go get 命令会下载 remote 包到 GOPATHsrc 文件夹下。

go install 命令默认将该包安装到第一个 GOPATHbin 文件夹下,若想设置自定义安装路径使用环境变量 GOBIN

go module 依赖包会自动下载到 pkg/mod 子目录下面,只读。使用该命令删除:go clean -modcache

参考:How to Write Go Code

Go 语言编程规范(2020.12.9)

CodeReviewComments

EffectiveGo

Go 如何优雅的关闭 http 服务器

代码示例(go 1.8之后):

func main(){
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
        time.Sleep(time.Second * 3)
        fmt.Fprint(rw, "hello world")
    })

    done := make(chan os.Signal, 1)
    signal.Notify(done, os.Interrupt, os.Kill)

    srv := http.Server{
        Addr:    ":10000",
        Handler: mux,
    }

    go func() {
        if err := srv.ListenAndServe(); err != nil {
            log.Printf("Server serve failed.  %v", err)
        }
    }()

    <-done

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    srv.Shutdown(ctx)
}

Go 读取 http.Request.Body 内容

if http.Request.Body == nil {
    return 
}
body, err := ioutil.ReadAll(http.Request.Body) // panic if http.Request.Body is nil.
if err != nil {
    log.Fatal(err)
}
http.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) // do not anything,http.Request.Body closed by request.

参考:golang-read-request-body

Go HTTP 单元测试


func TestVersion(t *testing.T){
    engine := gin.Default()

    inputs := []struct {
        method  string
        url     string
        body    io.Reader
        request func(method string, url string, body io.Reader) (*http.Request, error)

        rr   *httptest.ResponseRecorder
        want string
    }{
        {
            method:  "GET",
            url:     "/last/version",
            body:    nil,
            request: http.NewRequest,
            rr:      httptest.NewRecorder(),
            want:    `{"code":0,"message":"ok","data":"v1.2.9"}`,
        },
        {
            method:  "GET",
            url:     "/last/version",
            body:    nil,
            request: http.NewRequest,
            rr:      httptest.NewRecorder(),
            want:    `{"code":0,"message":"ok","data":"v1.2.9"}`,
        },
    }

    for _, input := range inputs {
        r, err := input.request(input.method, input.url, input.body)
        if err != nil {
            t.Errorf("http request failed. %v", err)
        }

        engine.ServeHTTP(input.rr, r)

        if input.rr.Code != http.StatusOK {
            t.Errorf("request: %s | %s | failed. %v", input.method, input.url, err)
        }

        if input.rr.Body.String() != input.want {
            t.Errorf("request: %s | %s | failed. got: %s; want: %v", input.method, input.url, input.rr.Body.String(), input.want)
        }
    }
}

关于

版本日志

  • 第一版:2020 年 08 月 27 日
  • 第二版:2021 年 02 月 20 日
  • 第三版:2021 年 03 月 29 日

 上一篇
TypeScript学习笔记 TypeScript学习笔记
TypeScript学习笔记简介TypeScript是微软推出的 JavaScript 的超集。 语法结构如下 // boolean let isDone: boolean = false // number let decimal:
2020-08-31
下一篇 
mongodb学习笔记 mongodb学习笔记
MongoDB学习笔记简介MongoDB 是一个基于文档的通用分布式部署的现代应用数据库。数据存储格式跟 JSON 格式类似,每个数据可以看成是一个对象并可以嵌套。 安装请参考MongoDB Install 或使用 docker 安装。 d
2020-08-27
  目录