the way to go advaced

第12章 读写数据

12.1 读取用户的输入

  • fmt.Scanln扫描来自标准输入的文本,空格分隔,直到换行为止
  • fmt.Scanf第一个参数用来格式化字符串
  • fmt.Sscan从字符串读取
  • bufio.Reader缓冲读,读到指定字符输出一次
1
2
3
inputReader := bufio.NewReader(os.Stdin)
fmt.Println("Please enter your name:")
input, err := inputReader.ReadString('\n')

12.2 文件读写

12.2.1 读文件

  • go语言中os.File类型的指针叫做文件句柄
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main() {
inputFile, inputError := os.Open("input.dat")
if inputError != nil {
fmt.Printf("An error occurred on opening the inputfile\n Does the file exist?\n Have you got acces to it?\n")
return
}
defer inputFile.Close()
inputReader := bufio.NewReader(inputFile)
for {
inputString, readerError := inputReader.ReadString('\n')
if readerError == io.EOF {
return
}
fmt.Printf("The input was: %s", inputString)
}
}
  • 将整个文件的内容读到一个字符串里边去 - ioutil.ReadFile(),第一个返回值是[]byte,第二个是error
  • 带缓冲的读取 - bufio.Reader.Read()
  • 按列读取文件中的数据,空格分隔,fmt.Fscanln(file, str1 str2 str3)

12.2.2 compress包:读取压缩文件

  • compress包支持的压缩文件格式为bzip2,flate,gzip,lzw,zlib
1
fz, err := gzip.NewReader(fi)

12.2.3 写文件

1
2
3
4
5
6
7
8
9
10
11
outputFile, outputError := os.OpenFile("output.dat", os.O_WRONLY|os.O_CREATE, 0666)
if outputError != nil {
return
}
defer outputFile.Close()
outputWriter := bufio.NewWriter(outputFile)
outputString := "Hello hello hello"
for i := 0; i < 10; i++ {
outputWriter.WriteString(outputString)
}
outputWrite.Flush()
  • os.O_RDONLY
  • os.O_WRONLY
  • os.O_CREATE:不存在创建
  • os.O_TRUNC:存在截断长度为0,类似清空
  • 使用os.Stdout.WriteString(“string”) 可以输出到屏幕

12.3 文件拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main() {
CopyFile("target.txt", "source.txt")
}
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
panic()
}
defer src.Close()
dst, err := os.Create(dstName)
if err != nil {
panic()
}
defer dst.Close()
return io.Copy(dst, src)
}

12.4 从命令行读取参数

12.4.1 os包

  • os包中的os.Args来处理命令行参数,在程序启动后读取命令行输入的参数
1
2
3
4
5
6
7
func main() {
who := "Alice"
if len(os.Args) > 1 {
who += strings.Join(os.Args[1:], " ")
}
fmt.Println("hello", who)
}

12.4.2 flag包

  • flag包可以用来替换变量
  • flag.Parse()扫描参数列表或者常量列表
  • flag.Arg(0) 第一个参数

12.5 buffer读取文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func cat(r *bufio.Reader) {
for {
buf, err := r.ReadBytes('\n')
if err == io.EOF {
break
}
fmt.Fprintf(os.Stdout, "%s", buf)
}
}
func main() {
flag.Parse()
if flag.NArgs() == 0 {
cat(bufio.NewReader(os.Stdin))
}
for i := 0; i < flag.NArg(); i++ {
f, err := os.Open(flag.Arg(i))
if err != nil {
continue
}
cat(bufio.NewReader(f))
}
}

12.6 用切片读写文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func cat(f *os.File) {
const NBUF = 512
var buf [NBUF]byte
for {
switch nr, err := f.Read(buf[:]); true {
case nr < 0:
fmt.Fprintf(os.Stderr, "cat: error reading: %s\n", err.Error())
os.Exit(1)
case nr == 0: // EOF
return
case nr > 0:
if nw, ew := os.Stdout.Write(buf[0:nr]); nw != nr {
fmt.Fprintf(os.Stderr, "cat: error writing: %s\n", ew.Error())
}
}
}
}

12.7 defer关闭文件

函数退出前会执行文件关闭

1
2
3
4
5
6
func data(name string) string {
f, _ := os.Open(name, os.O_RDONLY, 0)
defer f.Close()
contents, _ := ioutil.ReadAll(f)
return string(contents)
}

12.8 fmt.Fprintf

接口概念阐述例子:

1
2
3
4
5
6
7
8
9
func main() {
// unbuffered
fmt.Fprintf(os.Stdout, "%s\n", "hello world! - unbuffered")
// buffered: os.Stdout implements io.Writer
buf := bufio.NewWriter(os.Stdout)
// and now so does buf.
fmt.Fprintf(buf, "%s\n", "hello world! - buffered")
buf.Flush()
}
  • 函数签名:func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)

  • 第一个参数必须实现io.Writer接口

12.9 json数据格式

  • 编码格式JSON,XML,gob等

  • 术语

    • 数据结构->指定格式=序列化或者编码-传输之前
    • 指定格式->数据结构=反序列化或者解码-传输之后
  • 序列化是将内存中的数据结构转换成指定格式

  • json.Marshal() - 编码,web中使用json.MarshalforHTML()

  • JSON和go类型对应:

    • bool-boolean
    • float64-number
    • string-string
    • nil-null
  • 编码要求:

    • JSON 对象只支持字符串类型的 key;要编码一个 Go map 类型,map 必须是 map[string]T(T是 json 包中支持的任何类型)
    • Channel,复杂类型和函数类型不能被编码
    • 不支持循环数据结构;它将引起序列化进入一个无限循环
    • 指针可以被编码,实际上是对指针指向的值进行编码(或者指针是 nil)
  • 反序列化

    • json.Unmarshal(json, &v)
  • 编码和解码流

1
2
func NewDecoder(r io.Reader) *Decoder
func NewEncoder(r io.Writer) *Encoder
  • json到文件 encode
  • 文件到json decode

12.10 XML数据格式

  • encoding/xml包含了xml解析器来解析xml内容
  • marshal(),unMarshal()完成数据结构和xml内容的转换
  • xml中的标签
    • StartElement
    • Chardata
    • EndElement
    • Comment
    • Directive
    • ProcInst
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func main() {
input := "<Person><FirstName>Laura</FirstName><LastName>Lynn</LastName></Person>"
inputReader := strings.NewReader(input)
p := xml.NewDecoder(inputReader)
for t, err = p.Token(); err == nil; t, err = p.Token() {
switch token := t.(type) {
case xml.StartElement:
name := token.Name.Local
fmt.Printf("Token name: %s\n", name)
for _, attr := range token.Attr {
attrName := attr.Name.Local
attrValue := attr.Value
fmt.Printf("An attribute is: %s %s\n", attrName, attrValue)
// ...
}
case xml.EndElement:
fmt.Println("End of token")
case xml.CharData:
content := string([]byte(token))
fmt.Printf("This is the content: %v\n", content)
// ...
default:
// ...
}
}
}

Gob传输数据

  • Gob是go自己的以二进制形式序列化和反序列化程序数据的格式
  • Gob通常用于远程调用参数和结果的传输,机器和程序之间的数据传输
  • Gob只用于go语言中
  • Gob 文件或流是完全自描述的:里面包含的所有类型都有一个对应的描述,并且总是可以用 Go 解码,而不需要了解文件的内容。
  • 可导出字段会被编码,零值会被忽略
  • Gob 使用通用的 io.Writer 接口,通过 NewEncoder() 函数创建 Encoder 对象并调用 Encode();相反的过程使用通用的 io.Reader 接口,通过 NewDecoder() 函数创建 Decoder 对象并调用 Decode()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type P struct {
X, Y, Z int
Name string
}
type Q struct {
X, Y *int32
Name string
}
func main() {
// Initialize the encoder and decoder. Normally enc and dec would be
// bound to network connections and the encoder and decoder would
// run in different processes.
var network bytes.Buffer // Stand-in for a network connection
enc := gob.NewEncoder(&network) // Will write to network.
dec := gob.NewDecoder(&network) // Will read from network.
// Encode (send) the value.
err := enc.Encode(P{3, 4, 5, "Pythagoras"})
if err != nil {
log.Fatal("encode error:", err)
}
// Decode (receive) the value.
var q Q
err = dec.Decode(&q)
if err != nil {
log.Fatal("decode error:", err)
}
fmt.Printf("%q: {%d,%d}\n", q.Name, *q.X, *q.Y)
}
  • 编码到文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
