go的横空出世,让很多人眼前一亮,它的语法以简洁著称,并且它对多核并发的原生支持,让他在云计算和分布式领域展露头脚,它的核心围绕channel和goroutine展开。
首先golang channel 分为有缓冲与无缓冲两种类型,很多人认为无缓冲channel单单只是 默认缓冲为1缓冲的channel,其实它们最大的区别是阻塞问题。如下
c1:=make(chan int) 无缓冲
c2:=make(chan int,1) 有缓冲
c1<-1
在c1<- 1处,因为c1是无缓冲的,因此程序会阻塞在这里,不会往下执行,但是c2则不会,他在channel满之前是不会阻塞的,他会继续往下执行,但是如果你在c2第一个数据未取出之前继续塞第二个数据,就会阻塞在你放第二个数据处,因为她的缓冲区单位只有1.例:
dgucochann := make(chan int,2)
dgucochann <- 1
fmt.Println("Hello DGuco")
dgucochann <- 2
fmt.Println("Hello Yuki")
dgucochann <- 3
fmt.Println("Hello Go")
在这种情况下,程序在第6行处会阻塞,会打印前两句话Hello DGuco和Hello Yuki,因为该channel只有两个单位的缓冲,在第6行时没有另外一个goroutine去取出里面的数据,所以阻塞在这里。
下面我们来介绍一下channel的基本用法。
1 信号量的传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 |
package
main
import
(
"fmt"
"time"
)
var
mychan
=
make
(
chan
int
)
func
dgucofunc1
()
{
fmt
.
Println
(
"In dguco_func1"
)
mychan
<-
1
}
func
dgucofunc2
()
{
a
:=
<-
mychan
fmt
.
Println
(
"In dguco_func1"
)
}
func
main
()
{
go
dgucofunc1
()
go
dgucofunc2
()
time
.
Sleep
(
time
.
Millisecond
*
10
)
}
|
可以看到依次打出了相应的两段文字,如果把第6行去掉,因为在dgucofunc2收不到来自channel的信号就会阻塞,结果只会打印In dguco_func1
2 生产者消费者问题
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 |
package
main
import
(
"fmt"
"time"
)
func
dgucoproducer
(
c
chan
int
,
max
int
)
{
for
i
:=
0
;
i
<
max
;
i
++
{
c
<-
i
}
}
func
dgucoconsumer
(
c
chan
int
)
{
ok
:=
true
for
ok
{
if
value
,
ok
:=
<-
c
;
ok
{
fmt
.
Println
(
value
)
}
}
}
func
main
()
{
c
:=
make
(
chan
int
)
defer
close
(
c
)
go
dgucoproducer
(
c
,
10
)
go
dgucoconsumer
(
c
)
time
.
Sleep
(
time
.
Millisecond
*
10
)
}
|
这个很简单,执行结果依次打印0-9
3 定时器(这个经常用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 |
package
main
import
"time"
import
"fmt"
var
mychannel
=
make
(
chan
bool
)
func
dgucotimertask
()
{
fmt
.
Println
(
"My TimerTask.."
)
}
func
dgucotimer
()
{
//返回一个定时器
mytimer
:=
time
.
NewTicker
(
time
.
Millisecond
*
1000
)
select
{
case
<-
mytimer
.
C
:
go
dgucotimertask
()
}
}
func
main
()
{
dgucotimer
()
time
.
Sleep
(
time
.
Millisecond
*
10
)
}
|
顺便说一下select的作用,首先它就相当于switch,但是他是用于channel的,举个例子:
select{
case <- c1:
.........
case <- c2:
........:
}
当程序执行到这里时,程序会阻塞在此,直到c1或者c2中有数据被放入就会继续执行,当然一般不会这样写,因为如果c1,和c2永远收不到数据程序阻塞在这里肯定不是我们想要的结果,一般我们都会写一个超时处理。
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 |
package
main
import
"time"
import
"fmt"
var
mychannel
=
make
(
chan
bool
)
func
dgucotimertask
()
{
fmt
.
Println
(
"My TimerTask.."
)
}
func
dgucotimer
()
{
timeout
:=
time
.
NewTicker
(
time
.
Millisecond
*
10000
)
select
{
case
<-
mychannel
:
go
dgucotimertask
()
case
<-
timeout
.
C
:
fmt
.
Println
(
"Time out"
)
}
fmt
.
Println
(
"Hello go"
)
}
func
main
()
{
dgucotimer
()
time
.
Sleep
(
time
.
Millisecond
*
10
)
}
|
这里我们是没有向mychannel中写数据的,但是等到10秒之后会打印出hello go,程序并没有因此阻塞在这里。
4 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 |
package
main
import
"time"
import
"fmt"
var
mychannel
=
make
(
chan
int
)
func
dgucoroutine
()
{
for
i
:=
0
;
i
<
10
;
i
++
{
mychannel
<-
i
}
}
func
dgucoroutine1
()
{
for
{
i
:=
<-
mychannel
fmt
.
Println
(
"dgucoroutine1:"
,
i
)
}
}
func
dgucoroutine2
()
{
for
{
i
:=
<-
mychannel
fmt
.
Println
(
"dgucoroutine2:"
,
i
)
}
}
func
main
()
{
go
dgucoroutine
()
go
dgucoroutine1
()
go
dgucoroutine2
()
// mychannel <- 4
time
.
Sleep
(
time
.
Millisecond
*
100
)
}
|
这里实现了一个goroutine写,两个goroutine读的过程,可以看到按顺序写进去的数据,被哪个goroutine读到是完全随机的,在golang中我们要实现进程间通信,channel是唯一途径,也是推荐的途径,她的底层是通过共享内存实现的,速度非常快,这样我们摆脱了linux系统繁琐,古老的进程间通信方式,管道,消息队列,信号量等,非常的方便。
最后做一个总结,在使用channel的时候,一定要至少有一个goroutine来负责读(除非你有特殊的需求),否则你的程序就会阻塞在你写channel的地方。