《Go程序设计语言》阅读要点记录

第1章 入门

  1. 找出重复行
func main() {
	counts := make(map[string]int)
	input := bufio.NewScanner(os.Stdin)
	for input.Scan() {
		counts[input.Text()]++
	}

// 忽略了input.Err()中可能的错误
	for line, n := range counts {
		if n > 1 {
			println(line, n)
		}
	}
}

(1)bufio.NewScanner可以读取输入,以行或者单词为单位隔开,这是处理以行为单位的输入内容的最简单方式;
(2)每一次调用input.Scan()读取下一行,并且将结尾的换行符去掉;
(3)通过调用input.Text()来获取读到的内容;
(4)input.Scan()在读到新行的时候返回true,在没有更多内容的时候返回false;

  1. printf的部分格式
%x, %o, %b      十六进制、八进制、二进制整数
%v                  内置格式的任何值
%T                  任何值的类型
  1. 读取文件
data, err := ioutil.ReadFile(filename)
... ...
for _, line := range strings.Split(string(data), "\n") {
    counts[line]++
}
  1. 产生随机数
rand.Seed(time.Now().UTC().UnixNano())
... ...
freq := rand.Float64()
  1. 系统异常退出 os.Exit(1)
  2. 获取url的内容
resp, err := http.Get(url)
... ...
b, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
  1. 计算消耗的时间 time.Since(start).Seconds()
  2. 一个Web服务器
package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", handler)

	http.ListenAndServe(":8080", nil)
}

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, %s", r.URL.Path)
}

r中包含很多信息,比如r.Method,r.URL,r.Proto,r.Host,r.Form等。

第2章 程序结构

第8章 goroutine和通道

Go有两种并发编程的风格

(1)CSP并发模式:goroutine+channel(如channel)
(2)共享内存多线程的传统模型(如sync.Mutex)
  1. 当一个程序启动时,只有一个goroutine来调用main函数,称之为主goroutine。
  2. main函数返回时,所有的goroutine都暴力的直接终结。
  3. 没有程序化的方法让一个goroutine来停止另一个,但是有办法和goroutine通信来要求它自己停止。
  4. channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。
  5. channel是一个使用make创建的数据结构的引用,当复制或作为参数传递到一个函数时,复制的是引用,这样调用者和被调用者都引用同一份数据结构。
  6. 和其他引用一样,channel的零值是nil。
  7. 同种类型的channel可以使用==进行比较,channel也可以和nil进行比较。

发送、接收、关闭

  1. 通道主要有发送接收 两个操作,两者统称为通信。
(1)发送  ch <- data
(2)接收  data = <- ch  或 <- ch (接收的结果不使用而直接丢弃也是允许的)
  1. channel的第3种操作:关闭。
(1)在已经关闭的channel上进行发送操作将导致宕机
(2)在已经关闭的channel上进行接收,将获取所有已经发送的值,直到channel为空
(3)当关闭的channel被读完后,所有后续的接收操作都仍可进行,只是会取到通道元素类型对应的零值
(4)可以使用ok语法来判断channel是否已关闭
data, ok := <- ch
if !ok {
    // 说明channel已关闭且读完
}
(5)为了对(4)中的写法进行简化,可以用range语法循环对channel进行读取,range会不断从channel 接收值,直到它被关闭(close)。
(6)主动关闭channel不是一个必须的操作,channel是可以自动回收的。只有在需要通知接收方goroutine所有的数据都发送完毕的时候,发送方才需要主动关闭channel。
(7)GC是根据channel是否可以访问来决定是否回收它的,而不是根据它是否关闭。
(8)关闭一个已经关闭的channel会导致宕机,关闭一个空channel(nil)也会导致宕机。
(9)关闭channel还可以作为一个广播机制。

无缓冲通道和缓冲通道

ch = make(chan int)  //无缓冲通道
ch = make(chan int, 0)  //无缓冲通道
ch = make(chan int, 3)  //容量为3的缓冲通道
  1. 无缓冲通道的发送和接收会强制同步化,又称为同步通道。
  2. 并发并不意味着事件一定同时发生,而是不能假设事件的先后顺序。
  3. 缓冲通道的缓冲区,可以将发送和接收goroutine进行解耦。
  4. 如果程序要知道通道的容量,可以使用内置的cap函数:

fmt.Println(cap(ch))

  1. 如果要获取当前通道内的元素个数,可以使用内置的len函数:

fmt.Println(len(ch))
因为在并发程序中这个信息会随着检索操作很快过时,所以它的价值很低,但是它在错误诊断和性能优化的时候很有用。

  1. 粗暴的将缓冲通道作为队列在单个goroutine中使用是个错误。如果没有另一个goroutine从通道进行接收,发送者(也许是整个程序)有被永久阻塞的风险。如果仅仅需要一个简单的队列,使用slice创建一个即可。
  2. 对于无缓冲的通道,如果因为没有goroutine接收时,会导致多个发送的goroutine被卡住,这叫做 ** goroutine泄露 **,这属于一个bug。不像回收变量,泄露的goroutine不会自动回收,所以确保goroutine在不再需要的时候可以自动结束。

单向通道

  1. 函数的channel形参有时会要求不能发送或不能接收,这样就可以避免在函数内部误用。
  2. chan <- int 是只能往里写入的int类型通道,<- chan int是一个只能从中读取的int类型通道。
  3. 函数内违反单向通道使用原则的错误,会在编译时被检查出来。
  4. 由于close操作只能在发送方操作,所以试图关闭一个只能接收的单向通道会引起编译时报错。
  5. 在任何赋值操作中将双向通道转换为单向通道都是允许的,但是反过来是不行的。

并行循环

func test() {
    var wg sync.WaitGroup
    for f := range filenames {
        wg.Add(1)

        //worker
        go func(f string) {
            defer wg.Done()
            // do something
        }(f)
    }

    wg.Wait()
    // do other
}

#第9章 使用共享变量实现并发