type Address struct {
Type string
City string
Country string
}
type VCard struct {
FirstName string
LastName string
Addresses []*Address
Remark string
}
var content string
func main() {
pa := &Address{"private", "Aartselaar","Belgium"}
wa := &Address{"work", "Boom", "Belgium"}
vc := VCard{"Jan", "Kersschot", []*Address{pa,wa}, "none"}
// fmt.Printf("%v: \n", vc) // {Jan Kersschot [0x126d2b80 0x126d2be0] none}:
// using an encoder:
file, _ := os.OpenFile("vcard.gob", os.O_CREATE|os.O_WRONLY, 0666)
defer file.Close()
enc := gob.NewEncoder(file)
err := enc.Encode(vc)
if err != nil {
log.Println("Error in encoding gob")
}
}

12.12 go中的密码学

  • hash 包:实现了 adler32、crc32、crc64 和 fnv 校验;
  • crypto 包:实现了其它的 hash 算法,比如 md4、md5、sha1 等。以及完整地实现了 aes、blowfish、rc4、rsa、xtea 等加密算法。
  • sha1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main() {
hasher := sha1.New()
io.WriteString(hasher, "test")
b := []byte{}
fmt.Printf("Result: %x\n", hasher.Sum(b))
fmt.Printf("Result: %d\n", hasher.Sum(b))
//
hasher.Reset()
data := []byte("We shall overcome!")
n, err := hasher.Write(data)
if n!=len(data) || err!=nil {
log.Printf("Hash write error: %v / %v", n, err)
}
checksum := hasher.Sum(b)
fmt.Printf("Result: %x\n", checksum)
}

第13章 错误处理与测试

  • go没有try-catch-finally机制
  • go有defer-panic-and-recover机制
  • 永远不要忽略错误,否则可能会导致程序崩溃!!
  • 普通错误通过返回值来返回
  • 真正的异常用panic和recover来处理
  • 产生错误的函数会返回两个变量,一个值和一个错误码;如果后者是 nil 就是成功,非 nil 就是发生了错误。
  • 为了防止发生错误时正在执行的函数(如果有必要的话甚至会是整个程序)被中止,在调用函数后必须检查错误。

13.1 错误处理

  • go中的error接口
1
2
3
type error interface {
Error() string
}

13.1.1 定义错误

  • 新建:err := errors.New("error string")
  • 自定义error
1
2
3
4
5
6
7
8
9
// PathError records an error and the operation and file path that caused it.
type PathError struct {
Op string // "open", "unlink", etc.
Path string // The associated file.
Err error // Returned by the system call.
}
func (e *PathError) String() string {
return e.Op + " " + e.Path + ": "+ e.Err.Error()
}
  • 错误类型以Error结尾,错误变量以err或者Err开头

13.1.2 用fmt创建错误对象

1
fmt.Errorf("math: square root of negative number %g", f)

13.2 运行时异常和panic

  • 运行时异常:数组越界,断言失败等会触发panic
1
2
3
4
5
6
7
8
9
10
11
12
13
Starting the program
panic: A severe error occurred: stopping the program!
panic PC=0x4f3038
runtime.panic+0x99 /go/src/pkg/runtime/proc.c:1032
runtime.panic(0x442938, 0x4f08e8)
main.main+0xa5 E:/Go/GoBoek/code examples/chapter 13/panic.go:8
main.main()
runtime.mainstart+0xf 386/asm.s:84
runtime.mainstart()
runtime.goexit /go/src/pkg/runtime/proc.c:148
runtime.goexit()
---- Error run E:/Go/GoBoek/code examples/chapter 13/panic.exe with code Crashed
---- Program exited with code -1073741783
  • Go panicking:在多层嵌套的函数调用中调用 panic,可以马上中止当前函数的执行,所有的 defer 语句都会保证执行并把控制权交还给接收到 panic 的函数调用者。这样向上冒泡直到最顶层,并执行(每层的) defer,在栈顶处程序崩溃,并在命令行中用传给 panic 的值报告错误情况:这个终止过程就是 panicking

13.3 recover

  • recover用于从panic中恢复,停止终止过程,恢复正常执行
  • recover中能在defer修饰的函数中使用,用来取得panic调用中传递过来的错误值,如果是正常执行,调用recover会返回nil,不会生效
  • panic 会导致栈被展开直到 defer 修饰的 recover() 被调用或者程序中止。
1
2
3
4
5
6
7
8
9
10
11
func protect(g func()) {
defer func() {
log.Println("done")
// Println executes normally even if there is a panic
if err := recover(); err != nil {
log.Printf("run time panic: %v", err)
}
}()
log.Println("start")
g() // possible runtime-error
}
  • recover和catch很像
  • defer-panic-recover 在某种意义上也是一种像 if,for 这样的控制流机制

13.4 自定义包中的错误处理和panicking

  • 最佳实践
    • 在包的内部,总是应该从panic中recover,不允许显式的超出包范围的panic()
    • 向包的调用者返回错误值,而不是panic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// parse.go
package parse
import (
"fmt"
"strings"
"strconv"
)
// A ParseError indicates an error in converting a word into an integer.
type ParseError struct {
Index int // The index into the space-separated list of words.
Word string // The word that generated the parse error.
Err error // The raw error that precipitated this error, if any.
}
// String returns a human-readable error message.
func (e *ParseError) String() string {
return fmt.Sprintf("pkg parse: error parsing %q as int", e.Word)
}
// Parse parses the space-separated words in in put as integers.
func Parse(input string) (numbers []int, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("pkg: %v", r)
}
}
}()
fields := strings.Fields(input)
numbers = fields2numbers(fields)
return
}
func fields2numbers(fields []string) (numbers []int) {
if len(fields) == 0 {
panic("no words to parse")
}
for idx, field := range fields {
num, err := strconv.Atoi(field)
if err != nil {
panic(&ParseError{idx, field, err})
}
numbers = append(numbers, num)
}
return
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// panic_package.go
package main
import (
"fmt"
"./parse/parse"
)
func main() {
var examples = []string{
"1 2 3 4 5",
"100 50 25 12.5 6.25",
"2 + 2 = 4",
"1st class",
"",
}
for _, ex := range examples {
fmt.Printf("Parsing %q:\n ", ex)
nums, err := parse.Parse(ex)
if err != nil {
fmt.Println(err) // here String() method from ParseError is used
continue
}
fmt.Println(nums)
}
}

13.5 一种用闭包处理错误的模式

  • 每当函数返回时,我们检查是否有错误发生,会很繁琐
  • defer/panic/recover和闭包可以优雅的解决这个问题,但是要求所有函数都是同一种签名才可以
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
func main() {
f()
fmt.Println("Returned normally from f.")
}
func f() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
}
}()
fmt.Println("Calling g.")
g(0)
fmt.Println("Returned normally from g.")
}
func g(i int) {
if i > 3 {
fmt.Println("Panicking!")
panic(fmt.Sprintf("%v", i))
}
defer fmt.Println("Defer in g", i)
fmt.Println("Printing in g", i)
g(i + 1)
}
/*
Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.
*/

13.6 启动外部命令和程序

  • os包又一个StartProcess函数可以调用或者启动外部系统命令和二进制可执行文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// exec.go
