iota

可以按照特定的规则执行逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

const (
b = 1 << (10 * iota)
kb
mb
gb
tb
pb
)

func main(){
// 1 1024 1048576 1099511627776 1125899906842624
fmt.Println(b,kb,mb,tb,pb)
}

go中的slice本身没有数据,是对底层array的view。

闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func adder() func (value int) int {
sum:=0
return func (value int)int {
sum += value
return sum
}
}

func main() {
adder:=adder()
for i := 0; i < 10; i++ {
// 0 1 3 6 10 15 21 28 36 45
fmt.Println(adder(i))
}
}

以上的代码使用闭包保存了状态(有一个自由变量),正宗的函数式编程长这样:

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

import "fmt"

type iAddr func (int) (int,iAddr)

func adder(base int) iAddr {
return func (v int) (int,iAddr) {
return base + v,adder(base + v)
}
}

func main() {
add:=adder(0)
for i := 0; i < 10; i++ {
// 0 1 3 6 10 15 21 28 36 45
var s int
s,add = add(i)
fmt.Println(s)
}
}

常用的闭包例子:

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
// 斐波那契生成器
func fib() func() int {
a,b := 0,1
return func () int {
a,b = b,a + b
return a
}
}

// 函数实现接口

package main

import (
"bufio"
"fmt"
"io"
"strings"
)

type intGen func() int

func fibonacci() intGen {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}

// 函数实现read接口
func (g intGen) Read(p []byte) (n int, err error) {
next := g()
if next > 10000 {
return 0, io.EOF
}
s := fmt.Sprintf("%d\n", next)

// TODO: incorrect if p is too small!
return strings.NewReader(s).Read(p)
}

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

func main() {
var f intGen = fibonacci()
printFileContents(f)
}

资源管理和错误处理

defer调用可以确保在函数结束的时候被调用,它是一个栈。

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

import "fmt"

func main() {
for i := 0; i < 10; i++ {
defer fmt.Println(i)
if (i == 5) {
panic("print too many")
}
}
}

// $ go run main.go
// 5
// 4
// 3
// 2
// 1
// 0
// panic: print too many

recover可以在defer中调用,可以接收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
46
47
package main

import (
"fmt"
// "errors"
)

func tryRecover(){
defer func(){
r:= recover()
// type assert
if err, ok := r.(error);ok {
fmt.Println("Error occured:",err)
} else {
panic(fmt.Sprintf("I dont't know what to do: %v", r))
}
}()

// 输出:Error occured: this is an error
// panic(errors.New("this is an error"))

// 输出:Error occured: runtime error: integer divide by zero
// a:=5
// b:=0
// fmt.Println(a / b)

// 输出:
// panic: 123 [recovered]
// panic: I dont't know what to do: 123

// goroutine 1 [running]:
// main.tryRecover.func1()
// C:/Users/Administrator/Desktop/blog-source/source/main.go:15 +0x105
// panic(0x4b0a20, 0x4ea298)
// c:/go/src/runtime/panic.go:969 +0x176
// main.tryRecover()
// C:/Users/Administrator/Desktop/blog-source/source/main.go:27 +0x65
// main.main()
// C:/Users/Administrator/Desktop/blog-source/source/main.go:31 +0x27
// exit status 2
// re panic : 因为123不是error类型
panic(123)
}

func main() {
tryRecover()
}

测试

go采用表格驱动测试:

image.png

分离了测试数据和测试逻辑。虽然其他语言也能做,但是go语言的语法使得表格测试更容易实现。因为表格实现起来比较容易。

goroutine & channel

io操作会切换协程,纯CPU操作不会进行协程的切换,这意味着如果一个协程不主动交出控制权的话就会一直占用CPU资源:

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
var a[100000] int
for i := 0; i < 100000; i++ {
go func(i int) {
for {
a[i]++
// runtime.Gosched()
}
}(i)
}
time.Sleep(time.Millisecond)
fmt.Println(a)
}

执行上面的代码后cpu会跑满,可以使用runtime.Gosched()可以手动交出控制权。

channel是goroutine和goroutine的交互,发了数据没人收的话是会死锁的!

1
2
3
4
5
6
7
8
9
10
11
12
func main()  {
c:=make(chan int)

// 将1,2送入channel
c <- 1
c <- 2

// 从channel中读取
n:= <-c
fmt.Println(n)
}
// 报错:fatal error: all goroutines are asleep - deadlock!

正确的channel使用姿势应该长下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main()  {
c:=make(chan int)

// 注意:这一段代码要放在向channel中输入数据之前
go func () {
for{
n:= <-c
// 可能只打印 1,2,3,4中的前几个,因为可能函数调用已经完成
fmt.Println(n)
}
}()

// 将1,2送入channel
c <- 1
c <- 2
c <- 3
c <- 4
}

channel具有方向性,即可以使用<-或者->定义一个只能输入或者输出的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
func createWorker(id int) chan <- int {
c := make(chan int)
go func () {
for n:= range(c){
fmt.Printf("Worker %d received %c \n",id, n)
}
}()
return c
}

