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)