package main
import (
"fmt"
"os/exec"
"os"
)
func main() {
// 1) os.StartProcess //
/*********************/
/* Linux: */
env := os.Environ()
procAttr := &os.ProcAttr{
Env: env,
Files: []*os.File{
os.Stdin,
os.Stdout,
os.Stderr,
},
}
// 1st example: list files
pid, err := os.StartProcess("/bin/ls", []string{"ls", "-l"}, procAttr)
if err != nil {
fmt.Printf("Error %v starting process!", err) //
os.Exit(1)
}
fmt.Printf("The process id is %v", pid)
// 2nd example: show all processes
pid, err = os.StartProcess("/bin/ps", []string{"-e", "-opid,ppid,comm"}, procAttr)
if err != nil {
fmt.Printf("Error %v starting process!", err) //
os.Exit(1)
}
fmt.Printf("The process id is %v", pid)
/* Output 1st:
The process id is &{2054 0}total 2056
-rwxr-xr-x 1 ivo ivo 1157555 2011-07-04 16:48 Mieken_exec
-rw-r--r-- 1 ivo ivo 2124 2011-07-04 16:48 Mieken_exec.go
-rw-r--r-- 1 ivo ivo 18528 2011-07-04 16:48 Mieken_exec_go_.6
-rwxr-xr-x 1 ivo ivo 913920 2011-06-03 16:13 panic.exe
-rw-r--r-- 1 ivo ivo 180 2011-04-11 20:39 panic.go
*/
// 2) exec.Run //
/***************/
// Linux: OK, but not for ls ?
// cmd := exec.Command("ls", "-l") // no error, but doesn't show anything ?
// cmd := exec.Command("ls") // no error, but doesn't show anything ?
cmd := exec.Command("gedit") // this opens a gedit-window
err = cmd.Run()
if err != nil {
fmt.Printf("Error %v executing command!", err)
os.Exit(1)
}
fmt.Printf("The command is %v", cmd)
// The command is &{/bin/ls [ls -l] [] <nil> <nil> <nil> 0xf840000210 <nil> true [0xf84000ea50 0xf84000e9f0 0xf84000e9c0] [0xf84000ea50 0xf84000e9f0 0xf84000e9c0] [] [] 0xf8400128c0}
}
// in Windows: uitvoering: Error fork/exec /bin/ls: The system cannot find the path specified. starting process!

13.7 go中的单元测试和基准测试

  • testing包是用来进行自动化测试,日志和错误报告的
  • 单元测试文件名为*_test.go,测试文件不会被普通的go编译器编译,不会被部署,只会被gotest编译
  • func TestAbcde(t *testing.T)
  • 测试失败
    • Fail 失败继续执行
    • FailNow失败终止执行,继续执行下一个文件
    • Log 打印到错误日志
    • Fatal Log+FailNow
  • 基准测试函数 BenchmarkReverse()go test -test.bench=.*来执行基准函数

13.8 测试实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package even
import "testing"
func TestEven(t *testing.T) {
if !Even(10) {
t.Log(" 10 must be even!")
t.Fail()
}
if Even(7) {
t.Log(" 7 is not even!")
t.Fail()
}
}
func TestOdd(t *testing.T) {
if !Odd(11) {
t.Log(" 11 must be odd!")
t.Fail()
}
if Odd(10) {
t.Log(" 10 is not odd!")
t.Fail()
}
}
  • 测试用例需要覆盖
    • 正常的用例
    • 反面的用例
    • 边界检查用例

13.9 用测试数据表驱动测试

  • 将测试的输入数据和期望的结果写在一起组成一个数据表,数据表中的每条记录都是一个含有输入和期望值的完整测试用例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var tests = []struct{ // Test table
in string
out string
}{
{"in1", "exp1"},
{"in2", "exp2"},
{"in3", "exp3"},
...
}
func TestFunction(t *testing.T) {
for i, tt := range tests {
s := FuncToBeTested(tt.in)
if s != tt.out {
t.Errorf("%d. %q => %q, wanted: %q", i, tt.in, s, tt.out)
}
}
}

13.10 性能调试:分析并优化go程序

13.10.1 时间和内存消耗

  • /usr/bin/time -f '%Uu %Ss %er %MkB %C' goprogexec 用户时间,系统时间,实际时间,最大内存占用

13.10.2 go test调试

  • go test -x -v -cpuprofile=prof.out -file x_test.go

13.10.3 pprof调试

  • pprof运行时报告数据
  • 有用的命令
    • topN - 分析结果中最开头的N份样本
    • web - 调用统计,浏览器打开
    • list - 代码行数和执行消耗时间

第14章 协程和通道

不需要共享内存来通信,而是通过通信来共享内存。

14.1 并发,并行和协程

  • 不要使用全局变量或者共享内存,会给你代码在并发运算时带来风险
  • go中使用goroutine进行并发运算
  • 协程和操作系统的线程不是一对一的关系,协程是根据一个或多个线程的可用性,映射(多路复用,执行于)在他们之上的;协程调度器在 Go 运行时很好的完成了这个工作。
  • 协程工作在相同的地址空间中,所以共享内存的方式一定是同步的
  • go使用channel来同步协程
  • 当系统调用阻塞协程时,其他协程会继续在其他线程上工作
  • 协程比线程更轻量级,4K的内存就可以创建一个协程
  • 协程通过关键字go来创建
  • 协程的栈会根据需要进行伸缩,不会溢出,结束会自动退出清理
  • main可以看做一个协程
  • init函数中可以运行协程
  • 并发程序可能并行,也可能不是
  • 并行是一种通过多处理器来提高速度的能力
  • go中设置GOMASXPROCS来支持多个系统线程的应用,n个核设置GOMAXPROCS=n-1

  • Go 协程意味着并行(或者可以以并行的方式部署),协程一般来说不是这样的

  • Go 协程通过通道来通信;协程通过让出和恢复操作来通信

14.2 协程间的通信

14.2.1 概念

  • 通道(channel),就像一个可以用于发送类型化数据的管道,由其负责协程之间的通信,从而避开所有由共享内存导致的陷阱;这种通过通道进行通信的方式保证了同步性。数据在通道中进行传递:在任何给定时间,一个数据被设计为只有一个协程可以对其访问,所以不会发生数据竞争。 数据的所有权(可以读写数据的能力)也因此被传递。
  • channel未初始化未nil
  • channel只能传递一种类型的数据
  • channel实际上是类型化消息的队列,使数据得以传输。它是先进先出(FIFO)的结构所以可以保证发送给他们的元素的顺序
  • channel是引用类型,make来初始化 ch1 := make(chan string)

14.2.2 通信操作符<-

  • 数据是按照箭头的方向流动的
    • ch <-int1
    • int2 := <- ch
    • <-ch
  • channel的收发都是原子操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
go sendData(ch)
go getData(ch)
time.Sleep(1e9)
}
func sendData(ch chan string) {
ch <- "Washington"
ch <- "Tripoli"
ch <- "London"
ch <- "Beijing"
ch <- "Tokyo"
}
func getData(ch chan string) {
var input string
// time.Sleep(2e9)
for {
input = <-ch
fmt.Printf("%s ", input)
}
}
  • 协程的同步:
    • main() 等待了 1 秒让两个协程完成,如果不这样,sendData() 就没有机会输出。
    • getData() 使用了无限循环:它随着 sendData() 的发送完成和 ch 变空也结束了。
    • 如果我们移除一个或所有 go 关键字,程序无法运行,Go 运行时会抛出 panic:

14.2.3 通道阻塞

  • 通道默认是同步且无缓冲的:接收者接收数据之前,发送不会结束。通道的发送和接收操作都是在对方准备好之前是阻塞的
1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import "fmt"
func main() {
ch1 := make(chan int)
go pump(ch1) // pump hangs
fmt.Println(<-ch1) // prints only 0
}
func pump(ch chan int) {
for i := 0; ; i++ {
ch <- i
}
}
// 0

