Go的高并发
Go的并发比较一用,通过创建多个goroutine来实现,每一个goroutine都是一个微线程,也是我们经常说的协程。在Python中,yield来创建协程;在Go中,直接使用go就可以。非常方便,例如:
go func()........
关于Go的并发有很多的介绍,我就说几点我觉得应该注意的吧。
1、我们在程序中创建多个goroutine,会有一个主goroutine以及其他在该主goroutine中创建的goroutine。如果在主线程不阻塞,那么其他goroutine不会执行。如下面的例子:
func main() {
go func() {
for i:=0;i<10;i++ {
fmt.Println(i)
}
}()
fmt.Println("main goroutine ends")
}
输出:main goroutine ends
要想让其执行必须阻塞主goroutine。方法有两种,一是使用无缓冲的channel,二是使用sync.WaitGroup。
func main() {
c := make(chan int)
go func() {
for i:=0;i<10;i++ {
fmt.Println("put:",i)
c <- i
}
}()
for i:=0;i<10;i++ {
a := <- c
fmt.Println("get:",a)
}
fmt.Println("main goroutine ends")
}
<-c,管道没有数据,就会阻塞,go func创建的goroutine执行。
说完channel,再说另一种方法,sync.WaitGroup(),源代码对于该结构体给出了说明:
A WaitGroup waits for a collection of goroutines to finish
该结构体定义了三种方法,Add,Done,Wait。它维护了一个计数器,Add计数器加1,Done执行后减1。Wait会让主goroutine阻塞等待,知道计数器为0.如下:
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
//wg.Add(1)
for i:=0;i<10;i++{
wg.Add(1)
go func(j int) {
defer wg.Done()
fmt.Println("I'm:",j)
}(i)
}
//wg.Done()
wg.Wait()
fmt.Println("ok,main goroutine ends!")
}
有的时候,我们还需要实现一种场景:就是主动去停止一些goroutine,防止goroutine泄露。我们首先想到的是可以使用channel。在一个goroutine使用select,例子:
func main() {
fmt.Println("the program mainly display select and channel")
quit := make(chan bool)
go func() {
for {
select {
case <- quit:
//如果quit这个channel中有值了,那么意味着我要退出了。
fmt.Println("the sub goroutine will ends now!")
return
default:
fmt.Println("the sub goroutine executes in:",time.Now().Format("2006-01-02 15:04:05"))
}
}
}()
time.Sleep(100*time.Millisecond)
//main goroutine此时向quit这个channel发送了true。
quit <- true
fmt.Println("ok,the main goroutine ends")
}
上述方式比较优雅地解决了主动结束goroutine的问题。那它也同样存在着弊端。假如现在有很多很多的goroutine,那你需要的channel就会很多,本身就产生了复杂性,所以还需要有其他的解决方案。Go的context就是为了解决这种问题的。
好文章:Go context。
2、利用多核CPU
虽然可以创建多个goroutine,但默认情况下,调度器只会在一个线程上创建goroutine,不能充分利用多核的优势,如果当前的goroutine不发生阻塞,那它肯定不会让出CPU,即同一时刻仍然是只有一个运行。所以,我们要充分利用多核的优势,在Go中通过runtime的 GOMAXPROCS
来设置即可,传入的参数是CPU核数。
runtime.GOMAXPROCS(2)
我们设定了这项之后,那么当前线程的goroutine发生阻塞之后,runtime就会把当前线程其他的goroutine迁移到别的系统上的线程上。那到这的话,就感觉的Python中的协程还是不同,Python中协程是跑在单一的线程上。
3、并发永远躲避不了共享变量带来的安全问题。在Go中同样有同步锁。对于数据可以使用sync.Mutex加锁和解锁。
4、上面说到了阻塞会切换,但其实我们也可以主动让出,切换到其他的goroutine上,这也是异步的特点啊。可以使用runtime.Gosched函数,它就是暂停当前的goroutine的运行。
后续遇到问题,不断记录................
微信分享/微信扫码阅读