《Go程序设计语言》阅读要点记录
第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;
- printf的部分格式
%x, %o, %b 十六进制、八进制、二进制整数
%v 内置格式的任何值
%T 任何值的类型
- 读取文件
data, err := ioutil.ReadFile(filename)
... ...
for _, line := range strings.Split(string(data), "\n") {
counts[line]++
}
- 产生随机数
rand.Seed(time.Now().UTC().UnixNano())
... ...
freq := rand.Float64()
- 系统异常退出 os.Exit(1)
- 获取url的内容
resp, err := http.Get(url)
... ...
b, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
- 计算消耗的时间 time.Since(start).Seconds()
- 一个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)
- 当一个程序启动时,只有一个goroutine来调用main函数,称之为主goroutine。
- main函数返回时,所有的goroutine都暴力的直接终结。
- 没有程序化的方法让一个goroutine来停止另一个,但是有办法和goroutine通信来要求它自己停止。
- channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。
- channel是一个使用make创建的数据结构的引用,当复制或作为参数传递到一个函数时,复制的是引用,这样调用者和被调用者都引用同一份数据结构。
- 和其他引用一样,channel的零值是nil。
- 同种类型的channel可以使用==进行比较,channel也可以和nil进行比较。
发送、接收、关闭
- 通道主要有发送 和 接收 两个操作,两者统称为通信。
(1)发送 ch <- data
(2)接收 data = <- ch 或 <- ch (接收的结果不使用而直接丢弃也是允许的)
- 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的缓冲通道
- 无缓冲通道的发送和接收会强制同步化,又称为同步通道。
- 并发并不意味着事件一定同时发生,而是不能假设事件的先后顺序。
- 缓冲通道的缓冲区,可以将发送和接收goroutine进行解耦。
- 如果程序要知道通道的容量,可以使用内置的cap函数:
fmt.Println(cap(ch))
- 如果要获取当前通道内的元素个数,可以使用内置的len函数:
fmt.Println(len(ch))
因为在并发程序中这个信息会随着检索操作很快过时,所以它的价值很低,但是它在错误诊断和性能优化的时候很有用。
- 粗暴的将缓冲通道作为队列在单个goroutine中使用是个错误。如果没有另一个goroutine从通道进行接收,发送者(也许是整个程序)有被永久阻塞的风险。如果仅仅需要一个简单的队列,使用slice创建一个即可。
- 对于无缓冲的通道,如果因为没有goroutine接收时,会导致多个发送的goroutine被卡住,这叫做 ** goroutine泄露 **,这属于一个bug。不像回收变量,泄露的goroutine不会自动回收,所以确保goroutine在不再需要的时候可以自动结束。
单向通道
- 函数的channel形参有时会要求不能发送或不能接收,这样就可以避免在函数内部误用。
- chan <- int 是只能往里写入的int类型通道,<- chan int是一个只能从中读取的int类型通道。
- 函数内违反单向通道使用原则的错误,会在编译时被检查出来。
- 由于close操作只能在发送方操作,所以试图关闭一个只能接收的单向通道会引起编译时报错。
- 在任何赋值操作中将双向通道转换为单向通道都是允许的,但是反过来是不行的。
并行循环
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章 使用共享变量实现并发