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有对应说明)
相关文章:
以下是使用示例代码:
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 build
和 go 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 包到 GOPATH
的 src
文件夹下。
go install
命令默认将该包安装到第一个 GOPATH
的 bin
文件夹下,若想设置自定义安装路径使用环境变量 GOBIN
。
go module 依赖包会自动下载到 pkg/mod 子目录下面,只读。使用该命令删除:go clean -modcache
Go 语言编程规范(2020.12.9)
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.
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 日