go的函数式编程

defer的特点

  1. defer栈是后进先出,可以穿越panic和return
  2. 参数在defer语句运行时计算
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

func deferVal() {
for i := 0; i < 3; i++ {
fmt.Println(i)
defer func() { fmt.Println(i) }()
}
}

func writeFile(filename string) {
file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)
if err != nil {
//fmt.Println("file already exist")
if pathError, ok := err.(*os.PathError); !ok {
panic(err)
} else {
fmt.Println(pathError.Op, pathError.Path, pathError.Err)
}
return
}
defer file.Close()

writer := bufio.NewWriter(file)
defer writer.Flush()

//f := fib.Fib()
var i int = 0
for ; i < 3; i++ {
// 打印3遍
fmt.Fprintf(writer, "你好啊")
}
}


func main() {
// 创建文件 ./fib.txt
writeFile("fib.txt") // 你好啊你好啊你好啊

deferVal() // 0 1 2 3 3 3
}

http的错误处理

自定义错误、自定义结构体实现接口

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
98
99
100
101
102
103
104
type custom_error struct {
content string
time time.Time
position string
}

type userError string

func (r userError) Error() string {
return r.Message()
}
func (r userError) Message() string {
return string(r)

}

func (e custom_error) Error() string {
//TODO implement me
return fmt.Sprintf(" %s : %s", e.position, e.content)
}

func judgeCustomErr(err error) bool {
switch err.(type) {
case custom_error:
return true
default:
return false
}
}

const prefix = "/source/"

func fileHandler(writer http.ResponseWriter, request *http.Request) error {
if strings.Index(request.URL.Path, prefix) != 0 {
return userError("path must start with " + prefix)
}
path := request.URL.Path[len(prefix):]
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()

//// 随机自定义错误
//a, b := rand.Intn(400), 200
//if a < b {
// return custom_error{"错误自定义", time.Now(), "这里是错误"}
//}

all, err := ioutil.ReadAll(file)
if err != nil {
return err
}

writer.Write(all)
return nil
}

type appHandler func(writer http.ResponseWriter, request *http.Request) error

func errWraper(handler appHandler) func(writer http.ResponseWriter, request *http.Request) {
return func(writer http.ResponseWriter, request *http.Request) {
defer func() {
if r := recover(); r != nil {
log.Printf("process error: %v", r)
http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}()

err := handler(writer, request)
if err != nil {
if userErr, ok := err.(userError); ok {
http.Error(writer, userErr.Message(), http.StatusInternalServerError)
}

code := http.StatusOK
r, ok := err.(custom_error)
log.Printf("err occured during handling open file: %s ", err.Error())
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
case os.IsPermission(err):
code = http.StatusForbidden
case ok:
http.Error(writer, r.Error(), 500)
default:
code = http.StatusInternalServerError
}
if !judgeCustomErr(err) {
http.Error(writer, http.StatusText(code), code)
}
}
}
}

func main(){

http.HandleFunc("/source/", errWraper(fileHandler))

err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}

panic 和 recover

error
意料之中的错误使用error;如:文件打不开
意料之外的错误使用panic;如:数组越界

panic

  1. 停止当前函数执行
  2. 一直向上返回,执行每一层的defer
  3. 如果没有遇见recover,程序退出

recover

  1. 仅在defer中调用
  2. 获取panic的值
  3. 如果无法处理,重新panic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

func tryRecover() {
defer func() {
r := recover()
if err, ok := r.(error); ok {
fmt.Println("error occured:", err)
} else {
panic(err)
}
}()

b := 0
a := 5 / b
fmt.Println(a)

//panic(errors.New("this is a error"))
}

go面向对象

goroutine 协程

  • 协程又常被人们叫做fiber

  • 非抢占式多任务处理,有协程主动交出控制权(线程就叫抢占式多任务处理,操作系统控制)

  • 编译器/解释器/虚拟机层面的多任务

  • 多个协程可能在一个或多个线程上运行

1
2
3
4
5
6
7
8
9
10
11
12
13
func main (){
var a [10]int
for i := 0; i < 10; i++ {
go func(i int) {
for {
a[i]++
//fmt.Printf("Hello from goroutine %d\n", i) //io操作会进行切换,会有等待过程,所以可以让其他协程运行
}
}(i)
}
time.Sleep(time.Millisecond)
fmt.Println(a)
}
1
2
3

# 检查数据访问冲突
go run -race goroutine.go

goroutine的定义

  • 在go程序中,main结束,所有的goroutine都会被干掉
  • 任何函数只需加上go就能送给调度器运行
  • 不需要在定义时区分是否是异步函数
  • 调度器在合适的节点进行切换
  • 使用-race检测数据访问冲突