14.2.4 通过channel进行协程同步

  • 通过channel,两个协程在通信中的某刻同步交换数据
1
2
3
4
5
6
7
8
func f1(in chan int) {
fmt.Println(<-in)
}
func main() {
out := make(chan int)
out <- 2
go f1(out)
}

14.2.5 同步通道-使用带缓冲的通道

  • ch1 := make(chan string, buf) buf为个数
  • 在缓冲满载之前,给通道发送数据是不会被阻塞的
  • 在缓冲空之前,读取是不会阻塞的

14.2.6 协程中用通道输出结果

用通道来完成阻塞

1
2
3
4
ch := make(chan int)
go sum(bigArray, ch) // bigArray puts the calculated sum on ch
// .. do something else for a while
sum := <- ch // wait for, and retrieve the sum

14.2.7 信号量模式

  • 协程通过在通道中放置一个值来处理结束的信号
1
2
3
4
5
6
7
8
9
func compute(ch chan int){
ch <- someComputation() // when it completes, signal on the channel.
}
func main(){
ch := make(chan int) // allocate a channel.
go compute(ch) // stat something in a goroutines
doSomethingElseForAWhile()
result := <- ch
}

14.2.8 实现并行的for循环

1
2
3
4
5
6
for i, v := range data {
go func (i int, v float64) {
doSomething(i, v)
...
} (i, v)
}

14.2.8 用带缓冲通道实现一个信号量

  • 信号量是实现互斥锁常见的同步机制
    • 带缓冲通道的容量和要同步的资源容量相同
    • 通道的长度(当前存放的元素个数)与当前资源被使用的数量相同
    • 容量减去通道的长度就是未处理的资源个数(标准信号量的整数值)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main
import (
"fmt"
"time"
)
func main() {
stream := pump()
go suck(stream)
time.Sleep(1e9)
}
func pump() chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
ch <- i
}
}()
return ch
}
func suck(ch chan int) {
for {
fmt.Println(<-ch)
}
}

14.2.10 给通道使用for循环

  • for-range
1
2
3
for v := range ch {
fmt.Printf("The value is %v\n", v)
}
  • 通道迭代模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main
import (
"fmt"
"time"
)
func main() {
suck(pump())
time.Sleep(1e9)
}
func pump() chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
ch <- i
}
}()
return ch
}
func suck(ch chan int) {
go func() {
for v := range ch {
fmt.Println(v)
}
}()
}

14.2.11 通道的方向

  • 只接收:var recv_noly <-chan int
  • 只发送:var send_only chan<- int

14.3 协程的同步:关闭通道-测试阻塞的通道

  • 通道可以被显示的关闭
  • 发送者需要关闭通道,接收者不需要
  • 关闭方法:defer close(ch)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main() {
ch := make(chan string)
go sendData(ch)
getData(ch)
}
func sendData(ch chan string) {
ch <- "Washington"
ch <- "Tripoli"
ch <- "London"
ch <- "Beijing"
ch <- "Tokio"
close(ch)
}
func getData(ch chan string) {
for {
input, open := <-ch
if !open {
break
}
fmt.Printf("%s ", input)
}
}
  • for-range来读取通道会检测通道是否会关闭

14.4 使用select切换协程

  • select 被称为通信开关
  • select可以处理多个通信中的一个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go pump1(ch1)
go pump2(ch2)
go suck(ch1, ch2)
time.Sleep(1e9)
}
func pump1(ch chan int) {
for i := 0; ; i++ {
ch <- i * 2
}
}
func pump2(ch chan int) {
for i := 0; ; i++ {
ch <- i + 5
}
}
func suck(ch1, ch2 chan int) {
for {
select {
case v := <-ch1:
fmt.Printf("Received on channel 1: %d\n", v)
case v := <-ch2:
fmt.Printf("Received on channel 2: %d\n", v)
}
}
}

14.5 通道、超时和计时器

  • time.Ticker结构体
1
2
3
4
5
type Ticker struct {
C <-chan Time // the channel on which the ticks are delivered.
// contains filtered or unexported fields
...
}
  • 可以返回一个通道并且不关闭
1
2
3
4
5
6
7
rate_per_sec := 10
var dur Duration = 1e9
chRate := time.Tick(dur)
for req := range requests {
<- chRate
go client.Call("Service.Method", req, ...)
}

实现了按照指定频率处理请求

  • Timer和Ticker类似,但是只会发送一次时间,可以应用在超时模式中
1
2
3
4
5
6
7
8
9
10
timeout := make(chan bool, 1)
go func() {
time.Sleep(1e9)
timeout<-true
}()
select {
case <-ch:
case <-timeout
}

14.6 协程和恢复

1
2
3
4
5
6
7
8
9
10
11
12
13
func server(workChan <-chan *Work) {
for work := range workChan {
go safelyDo(work)
}
}
func safelyDo(work *Work) {
defer func() {
if err := revocer(); err != nil {
log.Printf("")
}
}()
do(work)
}

一个协程发生panic会继续执行其他的协程

14.7 新旧模型对比

旧模式 - 使用共享内存来进行同步

  • 首先定义一个结构体处理一个任务
  • 各个任务组成任务池来共享内存,引入锁机制
  • 任务数量巨大,则锁机制的开销会导致效率急剧下降

新模式 - 使用通道

  • 使用一个通道接受需要处理的任务,一个通道接受处理完成的任务及其结果。worker在协程中启动,数量N可以调整
  • 不使用锁的机制,因为从通道获取到新任务不存在竞争关系
  • 我理解实际上锁机制是实现在了channel中,一个channel的获取是原子性的,从而提高性能
1
2
3
4
5
6
7
8
//Worker的写法, Master -> Workers
func Worker(in, out chan *Task) {
for {
t := <- in
process(t)
out <- t
}
}
  • 使用锁的情景:
    • 访问共享数据结构中的缓存信息
    • 保存应用程序上下文和状态信息数据
  • 使用通道的情景
    • 与异步操作的结果进行交互
    • 分发任务
    • 传递数据所有权

14.8 惰性生成器的实现

  • 生成器是指当被调用时返回一个序列中下一个值的函数,也被称为惰性求值
  • int channel实现的生成器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var resume chan int
func integers() chan int {
yield := make(chan int)
count := 0
go func() {
for {
yield <- count
count++
}
}()
return yield
}
func generateInteger() int {
resume = integers()
return <- resume
}
  • 使用空接口,闭包和高阶函数可以实现一个通用的惰性生产期的工厂函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
type Any interface{}
type EvalFunc func(Any) (Any, Any)
func BuildLazyEvaluator(evalFunc, initState Any) func() Any {
retValChan := make(chan Any)
loopFunc := func(){
var actState Any = initState
var retVal
for {
retVal, actState = evalFunc(actState)
retValChan <- retVal
}
}
retFunc := func() Any {
return <- retValChan
}
go loopFunc()
return retFunc
}
func BuildLazyIntEvaluator(evalFunc EvalFunc, initState Any) func() int {
ef := BuildLazyIntEvaluator(evalFunc, initState)
return func() int {
return ef().(int)
}
}
func main() {
evenFunc := func(state Any) (Any, Any) {
os := state.(int)
ns := os + 2
return os, ns
}
even := BuildLazyIntEvaluator(evenFunc, 0)
for i := 0; i < 10; i++ {
fmt.
}
}

14.9 实现Future模式

  • Future指的是你使用某一个值之前需要先对其进行计算,因此可以提前计算好
  • 求两个矩阵的乘积的逆 -> 先逆运算再乘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func InverseProduct(a, b Matrix) {
a_inv_future := InverseFuture(a)
b_inv_future := InverseFuture(b)
a_inv := <- a_inv_future
b_inv := <- b_inv_future
return Product(a_inv, b_inv)
}
func InverseFuture(a Matrix) chan Matrix {
future := make(chan Matrix)
go func() {
future <- Inverse(a)
}()
return future
}

