@@ -9,40 +9,40 @@ goroutine是Go并行设计的核心。goroutine说到底其实就是协程,但
9
9
goroutine是通过Go的runtime管理的一个线程管理器。goroutine通过` go ` 关键字实现了,其实就是一个普通的函数。
10
10
``` Go
11
11
12
- go hello (a, b, c)
12
+ go hello (a, b, c)
13
13
```
14
14
通过关键字go就启动了一个goroutine。我们来看一个例子
15
15
``` Go
16
16
17
- package main
17
+ package main
18
18
19
- import (
20
- " fmt"
21
- " runtime"
22
- )
19
+ import (
20
+ " fmt"
21
+ " runtime"
22
+ )
23
23
24
- func say (s string ) {
25
- for i := 0 ; i < 5 ; i++ {
26
- runtime.Gosched ()
27
- fmt.Println (s)
28
- }
29
- }
30
-
31
- func main () {
32
- go say (" world" ) // 开一个新的Goroutines执行
33
- say (" hello" ) // 当前Goroutines执行
24
+ func say (s string ) {
25
+ for i := 0 ; i < 5 ; i++ {
26
+ runtime.Gosched ()
27
+ fmt.Println (s)
34
28
}
35
-
36
- // 以上程序执行后将输出:
37
- // hello
38
- // world
39
- // hello
40
- // world
41
- // hello
42
- // world
43
- // hello
44
- // world
45
- // hello
29
+ }
30
+
31
+ func main () {
32
+ go say (" world" ) // 开一个新的Goroutines执行
33
+ say (" hello" ) // 当前Goroutines执行
34
+ }
35
+
36
+ // 以上程序执行后将输出:
37
+ // hello
38
+ // world
39
+ // hello
40
+ // world
41
+ // hello
42
+ // world
43
+ // hello
44
+ // world
45
+ // hello
46
46
```
47
47
我们可以看到go关键字很方便的就实现了并发编程。
48
48
上面的多个goroutine运行在同一个进程里面,共享内存数据,不过设计上我们要遵循:不要通过共享来通信,而要通过通信来共享。
@@ -57,95 +57,95 @@ goroutine是通过Go的runtime管理的一个线程管理器。goroutine通过`g
57
57
goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。那么goroutine之间如何进行数据的通信呢,Go提供了一个很好的通信机制channel。channel可以与Unix shell 中的双向管道做类比:可以通过它发送或者接收值。这些值只能是特定的类型:channel类型。定义一个channel时,也需要定义发送到channel的值的类型。注意,必须使用make 创建channel:
58
58
``` Go
59
59
60
- ci := make (chan int )
61
- cs := make (chan string )
62
- cf := make (chan interface {})
60
+ ci := make (chan int )
61
+ cs := make (chan string )
62
+ cf := make (chan interface {})
63
63
```
64
64
channel通过操作符` <- ` 来接收和发送数据
65
65
``` Go
66
66
67
- ch <- v // 发送v到channel ch.
68
- v := <- ch // 从ch中接收数据,并赋值给v
67
+ ch <- v // 发送v到channel ch.
68
+ v := <- ch // 从ch中接收数据,并赋值给v
69
69
```
70
70
我们把这些应用到我们的例子中来:
71
71
``` Go
72
72
73
- package main
73
+ package main
74
74
75
- import " fmt"
75
+ import " fmt"
76
76
77
- func sum (a []int , c chan int ) {
78
- total := 0
79
- for _ , v := range a {
80
- total += v
81
- }
82
- c <- total // send total to c
77
+ func sum (a []int , c chan int ) {
78
+ total := 0
79
+ for _ , v := range a {
80
+ total += v
83
81
}
82
+ c <- total // send total to c
83
+ }
84
84
85
- func main () {
86
- a := []int {7 , 2 , 8 , -9 , 4 , 0 }
85
+ func main () {
86
+ a := []int {7 , 2 , 8 , -9 , 4 , 0 }
87
87
88
- c := make (chan int )
89
- go sum (a[:len (a)/2 ], c)
90
- go sum (a[len (a)/2 :], c)
91
- x , y := <- c, <- c // receive from c
88
+ c := make (chan int )
89
+ go sum (a[:len (a)/2 ], c)
90
+ go sum (a[len (a)/2 :], c)
91
+ x , y := <- c, <- c // receive from c
92
92
93
- fmt.Println (x, y, x + y)
94
- }
93
+ fmt.Println (x, y, x + y)
94
+ }
95
95
```
96
96
默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得Goroutines同步变的更加的简单,而不需要显式的lock。所谓阻塞,也就是如果读取(value := <-ch)它将会被阻塞,直到有数据接收。其次,任何发送(ch<-5)将会被阻塞,直到数据被读出。无缓冲channel是在多个goroutine之间同步很棒的工具。
97
97
98
98
## Buffered Channels
99
99
上面我们介绍了默认的非缓存类型的channel,不过Go也允许指定channel的缓冲大小,很简单,就是channel可以存储多少元素。ch:= make(chan bool, 4),创建了可以存储4个元素的bool 型channel。在这个channel 中,前4个元素可以无阻塞的写入。当写入第5个元素时,代码将会阻塞,直到其他goroutine从channel 中读取一些元素,腾出空间。
100
100
``` Go
101
101
102
- ch := make (chan type , value)
102
+ ch := make (chan type , value)
103
103
```
104
104
当 value = 0 时,channel 是无缓冲阻塞读写的,当value > 0 时,channel 有缓冲、是非阻塞的,直到写满 value 个元素才阻塞写入。
105
105
106
106
我们看一下下面这个例子,你可以在自己本机测试一下,修改相应的value值
107
107
``` Go
108
108
109
- package main
109
+ package main
110
110
111
- import " fmt"
111
+ import " fmt"
112
112
113
- func main () {
114
- c := make (chan int , 2 )// 修改2为1就报错,修改2为3可以正常运行
115
- c <- 1
116
- c <- 2
117
- fmt.Println (<- c)
118
- fmt.Println (<- c)
119
- }
113
+ func main () {
114
+ c := make (chan int , 2 )// 修改2为1就报错,修改2为3可以正常运行
115
+ c <- 1
116
+ c <- 2
117
+ fmt.Println (<- c)
118
+ fmt.Println (<- c)
119
+ }
120
120
// 修改为1报如下的错误:
121
121
// fatal error: all goroutines are asleep - deadlock!
122
122
```
123
123
## Range和Close
124
124
上面这个例子中,我们需要读取两次c,这样不是很方便,Go考虑到了这一点,所以也可以通过range,像操作slice或者map一样操作缓存类型的channel,请看下面的例子
125
125
``` Go
126
126
127
- package main
127
+ package main
128
128
129
- import (
130
- " fmt"
131
- )
129
+ import (
130
+ " fmt"
131
+ )
132
132
133
- func fibonacci (n int , c chan int ) {
134
- x , y := 1 , 1
135
- for i := 0 ; i < n; i++ {
136
- c <- x
137
- x, y = y, x + y
138
- }
139
- close (c)
133
+ func fibonacci (n int , c chan int ) {
134
+ x , y := 1 , 1
135
+ for i := 0 ; i < n; i++ {
136
+ c <- x
137
+ x, y = y, x + y
140
138
}
141
-
142
- func main () {
143
- c := make (chan int , 10 )
144
- go fibonacci (cap (c), c)
145
- for i := range c {
146
- fmt.Println (i)
147
- }
139
+ close (c)
140
+ }
141
+
142
+ func main () {
143
+ c := make (chan int , 10 )
144
+ go fibonacci (cap (c), c)
145
+ for i := range c {
146
+ fmt.Println (i)
148
147
}
148
+ }
149
149
```
150
150
` for i := range c ` 能够不断的读取channel里面的数据,直到该channel被显式的关闭。上面代码我们看到可以显式的关闭channel,生产者通过内置函数` close ` 关闭channel。关闭channel之后就无法再发送任何数据了,在消费方可以通过语法` v, ok := <-ch ` 测试channel是否被关闭。如果ok返回false,那么说明channel已经没有任何数据并且已经被关闭。
151
151
@@ -159,66 +159,66 @@ channel通过操作符`<-`来接收和发送数据
159
159
` select ` 默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个channel都准备好的时候,select是随机的选择一个执行的。
160
160
``` Go
161
161
162
- package main
162
+ package main
163
163
164
- import " fmt"
164
+ import " fmt"
165
165
166
- func fibonacci (c , quit chan int ) {
167
- x , y := 1 , 1
168
- for {
169
- select {
170
- case c <- x:
171
- x, y = y, x + y
172
- case <- quit:
173
- fmt.Println (" quit" )
174
- return
175
- }
166
+ func fibonacci (c , quit chan int ) {
167
+ x , y := 1 , 1
168
+ for {
169
+ select {
170
+ case c <- x:
171
+ x, y = y, x + y
172
+ case <- quit:
173
+ fmt.Println (" quit" )
174
+ return
176
175
}
177
176
}
178
-
179
- func main () {
180
- c := make (chan int )
181
- quit := make (chan int )
182
- go func () {
183
- for i := 0 ; i < 10 ; i++ {
184
- fmt.Println (<- c)
185
- }
186
- quit <- 0
187
- }()
188
- fibonacci (c, quit)
189
- }
177
+ }
178
+
179
+ func main () {
180
+ c := make (chan int )
181
+ quit := make (chan int )
182
+ go func () {
183
+ for i := 0 ; i < 10 ; i++ {
184
+ fmt.Println (<- c)
185
+ }
186
+ quit <- 0
187
+ }()
188
+ fibonacci (c, quit)
189
+ }
190
190
```
191
191
在` select ` 里面还有default语法,` select ` 其实就是类似switch的功能,default就是当监听的channel都没有准备好的时候,默认执行的(select不再阻塞等待channel)。
192
192
``` Go
193
193
194
- select {
195
- case i := <- c:
196
- // use i
197
- default :
198
- // 当c阻塞的时候执行这里
199
- }
194
+ select {
195
+ case i := <- c:
196
+ // use i
197
+ default :
198
+ // 当c阻塞的时候执行这里
199
+ }
200
200
```
201
201
## 超时
202
202
有时候会出现goroutine阻塞的情况,那么我们如何避免整个程序进入阻塞的情况呢?我们可以利用select来设置超时,通过如下的方式实现:
203
203
``` Go
204
204
205
- func main () {
206
- c := make (chan int )
207
- o := make (chan bool )
208
- go func () {
209
- for {
210
- select {
211
- case v := <- c:
212
- println (v)
213
- case <- time.After (5 * time.Second ):
214
- println (" timeout" )
215
- o <- true
216
- break
217
- }
205
+ func main () {
206
+ c := make (chan int )
207
+ o := make (chan bool )
208
+ go func () {
209
+ for {
210
+ select {
211
+ case v := <- c:
212
+ println (v)
213
+ case <- time.After (5 * time.Second ):
214
+ println (" timeout" )
215
+ o <- true
216
+ break
218
217
}
219
- }()
220
- <- o
221
- }
218
+ }
219
+ }()
220
+ <- o
221
+ }
222
222
```
223
223
224
224
## runtime goroutine
0 commit comments