goroutine可能切换的点

  • io,select
  • channel
  • 等待锁
  • 函数调用(有时)
  • runtime.Gosched()
    只是参考,不能保证切换,不能保证在其他地方不切换
    在go中就算开1000个goroutine,还是根据核数来开物理线程数

channel

channel用于goroutine之间的通讯. 其内部实现了同步, 确保并发安全, 多个goroutine同时访问, 不需要加锁.

不要通过共享内存在来通信,通过通信来共享内存
对channel操作行为做如下总结:

  1. ch <- : 写入channel

  2. ch -> :读出channel

  3. close : 关闭channel

golang 中大部分类型都是值类型, 只有 slice / channel / map 是引用类型

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

func worker(id int, c chan int) {
for {
fmt.Printf("worker %d received %d \n", id, <-c)
}
}

func bufferChannelClose() {
// 第二个参数表示缓冲区,就是可以存放的等待数为4个
c := make(chan int, 4)
go worker(0, c)
c <- 2
c <- 2
c <- 2
c <- 2
close(c)
}
// 有一个goroutine发数据,必须有一个goroutine收数据, 不然就不能放进去了, 会报异常deadlock
// fatal error: all goroutines are asleep - deadlock!


func doWork(id int, c chan int) {
// 可以取出所有channel中传送的数据
for n := range c {
fmt.Printf("worker %d received %d \n", id, n)
}

// 另一种方式
// for {
// if n, ok := <-c; !ok {
// break
// } else {
// fmt.Printf("worker %d received %c \n", id, n)
// }
// }
}

func channelClose() {
c := make(chan int)
go doWork(0, c)
c <- 2
c <- 2
c <- 2
c <- 2
close(c)
time.Sleep(time.Millisecond)
}

worker

channel是goroutine之间的通讯. 所以有一个goroutine发数据, 就要有一个goroutine收数据

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
type worker struct {
in chan int
done chan bool
}

func work(id int, c chan int, done chan bool) {
for n := range c {
fmt.Printf("worker %d received %d \n", id, n)
// channel有发就要有收,但是在goroutine中另外开启一个goroutine就不用接受
go func() { done <- true }()
}
}

func createWorker(id int) worker {
w := worker{
make(chan int),
make(chan bool),
}
go work(id, w.in, w.done)
return w
}

func channelDemo() {
var workers [3]worker
for i := 0; i < 3; i++ {
workers[i] = createWorker(i)
}

for i := 0; i < 3; i++ {
workers[i].in <- i
}

for i := 0; i < 3; i++ {
workers[i].in <- 'a' + i
}

//wait for all task
for _, worker := range workers {
<-worker.done
<-worker.done
}
}

func main() {
channelDemo()
}

使用waitGroup来管理等待数量

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 worker struct {
in chan int
wg *sync.WaitGroup
}

func work(id int, w worker) {
for n := range w.in {
fmt.Printf("worker %d received %c \n", id, n)
w.wg.Done()
}
}

func createWorker(id int, wg *sync.WaitGroup) worker {
w := worker{ make(chan int), wg}
go work(id, w)
return w
}

func channelDemo() {
wg := sync.WaitGroup{}
var workers [10]worker
wg.Add(20)
for i := 0; i < 10; i++ {
workers[i] = createWorker(i, &wg)
}

for i, worker := range workers {
worker.in <- 'A' + i
}

for i, worker := range workers {
worker.in <- 'a' + i
}

wg.Wait()
//time.Sleep(time.Millisecond)
}

func main() {
channelDemo()
}

goroutine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

// 这么写会报deadline, 因为管道只有发送方, 没有接收方. 要求必须既有发送方又有接收方
// func main() {
// c := make(chan int)
// c <- 0
// }

// 这么写就不会有问题,下面的代码只会打印 11, goroutine运行了 c <- 1 就一直在等待有人接收
func main() {
c := make(chan int)
go func() {
log.Info("11")
c <- 1
log.Info("22")
c <- 2
log.Info("33")
c <- 3
log.Info("44")
c <- 4
}()
time.Sleep(time.Second)
}

使用channel进行树的遍历

查找树中最大的值

第一步: 循环遍历获取树, 然后将所有树节点放入到channel中. 返回一个管道

第二步: 从管道中取出树的节点, 进行计算

第三步: 在第一步中, 把所有节点都添加到管道中以后, 一定要close

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 定一个管道, 循环遍历, 把遍历后的节点添加到管道中
func (n *TreeNode) TraveresForChannel() chan *TreeNode {
out := make(chan *TreeNode)
go func() {
n.TraveresFunc(func(node *TreeNode) {
out <- node
})

close(out)
}()
return out
}

// 从管道中取出所有节点, 取最大值
max := 0
for c := range root.TraveresForChannel() {
if c.Value > max {
max = c.Value
}
}

