go 当中的并发编程是通过goroutine来实现的,利用channel(管道)可以在协程之间传递数据,实现协程的协调与同步。
使用
新建一个管道,使用make channel 来构建
1
2
3
4
5
6
7
8
|
// 构建一个缓存长度为8 的管道
ch := make(chan int ,8)
// 写入
ch <- 10
// 取出
number := <-ch
// 关闭
close(ch)
|
注意??: 取数据的时候,如果没得取,会阻塞代码的执行,如果一直没有取到,那就是死锁
实现生产者消费者模式
两个生产者者协程和一个消费者协程
使用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
42
43
44
45
46
|
func main() {
ch := make(chan int, 100)
wg := sync.WaitGroup{}
wg.Add(2)
// 生产者
go func() {
defer wg.Done()
// 写入数据
for i := 0; i < 10; i++ {
ch <- i
}
}()
// 生产者
go func() {
defer wg.Done()
// 写入数据
for i := 0; i < 10; i++ {
ch <- i
}
}()
wg2 := sync.WaitGroup{}
wg2.Add(1)
// 消费者
go func() {
sum := 0
fmt.Printf("sum %d \n", sum)
for {
// 这里会等待
temp, ok := <-ch
// close 并且 管道为空,ok = false
if !ok {
break
} else {
sum += temp
}
}
fmt.Printf("sum %d \n", sum)
wg2.Done()
}()
// 等待俩生产者结束
wg.Wait()
// 生产数据之后,消费者也并行读完了,此时可以关闭 管道 来 跳出for循环了
close(ch)
// 等待消费者协程结束
wg2.Wait()
}
|
使用管道则将wg2相关的代码改掉
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() {
//...
//...
ch2 := make(chan struct{}, 0)
go func() {
sum := 0
fmt.Printf("sum %d \n", sum)
for {
// 这里会等待
temp, ok := <- ch
// close 并且 管道为空,ok = false
if !ok {
break
} else {
sum += temp
}
}
fmt.Printf("sum %d \n", sum)
ch2 <- struct{}{}
}()
// 等待俩生产者结束
wg.Wait()
// 关闭管道
close(ch)
// 等待消费者协程结束
<-ch2
}
|
实战面试题: 「交替打印数字和字母」
题目
使用两个 goroutine 交替打印序列,一个 goroutine 打印数字, 另外一个 goroutine 打印字母, 最终效果如下:
12AB34CD56EF78GH910IJ1112KL1314MN1516OP1718QR1920ST2122UV2324WX2526YZ2728
解题思路
利用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
|
func main() {
letter, number := make(chan bool), make(chan bool)
wait := sync.WaitGroup{}
go func() {
i := 1
for {
if <-number {
fmt.Print(i)
i++
fmt.Print(i)
i++
letter <- true
}
}
}()
wait.Add(1)
go func() {
// 获得ASCII码
i := 'A'
for {
if <-letter {
// 当前已经超过Z时,无需再打印
if i > 'Z' {
// 停止等待,并且跳出循环
wait.Done()
break
}
// 将ASCII码强转成字母输出
fmt.Print(string(i))
i++
fmt.Print(string(i))
i++
number <- true
}
}
}()
// 放行数字打印的阻塞
number <- true
// 等待关闭主线程
wait.Wait()
}
|
其实完全也可以将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
|
func main() {
letter, number := make(chan bool), make(chan bool)
// 再定义一个管道来等待,替代waitGroup的作用
wait := make(chan bool)
// 打印数字
go func() {
i := 1
for {
if <-number {
fmt.Print(i)
i++
fmt.Print(i)
i++
letter <- true
}
}
}()
// 打印字母
go func() {
// 获得ASCII码
i := 'A'
for {
if <-letter {
if i > 'Z' {
wait <- true
break
}
// 将ASCII码强转成字母输出
fmt.Print(string(i))
i++
fmt.Print(string(i))
i++
number <- true
}
}
}()
number <- true
// 等待管道取值
<- wait
}
|
|