14.10 复用

CS模式

  • go中服务器通常会在协程中执行向客户端的响应,故而会对每一个客户端请求启动一个协程,常见操作为客户端请求包含一个通道,服务器向这个通道中发送响应
  • 使用协程可以大大提高QPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
type Request struct {
a, b int
replyc chan int
}
type binOp func(a, b int) int
func run(op binOp, req *Request) {
req.replyc <- op(req.a, req.b)
}
func server (op binOp, service chan *Request) {
for {
req := <- service
go run(op, req)
}
}
func startServer(op binOp) chan *Request {
reqChan := make(chan *Request)
go server(op, reqChan)
return reqChan
}
func main(){
adder := startServer(func(a, b int) int {return a + b})
const N = 100
var reqs [n]Request
for i := 0; i < N; i++ {
req := &reqs[i]
req.a = i
req.b = i + N
req.replyc = make(chan int)
adder <- req
}
}

通过信号通道关闭服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func startServer(op binOp) (service chan *Request, quit chan bool) {
service = make(chan *Request)
quit = make(chan bool)
go server(op, service, quit)
return service, quit
}
func server(op binOp, service chan *request, quit chan bool) {
for {
select {
case req := <-service:
go run(op, req)
case <- quit:
return
}
}
}

14.11 限制同时处理的请求数 - 带缓冲的通道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const MAXREQS = 50
var sem = make(chan int, MAXREQS)
type Request struct {
a, b int
replyc chan int
}
func process(r *Request) {
// ...
}
func handle(r *Request) {
sem <- 1
process(r)
<- sem
}
func server(service chan *Request) {
for {
request := <- service
go handler(request)
}
}
func main() {
service := make(chan *Request)
go server(service)
}

14.12 链式协程

  • 海量协程的创建和运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var ngoroutine = flag.Int("n", 100000, "how many goroutines")
func f(left, right chan int) {
left <- 1 + <- right
}
func main() {
flag.Parse()
leftmost := make(chan int)
var left, right chan int = nil, leftmost
for i := 0; i < *ngoroutine; i++ {
left, right = right, make(chan int)
go f(left, right)
}
right <- 0
x := <-leftmost
fmt.Println(x)
}

14.13 在多核心上并行运算

  • 信号量模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func DoAll() {
sem := make(chan int, NCPU)
for i := 0; i < NCPU; i++ {
go DoPart(sem)
}
for i := 0; i < NCPU; i++ {
<-sem // wait for one task to complete
}
}
func DoPart(sem chan int) {
sem <- 1
}
func main() {
runtime.GOMAXPROCS(NCPU)
DOAll()
}

14.14并行化大量数据的计算

  • 串行处理流水线
1
2
3
4
5
6
7
8
func SerialProcessData(in <- chan *Data, out chan <- *Data) {
for data := range in {
tmpA := PreproceeData(data)
tmpB := ProcessStepA(tmpA)
tmpC := ProcessStepB(tmpB)
out <- PostProcessData(tmpC)
}
}
  • 并行计算
1
2
3
4
5
6
7
8
9
10
11
12
13
func ParallelProcessData(in <-chan *Data, out chan<- *Data) {
// make channels:
preOut := make(chan *Data, 100)
stepAOut := make(chan *Data, 100)
stepBOut := make(chan *Data, 100)
stepCOut := make(chan *Data, 100)
// start parallel computations:
go PreprocessData(in, preOut)
go ProcessStepA(preOut,StepAOut)
go ProcessStepB(StepAOut,StepBOut)
go ProcessStepC(StepBOut,StepCOut)
go PostProcessData(StepCOut,out)
}

14.15 漏桶算法

  • 客户端
1
2
3
4
5
6
7
8
9
10
11
12
func client() {
for {
var b *Buffer
select {
case b = <- freeList
// got one
default: b = new(Buffer)
}
loadInto(b)
serverChan <- b
}
}
  • 服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
func server() {
for {
b := <-serverChan // Wait for work.
process(b)
// Reuse buffer if there's room.
select {
case freeList <- b:
// Reuse buffer if free slot on freeList; nothing more to do
default:
// Free list full, just carry on: the buffer is 'dropped'
}
}
}

14.16 对go协程进行基准测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func main() {
fmt.Println(" sync", testing.Benchmark(BenchmarkChannelSync).String())
fmt.Println("buffered", testing.Benchmark(BenchmarkChannelBuffered).String())
}
func BenchmarkChannelSync(b *testing.B) {
ch := make(chan int)
go func() {
for i := 0; i < b.N; i++ {
ch <- i
}
close(ch)
}()
for range ch {
}
}
func BenchmarkChannelBuffered(b *testing.B) {
ch := make(chan int, 128)
go func() {
for i := 0; i < b.N; i++ {
ch <- i
}
close(ch)
}()
for range ch {
}
}

14.17 使用通道并发访问对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
type Person struct {
Name string
salary float64
chF chan func()
}
func NewPerson(name string, salary float64) *Person {
p := &Person{name, salary, make(chan func())}
go p.backend()
return p
}
func (p *Person) backend() {
for f := range p.chF {
f()
}
}
// Set salary.
func (p *Person) SetSalary(sal float64) {
p.chF <- func() { p.salary = sal }
}
// Retrieve salary.
func (p *Person) Salary() float64 {
fChan := make(chan float64)
p.chF <- func() { fChan <- p.salary }
return <-fChan
}
func (p *Person) String() string {
return "Person - name is: " + p.Name + " - salary is: " + strconv.FormatFloat(p.Salary(), 'f', 2, 64)
}
func main() {
bs := NewPerson("Smith Bill", 2500.5)
fmt.Println(bs)
bs.SetSalary(4000.25)
fmt.Println("Salary changed:")
fmt.Println(bs)
}

第15章 网络,模板和网页应用

15.1 tcp服务器

  • 服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func main() {
listener, err := net.Listen("tcp", "localhost:50000")
if err != nil {
return
}
for {
conn, err := listener.Accept()
if err != nil {
return
}
go doServerStuff(conn)
}
}
func doServerStuff(conn net.Conn) {
for {
buf := make([]byte, 512)
len, err := conn.Read(buf)
if err != nil {
return
}
fmt.Println("%v", string(buf[:len]))
}
}
  • 客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func main() {
//打开连接:
conn, err := net.Dial("tcp", "localhost:50000")
if err != nil {
//由于目标计算机积极拒绝而无法创建连接
fmt.Println("Error dialing", err.Error())
return // 终止程序
}
inputReader := bufio.NewReader(os.Stdin)
fmt.Println("First, what is your name?")
clientName, _ := inputReader.ReadString('\n')
// fmt.Printf("CLIENTNAME %s", clientName)
trimmedClient := strings.Trim(clientName, "\r\n") // Windows 平台下用 "\r\n",Linux平台下使用 "\n"
// 给服务器发送信息直到程序退出:
for {
fmt.Println("What to send to the server? Type Q to quit.")
input, _ := inputReader.ReadString('\n')
trimmedInput := strings.Trim(input, "\r\n")
// fmt.Printf("input:--%s--", input)
// fmt.Printf("trimmedInput:--%s--", trimmedInput)
if trimmedInput == "Q" {
return
}
_, err = conn.Write([]byte(trimmedClient + " says: " + trimmedInput))
}
}
  • 改进后的tcp_server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