fmt.Println(max)

go的函数式编程

go里面函数是一等公民,可以作为参数,变量,返回值。 可实现高阶函数和闭包等功能

闭包实现求和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func adder() func(int) int {
sum := 0
return func(val int) int {
sum += val
return sum
}
}

type iAdder func(int) (int, iAdder)

func adder2(base int) iAdder {
return func(val int) (int, iAdder) {
return base + val, adder2(base + val)
}
}

func main() {
add := adder()
for i := 1; i < 10; i++ {
fmt.Printf("0 - %d 的和为:%d \n", i, add(i))
}
}

菲波那切数列

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
func fabnqie() intGen {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}

type intGen func() int

func (g intGen) Read(p []byte) (n int, err error) {
next := g()
if next > 10 {
return 0, io.EOF
}
s := fmt.Sprintf("%d\n", next)
return strings.NewReader(s).Read(p)
}

func printContent(reader io.Reader) {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}

func main(){
f := fabnqie()
printContent(f)
}

方法表达式

instance.method(args…) —> .func(instance, args…)

前者称为 method value,后者 method expression。
两者都可像普通函数那样赋值和传参,区别在于 method value 绑定实例,而 method expression 则须显式传参。

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
type User struct {
id int
name string
}

func (self *User) Test() {
fmt.Printf("%p, %v\n", self, self)
}

func main() {
u := User{1, "Tom"}
u.Test() // 0xc42000a060, &{1 Tom}

mValue := u.Test
// 隐式传递 receiver
mValue() // 0xc42000a060, &{1 Tom}

mExpression := (*User).Test
// 显式传递 receiver
mExpression(&u) // 0xc42000a060, &{1 Tom}


u2 := User{1, "Tom"}
m2Value := u.Test // 立即复制 receiver,因为不是指针类型,不受后续修改影响。

u2.id, u2.name = 2, "Jack"
u2.Test() // {2 Jack}

m2Value() // {1 Tom}
}

在汇编层面,method value 和闭包的实现方式相同,实际返回 FuncVal 类型对象。

学习两种赋值方式

1
2
3
4
a := (*interface{})(nil) // 类似于 int(16.5)

var c interface{}
c = (*interface{})(nil)

a现在是啥?

(*interface{})(nil) 意思是把nil 类型转换为 *interface{}类型

a相当于 var a *interface{} = nil
a是个指针, 指向了nil, 所以a是nil

c现在是啥 ?

c是 interface{} 类型 , 这个类型有两个属性 , type和data,

c的type属性是*interface{},有值
c的data属性是nil,无值

只有当type和data都是nil时, 空接口才是nil, 所以c不是nil

1
2
3
4
5
a := (*interface{})(nil)
log.Printf("%v\n", a == nil) // true
var c interface{}
c = (*interface{})(nil)
log.Printf("%v\n", c == nil) // false

go的测试

go测试注意事项

  • go的测试文件需要以_test结尾
  • 测试函数名必须以大写Test开头

表格驱动测试

  • 分离测试数据和测试逻辑
  • 明确的出错信息
  • 可以部分失败
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
func add(a, b int) int {
return a + b
}

func TestAdd(t *testing.T) {
var add =func (a, b int) int {
return a + b
}

var tests = []struct {
a, b, c int
}{
{1, 2, 3},
{1, 5, 6},
{1, 8, 9},
}

for _, tt := range tests {
if actual := add(tt.a, tt.b); actual != tt.c {
t.Errorf("add(%d, %d);"+
"got %d; expected %d", tt.a, tt.b, actual, tt.c)
}
}
}

func TestSubstr(t *testing.T) {
var longestStrInString = func(str string) int {
var length int = 0
var start int = 0
lastOcc := make(map[rune]int)
for i, char := range []rune(str) {
if index, ok := lastOcc[char]; ok && index >= start {
start = i
}
if i-start+1 > length {
length = i - start + 1
}
lastOcc[char] = i
}
return length
}

var tests = []struct {
a string
b int
}{
{"pwbds6786868767867wbertyuio", 11},
{"", 0},
{"这里是你最偶偶的地方", 6},
{"一二三二一", 3},
}

for _, tt := range tests {
if actual := longestStrInString(tt.a); actual != tt.b {
t.Errorf("longestStrInString(%s);"+
"got %d; expected %d", tt.a, actual, tt.b)
}
}
}

go测试命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 测试当前目录所有test文件
go test .

# 生成test的测试结果文件
go test -coverprofile=c.out

# 查看cover的可用命令
go tool cover

# 使用html的方式展示覆盖率
go tool cover -html=c.out

# 查看_test.go的内容
less _test.go

