Go in Action
Go in Action - Manning 2016
目录
介绍Go
- fmt.Println("Hello, world")
- ?这本书里似乎没提到Go异常处理?
Go快速入门
- log.SetOutput(os.Stdout) //import ("log" ...)
- package main //main必须在main包里才会生成可执行文件? 通常以所在目录命名?
- func init() { ... }
- func main() { ... }
- search.go
- var matchers = make(map[string]Matcher)
- go func(matcher Matcher, feed *Feed) { ... }(matcher, feed)
- defer file.Close() //跟初始化file, err := os.Open(dataFile)在同一个作用域,确实比try-finally或者using要简单一点
- func Match(matcher Matcher, feed *Feed, searchTerm string, results chan<- *Result) {
- 注意这里‘写chan’的参数声明
- 如果2个value struct里的*成员引用同一个对象?——会自动引用计数吗?
- XMLName xml.Name `xml:"item"` //还有这种类型?
打包、工具
- 远程导入:
- import "github.com/spf13/viper" //go build命令会搜索本地GOPATH(嗯?这会导致总是下载到最新的版本?有可能不兼容)
- 命名导入:
- import (myfmt "mylib/fmt")
- 匿名导入(sql驱动,略)
- filename := os.Args[1]
- contents, err := ioutil.ReadFile(filename) //读全部内容?没有迭代器吗
- go build .
- go vet //lint工具?
- go fmt //代码洁癖...
- godoc -http=:6060
- 依赖管理
- vendoring
- import "bitbucket.org/ww/goautoneg" 转换为
- "github.ardanstudios.com/myproject/Godeps/_workspace/src/bitbucket.org/ww/goautoneg"
- import "bitbucket.org/ww/goautoneg" 转换为
- gb
- import的问题是没有指定版本
- vendored code:$PROJECT/vendor/src/
- 与go get不兼容
- gb build all
- vendoring
数组、切片和maps
- 数组
- array := [5]int{10, 20, 30, 40, 50}
- array := [...]int{10, 20, 30, 40, 50} //自动推断数组长度,维度一旦初始化不可修改
- array := [5]int{1: 10, 2: 20} //选择下标初始化?
- array[2] = 35
- array := [5]*int{0: new(int), 1: new(int)} //指针数组
- *array[0] = 10 //其实Go语言里并不严格区分指针和引用?
- array1 = array2 //数组是value对象,赋值操作将导致内存copy
- array2d := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
- 传递数组的地址给函数:func foo(array *[1e6]int) { ... } //注意,这里的数组指针类型比C语言要直接多了
- slice:是对array的封装,内部提供了addr,length,capacity元数据
- slice := make([]string, 5)
- slice := make([]int, 3, 5)
- slice := []string{"Red", "Blue", "Green", "Yellow", "Pink"} //注意这里[]里既没有长度常量,也没有...自动推断
- newSlice := slice[1:3] //长度2,容量4?newSlice初始情况下引用slice?
- slice容量=5,newSlice的容量=5-1=4,注意,newSlice不可以访问slice[0]元素(这种设计很有意思!)
- 多个slice可能共享一块内存(那么修改需要同步吗)
- slice[index]只能访问length范围内的元素,要访问容量范围内的,需要append:newSlice = append(newSlice, 60)
- 当底下的实际存储不足新capacity时,append会分配新内存?有点realloc的味道
- append:length=1000一下时每次double,否则增加25%
- slice := source[2:3:4] //如果是start:length:capacity三元组(不是Python里面的那种语法)
- 迭代:for index, value := range slice { ... } //Go惯用法;想起了Python里的enumerate函数
- map
- dict := make(map[string]int)
- dict := map[string]string{"Red": "#da1337", "Orange": "#e95a22"} //注意,:=代表类型定义(自动推断)及初始化
- dict := map[[]string]int{} //编译错误:slice不能用作map key(必须可执行==操作),但可用作map value
- nil map问题:
- var colors map[string]string
- colors["Red"] = "#da1337" //运行时错误
- 可以这样:value, exists := colors["Blue"] //又是Go惯用法
- value := colors["Blue"] //这样是可以的,如果key不存在,value返回类型的零值
- delete(colors, "Coral") //删除元素,又是内置函数,这一点不同于C++语言的成员函数风格
- map在函数之间传递时不会copy
Go的类型系统
- var总是初始化变量为零值
- := 同时声明及初始化
- type typeName struct { fields... }
- 然后就可以func (a typeName) methodName(args) retType { ... }扩展方法了
- 也可以写成func (a *typeName) ...,但是调用方法时全部是.号,但是指针类型的变量必须是&struct{...}初始化来的
- a typeName的形参代表方法操作的是type的value类型,也就是C++里面的const方法?
- value类型的变量可以调用用指针类型定义的方法(这些地方太容易让人犯迷糊了),将会被转换为(&obj).method()
- 然后就可以func (a typeName) methodName(args) retType { ... }扩展方法了
- 引用类型:slice、map、channel、interface、func
- `header` value, contains a pointer
- Interfaces
- `method sets`(C++里的vtable?)
- 接口里的方法指针是不是运行时动态填充的???
- 接口实现的接收者通常是*T,而不是value类型的T?
- 如果你以value T类型作为接受者来实现接口,那么接口作为参数时可以接受T和*T(?)
- 类型嵌入
- 有点Ruby/Scala mixin的风格~
- inner type promotion //只要名字不冲突?
- Exporting and unexporting identifiers
- 取决于名字的第1个字母是大写还是小写。。。
- 感觉本章里作者说的废话太多了,其实简单一句话就可以说清楚了!
并发
- logical processors?
- 当执行blocking IO操作时,detach;当IO ready时,重新attach?
- 第1个例子:
- runtime.GOMAXPROCS(1) //或runtime.NumCPU()
- var wg sync.WaitGroup //wg的用法是不是可以直接看文档和sample代码即时掌握?
- wg.Add(2)
- go func() {
- defer wg.Done()
- }()
- wg.Wait()
- scheduler可在goroutines执行时自动打断?(swap out?抢占式调度?)这个内部操作发生在fmt.Printf调用的时候吗
- 竞争条件
- 读写共享变量必须是atomic的;
- 检测工具:go build -race
- 方法1:atomic.AddInt64(&counter, 1) #奇怪,atomic包是怎么做到对一个int64变量的修改是原子的?利用平台特定的汇编指令?
- 方法2:mutex sync.Mutex => mutex.Lock() { ... } mutex.Unlock()
- 更灵活地,Channels
- 初始化:
- unbuffered := make(chan int) #如果有一个没准备好,第一个操作(发送/接受)wait
- buffered := make(chan string, 10)
- close操作:仍然可以接受,但不可以再发送(任务派发端)
- 发送数据:buffered <- "Gopher"
- 接受数据:value := <-buffered
- 初始化:
- rand.Seed(time.Now().UnixNano())
并发模式
- Runner:监控运行时间及超时终止
- type Runner struct {
- interrupt chan os.Signal
- complete chan error
- timeout <-chan time.Time
- tasks []func(int)
- var ErrTimeout = errors.New("received timeout") //预创建的错误对象
- func (r *Runner) Add(tasks ...func(int)) { r.tasks = append(r.tasks, tasks...) } //注意这里的变参;
- func (r *Runner) Start() error { //运行所有tasks,并监控超时错误
- signal.Notify(r.interrupt, os.Interrupt)
- go func() { r.complete <- r.run() }()
- select {
- case err := <-r.complete: return err
- case <-r.timeout: return ErrTimeout
- func (r *Runner) run() error { ... } //依次执行每个task,略
- func (r *Runner) gotInterrupt() bool {
- select {
- case <-r.interrupt: signal.Stop(r.interrupt) return true //注意这里检查有没有接受到操作系统中断
- default: return false //default分支使得前面的<-r.interrupt操作不会阻塞(?有可能丢失中断吗)
- 注意,初始化:interrupt: make(chan os.Signal, 1), timeout: time.After(duration), //把OS中断和超时建模为特殊的chan
- select {
- type Runner struct {
- Pooling
- type Pool struct {
- m sync.Mutex //这个用来保护什么?chan本身不是自动同步的嘛(保护对closed变量的赋值???靠)
- resources chan io.Closer
- factory func() (io.Closer, error) //这个命名真恶心,应该改成allocNewResource
- closed bool
- func (p *Pool) Acquire() (io.Closer, error) {
- select {
- case r, ok := <-p.resources: ...
- default: return p.factory()
- select {
- func (p *Pool) Release(r io.Closer) {
- p.m.Lock()
- defer p.m.Unlock()
- ...
- select { case p.resources <- r: ... //default:pool已满,直接释放资源
- 循环内启动goroutine防止共享同一个index/counter变量:
- go func(a int){ ... }(i)
- type Pool struct {
- Work
- 命名有点问题,Work()似乎应该是ExecuteWork()?
标准库
- http://sourcegraph.com/code.google.com/p/go/.GoPackage/io/.def/Writer 靠,无法访问
- Logging
- log.SetPrefix("TRACE: ")
- log.SetFlags(log.Ldate | log.Lmicroseconds | log.Llongfile)
- log.Println/Fatalln/Panicln("message")
- const {
- Ldate = 1 << iota
- 定制logger:
- var ( Trace *log.Logger ...... )
- Trace = log.New(ioutil.Discard, "TRACE: ", log.Ldate|log.Ltime|log.Lshortfile) # var Discard io.Writer = devNull(0)
- Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin") //?
- io.MultiWriter(file, os.Stderr) //?
- Encoding/Decoding
- gResult struct {
- GsearchResultClass string `json:"GsearchResultClass"` //struct内的成员需要特殊的json映射声明(tags元数据);
- ...
- gResponse struct {
- ResponseData struct {
- Results []gResult `json:"results"`
- } `json:"responseData"`
- ResponseData struct {
- 解码:
- var gr gResponse
- err = json.NewDecoder(resp.Body).Decode(&gr)
- unmarshal:
- err := json.Unmarshal([]byte(JSON), &c) //?
- 更灵活的解析:?
- var c map[string]interface{}
- err := json.Unmarshal([]byte(JSON), &c) //这种情况下感觉代码中interface{}会被滥用?
- 编码
- c := make(map[string]interface{})
- ...
- data, err := json.MarshalIndent(c, "", " ")
- gResult struct {
- Input and output
- 好像没什么特别值得一说的(又一套API命名风格)
测试与性能基准
- listing01_test.go
- func TestDownload(t *testing.T) {
- t.Log("Given the need to test downloading content.") #t.Errorf代表测试失败?
- ...
- resp, err := http.Get(url)
- defer resp.Body.Close()
- func TestDownload(t *testing.T) {
- func SendJSON(rw http.ResponseWriter, r *http.Request) {
- u = ...
- rw.Header().Set("Content-Type", "application/json")
- rw.WriteHeader(200)
- json.NewEncoder(rw).Encode(&u)
- ?
- r, _ := http.NewRequest("GET", "/sendjson", nil)
- rw := httptest.NewRecorder()
- http.DefaultServeMux.ServeHTTP(rw, r)
- 性能基准测试
- func BenchmarkSprintf(b *testing.B) {
- b.ResetTimer()
- go test -v -run="none" -bench="BenchmarkSprintf" //运行次数?
- func BenchmarkSprintf(b *testing.B) {
有疑问加站长微信联系(非本文作者)