const maxRead = 25
func main() {
flag.Parse()
if flag.NArg() != 2 {
panic("usage: host port")
}
hostAndPort := fmt.Sprintf("%s:%s", flag.Arg(0), flag.Arg(1))
listener := initServer(hostAndPort)
for {
conn, err := listener.Accept()
checkError(err, "Accept: ")
go connectionHandler(conn)
}
}
func initServer(hostAndPort string) *net.TCPListener {
serverAddr, err := net.ResolveTCPAddr("tcp", hostAndPort)
checkError(err, "Resolving address:port failed: `" + hostAndPort + "'")
listener, err := net.ListenTCP("tcp", serverAddr)
checkError(err, "ListenTCP: ")
println("Listening to: ", listener.Addr().String())
return listener
}
func connectionHandler(conn net.Conn) {
connFrom := conn.RemoteAddr().String()
println("Connection from: ", connFrom)
sayHello(conn)
for {
var ibuf []byte = make([]byte, maxRead + 1)
length, err := conn.Read(ibuf[0:maxRead])
ibuf[maxRead] = 0 // to prevent overflow
switch err {
case nil:
handleMsg(length, err, ibuf)
case syscall.EAGAIN: // try again
continue
default:
goto DISCONNECT
}
}
DISCONNECT:
err := conn.Close()
println("Closed connection: ", connFrom)
checkError(err, "Close: ")
}
func sayHello(to net.Conn) {
obuf := []byte{'L', 'e', 't', '\'', 's', ' ', 'G', 'O', '!', '\n'}
wrote, err := to.Write(obuf)
checkError(err, "Write: wrote " + string(wrote) + " bytes.")
}
func handleMsg(length int, err error, msg []byte) {
if length > 0 {
print("<", length, ":")
for i := 0; ; i++ {
if msg[i] == 0 {
break
}
fmt.Printf("%c", msg[i])
}
print(">")
}
}
func checkError(error error, info string) {
if error != nil {
panic("ERROR: " + info + " " + error.Error()) // terminate goprogram
}
}

15.2 简单的网页服务器

  • net/http包
  • http.ListenAndServer
  • http.URL
  • http.Request
  • request.FormValue
  • request.ParseForm
  • http.HandlerFunc 具体函数 - 对应路径,类似于controller
1
2
3
4
5
6
7
8
9
10
11
12
func HelloServer(w http.ResponseWriter, req *http.Request) {
fmt.Println("Inside HelloServer handler")
fmt.Fprintf(w, "Hello,"+req.URL.Path[1:])
}
func main() {
http.HandleFunc("/", HelloServer)
err := http.ListenAndServe("localhost:8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err.Error())
}
}
  • http.ListenAndServerTLS - https

15.3 访问并读取页面

  • http.Head()来请求URL
  • http.Get()获取Body中的网页内容
  • http.Redirect(w RespoonseWriter, r *Request, url string, code int) 浏览器重定向URL
  • http.NotFound(w ResponseWriter, r *Request) 404
  • http.Error(w ResponseWriter, r *Request) 错误信息和错误码
  • http.Request
  • req.Method

  • go定义的HTTP状态码

    • http.StatusContinue = 100
    • http.StatusOK = 200
    • http.StatusFound = 302
    • http.StatusBadRequest = 400
    • http.StatusUnauthorized = 401
    • http.StatusForbidden = 403
    • http.StatusNotFound = 404
    • http.StatusInternalServerError = 500

15.4 网页应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const form = `
<html><body>
<form action="#" method="post" name="bar">
<input type="text" name="in" />
<input type="submit" value="submit"/>
</form>
</body></html>
`
func SimpleServer(w http.ResponseWriter, request *http.Request) {
io.WriteString(w, "<h1>hello, world</h1>")
}
func FormServer(w http.ResponseWriter, request *http.Request) {
w.Header().Set("Content-Type", "text/html")
switch request.Method {
case "GET":
io.WriteString(w, form)
case "POST":
io.WriteString(w, request.FormValue("in"))
}
}
func main() {
http.HandleFunc("/test1", SimpleServer)
http.HandleFunc("/test2", FormServer)
if err := http.ListenAndServe(":8088", nil); err != nil {
panic(err)
}
}

15.5 网页应用健壮

  • 使用闭包的错误处理模式
1
2
3
4
5
6
7
8
9
10
11
12
type HandleFnc func(http.ResponseWriter, *http.Request)
func logPanics(function HandlerFunc) HandlerFnc {
return func(writer http.ResponseWriter, request *http.Request) {
defer func() {
if x := recover(); x != nil {
log...
}
}()
function(writer, request)
}
}

15.6 模板技术动态生成页面

  • 模板缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package main
import (
"io/ioutil"
"log"
"net/http"
"regexp"
"text/template"
)
const lenPath = len("/view/")
var titleValidator = regexp.MustCompile("^[a-zA-Z0-9]+$")
var templates = make(map[string]*template.Template)
var err error
type Page struct {
Title string
Body []byte
}
func init() {
for _, tmpl := range []string{"edit", "view"} {
templates[tmpl] = template.Must(template.ParseFiles(tmpl + ".html"))
}
}
func main() {
http.HandleFunc("/view/", makeHandler(viewHandler))
http.HandleFunc("/edit/", makeHandler(editHandler))
http.HandleFunc("/save/", makeHandler(saveHandler))
err := http.ListenAndServe("localhost:8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err.Error())
}
}
func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
title := r.URL.Path[lenPath:]
if !titleValidator.MatchString(title) {
http.NotFound(w, r)
return
}
fn(w, r, title)
}
}
func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
p, err := load(title)
if err != nil { // page not found
http.Redirect(w, r, "/edit/"+title, http.StatusFound)
return
}
renderTemplate(w, "view", p)
}
func editHandler(w http.ResponseWriter, r *http.Request, title string) {
p, err := load(title)
if err != nil {
p = &Page{Title: title}
}
renderTemplate(w, "edit", p)
}
func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
body := r.FormValue("body")
p := &Page{Title: title, Body: []byte(body)}
err := p.save()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/view/"+title, http.StatusFound)
}
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
err := templates[tmpl].Execute(w, p)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func (p *Page) save() error {
filename := p.Title + ".txt"
// file created with read-write permissions for the current user only
return ioutil.WriteFile(filename, p.Body, 0600)
}
func load(title string) (*Page, error) {
filename := title + ".txt"
body, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return &Page{Title: title, Body: body}, nil
}

15.7 template包 - 先不看

15.8 多功能网页服务器

15.9 RPC远程调用

  • net/rpc包实现互相通信

  • rpc_object

1
2
3
4
5
6
7
8
9
10
package rpc_objects
type Args struct {
N, M int
}
func (t *Args) Multiply(args *Args, reply *int) error {
*reply = args.N * args.M
return nil
}
  • server
1
2
3
4
5
6
7
8
9
10
11
func main() {
calc := new(rpc_objects.Args)
rpc.Register(calc)
rpc.HandleHTTP()
listener, e := net.Listen("tcp", "localhost:1234")
if e != nil {
log.Fatal("Starting RPC-server -listen error:", e)
}
go http.Serve(listener, nil)
time.Sleep(1000e9)
}
  • rpc_client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const serverAddress = "localhost"
func main() {
client, err := rpc.DialHTTP("tcp", serverAddress + ":1234")
if err != nil {
log.Fatal("Error dialing:", err)
}
// Synchronous call
args := &rpc_objects.Args{7, 8}
var reply int
err = client.Call("Args.Multiply", args, &reply)
if err != nil {
log.Fatal("Args error:", err)
}
fmt.Printf("Args: %d * %d = %d", args.N, args.M, reply)
}

15.10 基于网络的通道 netchan

  • netchan包实现了类型安全的网络通道:允许一个通道两端出现由网络连接的不同计算机
  • 一组导出器会按名称发布一组通道,导入器连接到导出的机器,并按照名称导入这些通道

  • 发送端

1
2
3
4
5
6
7
8
9
exp, err := netchan.NewExporter("tcp", "netchanserver.mydomain.com:1234")
if err != nil {
log.Fatalf("Error making Exporter: %v", err)
}
ch := make(chan myType)
err := exp.Export("sendmyType", ch, netchan.Send)
if err != nil {
log.Fatalf("Send Error: %v", err)
}
  • 接收端