go benchmark和pprof

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
// map版本  较短字符串处理速度快
func LongestStrInString(str string) int {
var length int = 0
var start int = 0
lastOcc := make(map[rune]int)
for i, char := range []rune(str) {
if index, ok := lastOcc[char]; ok && index >= start {
start = i
}
if i-start+1 > length {
length = i - start + 1
}
lastOcc[char] = i
}
return length
}


// slice版本的处理 较长字符串处理速度慢
var lastOcc = make([]int, 0xffff)
func LongestStrInStringUpgrade(str string) int {
for i := range lastOcc {
lastOcc[i] = -1
}
var length int = 0
var start int = 0
for i, char := range []rune(str) {
if index := lastOcc[char]; index >= start {
start = i
}
if i-start+1 > length {
length = i - start + 1
}
lastOcc[char] = i
}
return length
}

// benchmark
func BenchmarkLongestStrInString(b *testing.B) {
s := "这里是你最偶偶的地方"
res := 6

b.ResetTimer()
for i := 0; i < b.N; i++ {
actual := LongestStrInString(s)
if actual != res {
b.Errorf("got %d for input %s, expected %d", actual, s, res)
}
}
}
1
2
3
4
5
6
7
8
9
10

# benchmark测试命令
go test -bench .

# 生成性能报告
go test -bench . -cpuprofile cpu.out

# 交互式命令
go tool pprof cpu.out
web #显示运行耗时的程序

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

package main

import (
"errors"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"time"
)

func panicError(writer http.ResponseWriter, request *http.Request) error {
return errors.New("Internal Server Error")
}
func errUserError(writer http.ResponseWriter, request *http.Request) error {
return custom_error{"user error", time.Now(), "position"}
}

func notExistError(writer http.ResponseWriter, request *http.Request) error {
return os.ErrNotExist
}

func forbidError(writer http.ResponseWriter, request *http.Request) error {
return os.ErrPermission
}

func noError(writer http.ResponseWriter, request *http.Request) error {
return nil
}

var tests = []struct {
h appHandler
code int
message string
}{
{panicError, 500, "Internal Server Error"},
{errUserError, 400, "position: user error"},
{notExistError, 404, "Not Found"},
{forbidError, 403, "Forbidden"},
{noError, 200, ""},
}

// 使用假的http请求来测试
func TestErrWrapper(t *testing.T) {
for _, tt := range tests {
f := errWraper(tt.h)
response := httptest.NewRecorder()
request := httptest.NewRequest(
http.MethodGet,
"http://www.baidu.com",
nil,
)
f(response, request)
verifyResponse(response.Result(), tt.code, tt.message, t)
}
}

// 使用真实的http请求来测试
func TestErrWraperInServer(t *testing.T) {
for _, tt := range tests {
f := errWraper(tt.h)
server := httptest.NewServer(http.HandlerFunc(f))
resp, _ := http.Get(server.URL)
verifyResponse(resp, tt.code, tt.message, t)
}
}

func verifyResponse(resp *http.Response, expectedCode int, expectedMsg string, t *testing.T) {
b, _ := ioutil.ReadAll(resp.Body)
body := strings.Trim(string(b), "\n")
if resp.StatusCode != expectedCode || body != expectedMsg {
t.Errorf("expect (%d, %s);"+
"got (%d, %s)",
expectedCode, expectedMsg,
resp.StatusCode, body)
}
}

go的包

封装

  • 包内变量、方法及函数,如果希望暴露给外部,首字母要大写;如果是私有的,使用小写字母开头
  • 每个目录只有一个包
  • main包包含可执行入口(package main 就表示这是一个program,而不是一个package)
  • 为struct定义的方法必须在一个包内,可以在不同的文件内

包的扩展

定义别名, 组合使用

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

// /Tree/Node.go
type TreeNode struct {
value int
left, right *TreeNode
}

func (MyNode TreeNode) print() {
fmt.Println(MyNode.value)
}

// /Tree/MyNode.go
type MyNode struct{
node *TreeNode
}

func (MyNode *MyNode ) RightReversal() {
if MyNode == nil {
fmt.Printf("node does not exist")
return
}
MyNode.node.right.print()
MyNode.node.print()
MyNode.node.left.print()
}

var node = TreeNode{value: 1}
node.setVal(4)
node.left = &TreeNode{value:12}
node.right = &TreeNode{value:7}

var mine_node = &MyNode{&node}
mine_node.RightReversal()


// /Queue
type Slice []interface{}

// 如果出入参不限制类型,可以在写入的时候限制
func (q *Slice) Add(val interface{}) {
*q = append(*q, val.(int))
}

// 虽然入参没有限制类型,但这个slice仍然只能存储int类型
func (q *Slice) Pop() interface{} {
head := (*q)[0]
*q = (*q)[1:]
return head.(int)
}