Go协程与协程池
Posted 沈子恒
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go协程与协程池相关的知识,希望对你有一定的参考价值。
1. Golang协程
- golang和其它语言最大区别莫过于goroutine,也就是go的协程,example如下:
package main
import "fmt"
import "time"
func go_worker(name string)
for i:=0; i<10; i++
fmt.Println("this is go worker :" , name)
func main()
go go_worker("lineshen")
go go_worker("glorialu")
time.Sleep(time.Second*5) // print effective
fmt.Println("main finished")
输出:
this is go worker : lineshen
this is go worker : lineshen
this is go worker : lineshen
this is go worker : lineshen
this is go worker : lineshen
this is go worker : lineshen
this is go worker : lineshen
this is go worker : lineshen
this is go worker : lineshen
this is go worker : lineshen
this is go worker : glorialu
this is go worker : glorialu
this is go worker : glorialu
this is go worker : glorialu
this is go worker : glorialu
this is go worker : glorialu
this is go worker : glorialu
this is go worker : glorialu
this is go worker : glorialu
this is go worker : glorialu
main finished
如果不用协程,上面代码应该三个线程运行,分别是主函数main和 两个函数。 使用协程之后,实际运行两个线程,这就是并发的好处。
Note:当使用go启动协程之后,这2个函数就被切换到协程里面执行了,但是这时候主线程结束了,这2个协程还没来得及执行就会挂了!所以不在main中加time进行sleep就无法看到协程中的执行结果。
- 多次运行,其结果可能如下:
this is go worker : glorialu
this is go worker : glorialu
this is go worker : glorialu
this is go worker : glorialu
this is go worker : glorialu
this is go worker : glorialu
this is go worker : glorialu
this is go worker : glorialu
this is go worker : glorialu
this is go worker : glorialu
this is go worker : lineshen
this is go worker : lineshen
this is go worker : lineshen
this is go worker : lineshen
this is go worker : lineshen
this is go worker : lineshen
this is go worker : lineshen
this is go worker : lineshen
this is go worker : lineshen
this is go worker : lineshen
main finished
会发现lineshen和 glorialu 的顺序不是固定的,这进一步说明了一个问题,那就是多个协程是同时执行的。不过睡眠这种做法肯定是不靠谱的,go 自带一个WaitGroup可以解决这个问题,。
- WaitGroup示例如下:
package main
import ("fmt"
"time"
"sync")
var wg sync.WaitGroup
func go_worker(name string)
for i:=0; i<10; i++
fmt.Println("this is go worker :" , name)
wg.Done()
func main()
wg.Add(2)
go go_worker("lineshen")
go go_worker("glorialu")
wg.Wait()
// time.Sleep(time.Second*5) // print effective
fmt.Println("main finished")
sync.WaitGroup也起到了time.Sleep的效果。但是协程内部的执行顺序是不确定的。
sync.WaitGroup用法: var 是声明了一个全局变量 wg,类型是sync.WaitGroup,wg.Add(2) 指2个goroutine需要执行,
wg.Done 相当于 wg.Add(-1) 意思就是该协程执行结束;wg.Wait()通信主线程wait,等2个协程都执行完再退出。
应用实例:从3个库取不同的数据汇总处理,最简单写法就是查3次库,但是这3次查询必须按顺序执行,大部分编程语言的代码执行顺序都是从上到下,假如一个查询耗时1s,3个查询就是3s,但是使用协程你可以让这3个查询同时进行,也就是1s就可以搞定。
2. Golang协程池与channel机制
- 同类型channel
如何将协程中结果返回给主线程?golang提供了channel机制。示例如下:
package main
import ("fmt"
"sync")
var wg sync.WaitGroup
func go_worker(name string, ch chan string)
for i:=0; i<10; i++
ch <- name
wg.Done()
func main()
wg.Add(2)
ch := make(chan string)
go go_worker("lineshen", ch)
go go_worker("glorialu", ch)
for
fmt.Println("this is go worker :" , <-ch)
wg.Wait()
fmt.Println("main finished")
代码执行结果如下。就是实例化了一个channel,go启动的协程同时向这个2个管道输出数据,主线程使用了一个for循环从管道里面取数据。其实就是一个生产者-消费者模式,与redis队列有点像。下面也可以看到lineshen和glorialu进入管道的顺序是不固定的。如果实验发现输出顺序固定的,那是因为电脑跑的太快了,可以把循环开的大一点。
this is go worker : glorialu
this is go worker : glorialu
this is go worker : lineshen
this is go worker : glorialu
this is go worker : lineshen
this is go worker : glorialu
this is go worker : lineshen
this is go worker : glorialu
this is go worker : lineshen
this is go worker : glorialu
this is go worker : lineshen
this is go worker : glorialu
this is go worker : lineshen
this is go worker : glorialu
this is go worker : lineshen
this is go worker : glorialu
this is go worker : lineshen
this is go worker : glorialu
this is go worker : lineshen
this is go worker : lineshen
fatal error: all goroutines are asleep - deadlock!
也看到发生了致命的错误:fatal error: all goroutines are asleep - deadlock! 所有的协程都睡眠了,程序监测到死锁!go的channel默认是阻塞的(假如不设置缓存),需要“放一个-取一个”。如果channel里面放置的数据没有取出来,程序就会一直等下去,死锁了;同时,如果channel里面没有放置数据,主线程去取数据也会发生死锁!
如何解决这个问题呢?标准的做法是主动关闭管道,或者知道应该什么时候关闭管道。对于示例代码,可以简单加入一个计数器判断何时channel中的数据可以完全被取出。如下:
package main
import ("fmt"
"sync")
var wg sync.WaitGroup
func go_worker(name string, ch chan string)
for i:=0; i<10; i++
ch <- name
wg.Done()
func main()
wg.Add(2)
ch := make(chan string)
go go_worker("lineshen", ch)
go go_worker("glorialu", ch)
counter := 1
for
fmt.Println("this is go worker :" , <-ch)
if counter >= 20
close(ch)
break
counter++
wg.Wait()
fmt.Println("main finished")
输出如下:
this is go worker : glorialu
this is go worker : glorialu
this is go worker : lineshen
this is go worker : glorialu
this is go worker : lineshen
this is go worker : glorialu
this is go worker : lineshen
this is go worker : glorialu
this is go worker : lineshen
this is go worker : glorialu
this is go worker : lineshen
this is go worker : glorialu
this is go worker : lineshen
this is go worker : glorialu
this is go worker : lineshen
this is go worker : glorialu
this is go worker : lineshen
this is go worker : glorialu
this is go worker : lineshen
this is go worker : lineshen
main finished
- 多种类型channel,使用select进行判断
package main
import ("time"
"fmt")
func go_worker1_int(ch chan int)
for i:=0; ; i++
ch <- i
time.Sleep(time.Duration(time.Second))
func go_worker2_string(name string, ch chan string)
for i:=0; ; i++
ch <- name
time.Sleep(time.Duration(time.Second))
func go_worker3_print(ch1 chan int, ch2 chan string)
chRate := time.Tick(time.Duration(time.Second))
for
select
case v := <-ch1:
fmt.Printf("Received on channel 1: %d\\n", v)
case v := <-ch2:
fmt.Printf("Received on channel 2: %s\\n", v)
case <-chRate:
fmt.Printf("log...\\n")
func main()
ch1 := make(chan int)
ch2 := make(chan string)
go go_worker1_int(ch1)
go go_worker2_string("lineshen", ch2)
go_worker3_print(ch1, ch2)
time.Sleep(time.Duration(time.Second*10))
fmt.Println("main finished")
输出结果如下:
Received on channel 1: 0
Received on channel 2: lineshen
Received on channel 1: 1
Received on channel 2: lineshen
log...
Received on channel 2: lineshen
Received on channel 1: 2
log...
log...
Received on channel 1: 3
Received on channel 2: lineshen
log...
Received on channel 1: 4
Received on channel 2: lineshen
log...
该程序建立了2个管道一个传输int,一个传输string,同时启动了3个协程,前2个协程非常简单,就是每隔1s向管道输出数据,第三个协程是不停的从管道取数据,并通过定时器功能可以每隔一段时间向管道输出内容!
不同的是,go_worker1_int和go_worker2_string是2个不同的管道,通过select可以实现在不同管道之间切换,哪个管道有数据就从哪个管道里面取数据,如果都没数据就等着。
以上是关于Go协程与协程池的主要内容,如果未能解决你的问题,请参考以下文章