1
2
3
4
5
6
7
8
9
imp, err := netchan.NewImporter("tcp", "netchanserver.mydomain.com:1234")
if err != nil {
log.Fatalf("Error making Importer: %v", err)
}
ch := make(chan myType)
err = imp.Import("sendmyType", ch, netchan.Receive)
if err != nil {
log.Fatalf("Receive Error: %v", err)
}

15.11 websocket

  • 服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func server(ws *websocket.Conn) {
fmt.Printf("new connection\n")
buf := make([]byte, 100)
for {
if _, err := ws.Read(buf); err != nil {
fmt.Printf("%s", err.Error())
break
}
}
fmt.Printf(" => closing connection\n")
ws.Close()
}
func main() {
http.Handle("/websocket", websocket.Handler(server))
err := http.ListenAndServe(":12345", nil)
if err != nil {
panic("ListenAndServe: " + err.Error())
}
}
  • 客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func main() {
ws, err := websocket.Dial("ws://localhost:12345/websocket", "",
"http://localhost/")
if err != nil {
panic("Dial: " + err.Error())
}
go readFromServer(ws)
time.Sleep(5e9)
ws.Close()
}
func readFromServer(ws *websocket.Conn) {
buf := make([]byte, 1000)
for {
if _, err := ws.Read(buf); err != nil {
fmt.Printf("%s\n", err.Error())
break
}
}
}

15.12 使用smtp发送邮件

  • smpt Simple Mail Transfer Protocol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func main() {
// Set up authentication information.
auth := smtp.PlainAuth(
"",
"user@example.com",
"password",
"mail.example.com",
)
// Connect to the server, authenticate, set the sender and recipient,
// and send the email all in one step.
err := smtp.SendMail(
"mail.example.com:25",
auth,
"sender@example.org",
[]string{"recipient@example.net"},
[]byte("This is the email body."),
)
if err != nil {
log.Fatal(err)
}
}

第16章 常见的错误与陷阱

  • 永远不要声明形如var p*a的变量
  • 永远不要在for循环中改变计数器的变量
  • 永远不要在for-range循环中使用一个值去改变自身的值
  • 永远不要将goto和前置标签一起使用
  • 永远不要忘记在函数名后边加上()
  • 永远不要使用new()创建map
  • 为一个两类型定义一个String方法时,不要使用fmt.Print或者类似的代码
  • 永远不要忘记当终止缓存写入时,使用Flush函数
  • 永远不要忽略错误提示,忽略错误会导致程序崩溃
  • 不要使用全局变量或者共享内存,并发不安全
  • println函数仅仅用于调试
  • 使用正确的方式初始化一个元素是切片的映射,例如map[type]slice
  • 一直使用逗号,ok或者checked形式作为类型断言
  • 使用一个工厂函数创建并初始化自己定义的类型
  • 仅当一个结构体的方法想改变结构体时,使用结构体指针作为方法的接受者,否则使用一个结构体类型

16.1 误用短声明导致变量覆盖

1
2
3
4
5
6
7
8
9
10
11
12
func shadow() (err error) {
x, err := check1() // x是新创建变量,err是被赋值
if err != nil {
return // 正确返回err
}
if y, err := check2(x); err != nil { // y和if语句中err被创建
return // if语句中的err覆盖外面的err,所以错误的返回nil!
} else {
fmt.Println(y)
}
return
}

16.2 误用字符串

  • 字符串不可变
  • 字符串运算效率低下
  • 使用字符数组进行运算
1
2
3
4
5
6
var b bytes.Buffer
...
for condition {
b.WriteString(str) // 将字符串str写入缓存buffer
}
return b.String()

16.3 发生错误时使用defer关闭一个文件

1
2
3
4
5
6
7
8
9
for _, file := range files {
if f, err = os.Open(file); err != nil {
return
}
// 对文件进行操作
f.Process(data)
// 关闭文件
f.Close()
}
  • defer只在函数返回时才会被执行,在循环的结尾或其他一些有限范围的代码内是不会被执行的

16.4 new和make

  • 切片,Map和channel,用make
  • 数组,结构体,值类型用new

16.5 不需要将一个指向切片的指针传递给函数

  • 切片实际上就是一个指向潜在数组的指针
  • 当切片作为参数传递时,切记不要解引用切片

16.6 使用指针指向接口类型

  • 接口类型已经是一个指针了

16.7 使用值类型时误用指针

  • 值类型的内存时栈上分配,内存分配快速且开销不大
  • 指针类型需要在堆上创建,导致额外的内存分配

16.8 误用协程和通道

  • 大多数情况下,通过栈传递参数会更有效率
  • 当且仅当代码中并发执行非常重要,才使用协程和通道

16.9 闭包和协程的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
var values = [5]int{10, 11, 12, 13, 14}
func main() {
// 版本A:
for ix := range values { // ix是索引值
func() {
fmt.Print(ix, " ")
}() // 调用闭包打印每个索引值
}
fmt.Println()
// 版本B: 和A版本类似,但是通过调用闭包作为一个协程
for ix := range values {
go func() {
fmt.Print(ix, " ")
}()
}
fmt.Println()
time.Sleep(5e9)
// 版本C: 正确的处理方式
for ix := range values {
go func(ix interface{}) {
fmt.Print(ix, " ")
}(ix)
}
fmt.Println()
time.Sleep(5e9)
// 版本D: 输出值:
for ix := range values {
val := values[ix]
go func() {
fmt.Print(val, " ")
}()
}
time.Sleep(1e9)
}
/*
0 1 2 3 4
4 4 4 4 4
1 0 3 4 2
10 11 12 13 14
*/

16.10 糟糕的错误处理

  • 创建一个布尔型变量用于测试错误条件是多余的
  • 避免错误检测使代码变得混乱,使用闭包来封装你的错误检验

第17章 Go语言模式

17.1 逗号,ok模式

  1. 函数返回时检查错误
    1. val, err := pack1.Func()
  2. 检测Map中是否存在一个键值
    1. if value, isPresent = map1[key1]; isPresent {}
  3. 检测一个接口类型变量varI是否包含了类型T,类型断言
    1. if value, ok := varI.(T); ok {}
  4. 检测一个通道ch是否关闭
    1. for input := range ch {}

17.2 defer模式

使用场景 例子
关闭一个文件流 defer f.Close()
解锁一个被锁定的资源 defer mu.Unlock()
关闭一个通道 defer close(ch)
从panic恢复 defer func(){if err := revocer(); err != nil {}}
停止一个计时器 defer tick1.Stop()
释放一个进程p defer p,Release()
停止CPU性能分析并立即写入 defer pprof.StopCPUProfile()

17.3 可见性模式

17.4 运算符模式和接口

17.4.1 函数作为运算符

  • go中没有函数重载,我们不得不给函数起不同的名称,可以将其作为包的私有函数,并暴露单一的 Add() 函数作为公共 API。可以在嵌套的 switch 断言中测试类型,以便在任何支持的参数组合上执行操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func Add(a Matrix, b Matrix) Matrix {
switch a.(type) {
case sparseMatrix:
switch b.(type) {
case sparseMatrix:
return addSparseToSparse(a.(sparseMatrix), b.(sparseMatrix))
case denseMatrix:
return addSparseToDense(a.(sparseMatrix), b.(denseMatrix))
}
default:
// 不支持的参数
}
}

17.4.2 方法作为运算符

  • 根据接收者的不同,可以区分不同的方法
1
2
3
4
5
6
7
8
9
10
11
12
func (a *sparseMatrix) Add(b Matrix) Matrix {
switch b.(type) {
case sparseMatrix:
return addSparseToSparse(a.(sparseMatrix), b.(sparseMatrix))
case denseMatrix:
return addSparseToDense(a.(sparseMatrix), b.(denseMatrix))
default:
// 不支持的参数
}
}