func main() {
var channels [5]chan<- int
for i := 0; i < 5; i++ {
channels[i] = createWorker(i)
}
for i := 0; i < 5; i++ {
// createWorker 方法创建的channel具有方向性,只能向channel中发数据,如果试图从channel中读取数据将会报错
// n := <- channels[i]
channels[i] <- 'a' + i
}
for i := 0; i < 5; i++ {
channels[i] <- 'A' + i
}
time.Sleep(time.Millisecond)
}

// Worker 0 received a
// Worker 0 received A
// Worker 2 received c
// Worker 3 received d
// Worker 1 received b
// Worker 4 received e
// Worker 1 received B
// Worker 3 received D

make的第二个参数可以创建创建channel的缓冲区,对提升性能有帮助。

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

上面的例子中由于我们开启了5个goroutine来并发执行,为了让goroutine有机会执行,最后进行了1ms的延迟,这样其实是非常不好的!因为我们不能确定程序能在1ms内全部处理完。对上面的代码稍加改造:

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

func doWork(id int,c chan int,done chan bool) {
for n:= range(c){
fmt.Printf("Worker %d received %c \n",id, n)
done <- true
}
}

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

func main() {
var workers [5]worker
for i := 0; i < 5; i++ {
workers[i] = createWorker(i)
}
// 1个1个等
for i,worker := range(workers) {
worker.in <- 'a' + i
<-worker.done
}
for i,worker := range(workers) {
worker.in <- 'A' + i
<-worker.done
}
}

上面的代码会输出以下结果:

1
2
3
4
5
6
7
8
9
10
Worker 0 received a
Worker 1 received b
Worker 2 received c
Worker 3 received d
Worker 4 received e
Worker 0 received A
Worker 1 received B
Worker 2 received C
Worker 3 received D
Worker 4 received E

按顺序从小写字母打印到大写字母,程序是顺序执行。有没有什么办法提高程序并行度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func main()  {
var workers [5]worker
for i := 0; i < 5; i++ {
workers[i] = createWorker(i)
}
// 先并发打印小写
for i,worker := range(workers) {
worker.in <- 'a' + i
}
for _,worker := range(workers) {
<-worker.done
}
// 再并发打印大写
for i,worker := range(workers) {
worker.in <- 'A' + i
}
for _,worker := range(workers) {
<-worker.done
}
}

上面的代码提高了并发度,小写字母并发打印,大写字母并发打印,这样一般来说也满足要求了,有没有方法所有任务一起并发?很自然会写出下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main()  {
var workers [5]worker
for i := 0; i < 5; i++ {
workers[i] = createWorker(i)
}
for i,worker := range(workers) {
worker.in <- 'a' + i
}
for i,worker := range(workers) {
worker.in <- 'A' + i
}
for _,worker := range(workers) {
<-worker.done
<-worker.done
}
}

运行代码,我们会发现并发打印完小写字母后出现了死锁的报错:

1
2
3
4
5
6
Worker 4 received e
Worker 0 received a
Worker 3 received d
Worker 1 received b
Worker 2 received c
fatal error: all goroutines are asleep - deadlock!

比较容易的解决方法是改造doWorker,将向外输出done放在一个goroutine中:

1
2
3
4
5
6
func doWork(id int,c chan int,done chan bool) {
for n:= range(c){
fmt.Printf("Worker %d received %c \n",id, n)
go func () { done <- true } ()
}
}

其实对于等待多任务,golang本身提供了系统库级别的支持sync.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
type worker struct{
in chan int
wg *sync.WaitGroup
}

func doWork(id int,c chan int,wg *sync.WaitGroup) {
for n:= range(c){
fmt.Printf("Worker %d received %c \n",id, n)
wg.Done()
}
}

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

func main() {
var workers [5]worker
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
workers[i] = createWorker(i,&wg)
}
wg.Add(10)
for i,worker := range(workers) {
worker.in <- 'a' + i
}
for i,worker := range(workers) {
worker.in <- 'A' + i
}
wg.Wait()
}

使用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
29
30
31
32
33
34
35
func generator() chan int {
out:=make(chan int)
go func () {
i:=0
for {
time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)
out <- i
i++
}
}()
return out
}

func main() {
var c1,c2 = generator(),generator()
tm := time.After(5 * time.Second)
tick := time.Tick(time.Second)
for {
select {
case n := <- c1:
fmt.Println("receive from c1:",n)
case n := <- c2:
fmt.Println("receive from c2:",n)
case <- time.After(500 * time.Millisecond):
// 等待数据太慢
fmt.Println("timeout")
case <- tick:
fmt.Println("this will print every seconds")
case <- tm:
// 计时器终止后退出程序
fmt.Println("bye")
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
receive from c2: 0
receive from c1: 0
receive from c1: 1
receive from c2: 1
this will print every seconds
receive from c2: 2
timeout
receive from c1: 2
receive from c2: 3
this will print every seconds
timeout
receive from c1: 3
this will print every seconds
receive from c1: 4
receive from c1: 5
receive from c2: 4
receive from c2: 5
receive from c2: 6
receive from c1: 6
this will print every seconds
timeout
receive from c2: 7
this will print every seconds
bye