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的运行。

 

后续遇到问题,不断记录................

 

--------EOF---------
本文微信分享/扫码阅读