17.4.3 使用接口

  • 不同类型上执行相同的方法时,创建一个通用化的接口以实现多态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Algebraic interface {
Add(b Algebraic) Algebraic
Min(b Algebraic) Algebraic
Mult(b Algebraic) Algebraic
Elements()
}
func (a *denseMatrix) Add(b Algebraic) Algebraic {
switch b.(type) {
case sparseMatrix:
return addDenseToSparse(a, b.(sparseMatrix))
default:
for x in range b.Elements() …
}
}

第18章 实用代码块

18.1 字符串

  • 如何修改字符串中的一个字符
1
2
3
4
str := "hello"
c := []byte(str)
c[0] = 'c'
s2 := string(c)
  • 如何获取字符串的子串 - substr := str[n;m]

  • 如何使用for或者for-range遍历一个字符串

1
2
3
4
5
6
7
8
// gives only the bytes:
for i:=0; i < len(str); i++ {
… = str[i]
}
// gives the Unicode characters:
for ix, ch := range str {
}
  • 如何获取一个字符串的字节数 - len()

  • 如何获取一个字符串的字符数 - utf8.RuneCountInString(str)

  • 如何连接字符串

    • with a bytes.Buffer
    • Strings.Join()
    • +=
  • 如何解析命令行参数:使用os或者flag包

18.2 数组和切片

  • 创建

    • arr1 := new([len]type)
    • slice1 := make([]type, len)
  • 初始化

    • arr1 := [...]type{i1,i2,i3,i4,i5}
    • arrKeyValue := [len]type{i1:val1, i2: val2}
    • var slice1 []type = arr1[start:end]
  • 截断数组或者切片的最后一个元素

    • line = line[:len(line)-1]
  • 遍历数组或者切片

1
2
3
4
5
6
for i:=0; i < len(arr); i++ {
… = arr[i]
}
for ix, value := range arr {
}
  • 二维数组或者切片中查找指定值V
1
2
3
4
5
6
7
8
9
found := false
Found: for row := range arr2Dim {
for column := range arr2Dim[row] {
if arr2Dim[row][column] == V{
found = true
break Found
}
}
}

18.3 Map

  • 创建

    • map1 := make(map[keyType]valueType)
  • 初始化

    • map1 := map[string]int{"one":1, "two":2}
  • 遍历

    • for key, val := range map1{}
  • 在map中检测key1是否存在

    • val1, isPresent = map1[key1]
  • 删除一个键

    • delete(map1, key1)

18.4 结构体

  • 创建
1
2
3
4
5
6
type structName struct {
field1 type1
field2 type2
}
ms := new(struct1)
  • 初始化

    • ms := &struct1{10,10,"chris"}
  • 结构体命名大写开头则包外可见

  • 每个结构体需要定义一个构件函数来初始化结构体

18.5 接口

  • 检测一个值v是否实现了接口Stringer

    • if v, ok := v.(Stringer); ok {}
  • 如何使用接口实现一个类型分类接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func classifier(items ...interface{}) {
for i, x := range items {
switch x.(type) {
case bool:
fmt.Printf("param #%d is a bool\n", i)
case float64:
fmt.Printf("param #%d is a float64\n", i)
case int, int64:
fmt.Printf("param #%d is an int\n", i)
case nil:
fmt.Printf("param #%d is nil\n", i)
case string:
fmt.Printf("param #%d is a string\n", i)
default:
fmt.Printf("param #%d’s type is unknown\n", i)
}
}
}

18.6 函数

  • 使用recover恢复panic
1
2
3
4
5
6
7
8
9
10
11
func protect(g func()) {
defer func() {
log.Println("done")
// Println executes normally even if there is a panic
if x := recover(); x != nil {
log.Printf("run time panic: %v", x)
}
}()
log.Println("start")
g()
}

18.7 文件

  • 打开并读取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
file, err := os.Open("input.dat")
if err != nil {
}
defer file.Close()
iReader := bufio.NewReader(file)
for {
str, err := iReader.ReadString('\n')
if err != nil {
return
}
fmt.Println("")
}
  • 通过切片读写文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func cat(f *file.File) {
const NBUF = 512
var buf [NBUF]byte
for {
switch nr, er := f.Read(buf[:]); true {
case nr < 0:
fmt.Fprintf(os.Stderr, "cat: error reading from %s: %s\n",
f.String(), er.String())
os.Exit(1)
case nr == 0: // EOF
return
case nr > 0:
if nw, ew := file.Stdout.Write(buf[0:nr]); nw != nr {
fmt.Fprintf(os.Stderr, "cat: error writing from %s: %s\n",
f.String(), ew.String())
}
}
}
}

18.8 goroutine和channel

  • tips

    • 出于性能考虑建议使用带缓存的通道
    • 限制一个通道的数据数量并将它们封装成一个数组
  • 遍历一个channel

    • for v := range ch {}
  • 检测一个通道ch是否关闭

    • input, open := <-ch; !open { }
  • 通过一个通道让主程序等待直到协程完成

1
2
3
4
5
ch := make(chan int)
go func() {
ch <- 1
}()
<-ch
  • 通道的工厂模板
1
2
3
4
5
6
7
8
9
func pump() chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
ch <- i
}
}()
return ch
}
  • 通道迭代器模板
  • 如何限制并发请求的处理数量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const MAXREQS = 50
var sem = make(chan int, MAXREQS)
type Request struct {
a, b int
replyc chan int
}
func process(r *Request) {
// do something
}
func handle(r *Request) {
sem <- 1 // doesn't matter what we put in it
process(r)
<-sem // one empty place in the buffer: the next request can start
}
func server(service chan *Request) {
for {
request := <-service
go handle(request)
}
}
func main() {
service := make(chan *Request)
go server(service)
}
  • 如何在多核上实现并行计算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func DoAll(){
sem := make(chan int, NCPU) // Buffering optional but sensible
for i := 0; i < NCPU; i++ {
go DoPart(sem)
}
// Drain the channel sem, waiting for NCPU tasks to complete
for i := 0; i < NCPU; i++ {
<-sem // wait for one task to complete
}
// All done.
}
func DoPart(sem chan int) {
// do the part of the computation
sem <-1 // signal that this piece is done
}
func main() {
runtime.GOMAXPROCS(NCPU) // runtime.GOMAXPROCS = NCPU
DoAll()
}
  • 如何终止一个协程

    • runtime.Goexit()
  • 简单的超时模板

1
2
3
4
5
6
7
8
9
10
11
timeout := make(chan bool, 1)
go func() {
time.Sleep(1e9) // one second
timeout <- true
}()
select {
case <-ch:
// a read from ch has occurred
case <-timeout:
// the read from ch has timed out
}
  • 使用输入通道和输出通道代替锁
1
2
3
4
5
6
7
func Worker(in, out chan *Task) {
for {
t := <-in
process(t)
out <- t
}
}

18.9 网络和网页应用

  • 制作解析并使模板生效
    • var strTempl = template.Must(template.New("TName").Parse(strTemplateHTML))

18.10 其他

  • 在程序出错时终止程序
1
2
3
4
5
6
7
8
if err != nil {
...
os.Exit(1)
}
if err != nil {
panic("ERROR occurred: " + err.Error())
}

18.11 出于性能考虑的最佳实践和建议

  • 尽可能的使用:=去初始化声明的一个变量
  • 尽可能的使用字符代替字符串
  • 尽可能使用切片替代数组
  • 尽可能使用数组和切片替换映射
  • 如果只想获取切片中的某项值,不需要值的索引,尽可能使用for-range去遍历切片
  • 当数组元素是稀疏的,使用映射会降低内存消耗
  • 初始化Map时指定其容量
  • 当定义一个方法时,使用指针类型所谓方法的接受者
  • 在代码中使用常量或者标志提取常量的值
  • 尽可能在需要分配大量内存时使用缓存
  • 使用缓存模板
Donate comment here