上篇:GO——学习笔记(八)
下篇:GO——学习笔记(十)
参考:
https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/02.7.md
示例代码——go_8
https://github.com/jiutianbian/golang-learning/blob/master/go_8/main.go
并发
goroutine
大多数语言实现并发,通常通过多线程来实现,golang也类似,但有所不同,具体如下:goroutine是Go并行设计的核心,goroutine是协程,他的粒度要比线程小,可以认为是轻量级的线程,具体什么是协程,请看此网址 https://www.zhihu.com/question/20862617,
执行goroutine只需极少的栈内存,可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。
goroutine 中通过 go 关键字来实现,代码如下
func testgo() {
go sayhelloMan() //通过go来调用协程执行方法
go sayhelloWomen()
time.Sleep(time.Millisecond) //由于goroutine是协程,所以这边如果不加主线程等待时间,main函数会直接结束,不会等待goroutine执行完毕
fmt.Println("结束")
}
func sayhelloMan() {
for i := 0; i < 5; i++ {
fmt.Println("你好,大兄弟")
time.Sleep(10)
}
}
func sayhelloWomen() {
for i := 0; i < 5; i++ {
fmt.Println("你好,大妹子")
time.Sleep(10)
}
}
请注意time.Sleep(time.Millisecond),这段代码,如果不加入这段线程阻塞的方法,那么main函数将会很快执行完便退出了,新开的协程将不会继续执行。
channel
golang中协程间的通信,通过channel来实现,通过make创建出特定的channel类型的值,来发送和接收数据,channel通过操作符<-来接收和发送数据,代码如下:
func send(c chan int, i int) {
fmt.Println("发送消息:", i)
c <- i //将值塞到channle中,如果recive方法没有执行,send方法所在线程将阻塞
}
func recive(c chan int) {
i := <-c
fmt.Println("接收消息:", i)//将值塞从channle中取出,如果send方法没有执行,recive所在线程将阻塞
}
func testchannel() {
ch := make(chan int) //定义一个channle,用来接收传递的值
go send(ch, 10)
recive(ch)
}
我们上面代码中定义的channle,是无缓冲的channel,无缓冲的channle中数据的存取都是互斥的。拿上面的例子来说,通过send方法,往ch中存入了一个值,如果recive方法没有执行从ch取出值的操作,send所在线程将阻塞,知道recive方法调用。且recive方法,从ch取出值的时候,只有当send方法,往ch中存入了一个值的时候,才能执行,否则会一直等待。
当然,如果对无缓冲的channel只有存取值的操作的其中一个的话,都将会出现死锁,报错 fatal error: all goroutines are asleep - deadlock!
func testchannel() {
ch := make(chan int) //定义一个channle,用来接收传递的值
//go send(ch, 10) //如果注释此行或者下面一行中的其中一行,都将会出现死锁报错 fatal error: all goroutines are asleep - deadlock!
recive(ch)
}
buffered channel
上面的例子中都是定义的无缓冲的channle,现在定义有缓冲的channle,定义格式如下
ch := make(chan type, value)
其实,无缓冲的channle,是value = 0,当value>1时,就是有缓冲的channle。当读写有缓冲的channle时,直到写满value个元素才阻塞写入。
func testbufferchannel() {
//ch := make(chan int)
bufferchannel := make(chan int, 1) //定义缓冲为1的channel
bufferchannel <- 1 //可以直接往缓冲的channel中存值,只要小于1
//bufferchannel <- 2 //大于1,会报死锁的错误
//ch <- 1 //对于无缓冲的channle直接赋值,会报死锁的错误
fmt.Println(<-bufferchannel)
}
Range和Close
可以通过range像操作slice或者map一样操作缓存类型的channel
func testrangeandclose() {
bufferchannel_1 := make(chan int, 10)
go setNumber(cap(bufferchannel_1), bufferchannel_1)
for i := range bufferchannel_1 { //通过range取缓冲的channel中的值
fmt.Println(i)
}
}
func setNumber(n int, c chan int) {
for i := 0; i < n; i++ {
c <- i
}
close(c) //通过close关闭channel
}
Select
如果存在多个channel的时候,我们可以通过select监听channel上的数据流动。
select默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个channel都准备好的时候,select是随机的选择一个执行的。
func testselect() {
ch := make(chan int) //定义一个channle,用来接收传递的值
done := make(chan int) //用来传递完毕的标识
go speaknumber(ch, done)
for {
select {
case i := <-ch: //当ch准备好值时
fmt.Println("speack:", i)
case <-done: //当done准备好值时
fmt.Println("speack:done")
return
}
}
}
func speaknumber(ch chan int, done chan int) {
for i := 0; i < 10; i++ {
ch <- i
}
done <- 0
}
在select里面还有default语法,select其实就是类似switch的功能,default就是当监听的channel都没有准备好的时候,默认执行的(select不再阻塞等待channel)。
select {
case i := <-c:
// use i
default:
// 当c阻塞的时候执行这里
}
超时
对于上面描述过阻塞超时的情况,我们可以通过select来设置超时来避免.
func testovertime() {
ch := make(chan int) //定义一个channle,用来接收传递的值
go saynumber(ch)
for {
select {
case i := <-ch:
fmt.Println("say:", i)
case <-time.After(1 * time.Second):
fmt.Println("say:timeout")
return
}
}
}
func saynumber(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i
}
}