Go语言并发编程(一)

 Go语言的特色不得不提的就是并发机制,在C语言中编写非常繁琐复杂的并发程序在Go语言中可以非常便捷。 
  这几天写并发测试脚本的时候,结合代码和其他大牛的文章学习了一下。把自己的理解写下来。如有错误,请指正。

一、并发与并行

  Go中并发程序主要通过goroutine和channel来实现。 
  这里我想先解释一下的是“并发”一词,一开始把并发当做了并行,一直觉得代码有问题,其实这两者并不是一回事。 
  并发就是:两个队列,同时依次去访问一个资源。而并行:两个队列,分别依次访问两个资源。 
  简单来说,并发,就像一个人(cpu)喂2个孩子(程序),轮换着每人喂一口,表面上两个孩子都在吃饭。并行,就是2个人喂2个孩子,两个孩子也同时在吃饭。

代码示例

  以前我们调用多个线程分别打印输出有序的数字时,系统的线程会抢占式地输出, 表现出来的是乱序地输出。而多个goroutine并发执行结果是输出一串有序的数字接着一串有序的数字。示例代码:

var quit chan int = make(chan int) func loop() { for i := 0; i < 10; i++ { fmt.Printf("%d ", i) } quit <- 0 } func main() { // 开两个goroutine跑函数loop, loop函数负责打印10个数 go loop() go loop() //保证goroutine都执行完,主线程才结束 for i := 0; i < 2; i++ { <- quit } }

输出结果:

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9

但我们以前用线程实现的结果是乱序的,比如这样的:

0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9

二、goroutine是如何执行的

  实际上,默认Go所有的goroutines只能在一个线程里跑,而一个goroutine并不相当于一个线程,goroutine的出现正是为了替代原来的线程概念成为最小的调度单位。(一旦运行goroutine时,先去当前线程查找,如果线程阻塞了,则被分配到空闲的线程,若无空闲线程,就新建一个线程。注意的是,当goroutine执行完毕后,线程不会被回收,而是成为了空闲的线程。) 
  如果当前goroutine不发生阻塞,它是不会让出CPU给其他goroutine的, 在上面的代码实例中,输出会是一个一个goroutine进行,而如果使用sleep函数,则阻塞掉了当前goroutine, 当前goroutine主动让其他goroutine执行, 所以形成了并发。

代码实例

  使用goroutine想要达到真正的并行的效果也是可以的,解决方案有两个: 
1、允许Go使用多核(runtime.GOMAXPROCS)

var quit chan int = make(chan int) func loop() { for i := 0; i < 100; i++ { //为了观察,跑多些 fmt.Printf("%d ", i) } quit <- 0 } func main() { runtime.GOMAXPROCS(2) // 最多使用2个核 go loop() go loop() for i := 0; i < 2; i++ { <- quit } }

2、手动显式调动(runtime.Gosched)

func loop() {
    for i := 0; i < 10; i++ {
        runtime.Gosched() // 显式地让出CPU时间给其他goroutine fmt.Printf("%d ", i) } quit <- 0 } func main() { go loop() go loop() for i := 0; i < 2; i++ { <- quit } }

执行结果:

0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9

第二种主动让出CPU时间的方式仍然是在单核里跑。但手工地切换goroutine导致了看上去的“并行”。

三、runtime

runtime调度器是个很神奇的东西,但是我但愿它不存在,我希望显式调度能更为自然些,多核处理默认开启。

关于runtime包几个函数: 
Gosched() //让出cpu 
NumCPU()//返回当前系统的CPU核数量 
GOMAXPROCS() //设置最大的可同时使用的CPU核数 
Goexit() //退出当前goroutine(但是defer语句会照常执行)

四、总结

  默认所有goroutine会在一个原生线程里跑,也就是默认只使用一个CPU核。如果当前goroutine不发生阻塞,它是不会让出CPU时间给其他同线程的goroutines的,这是Go运行时对goroutine的调度,我们也可以使用runtime包来手工调度。 
  若我们开启多核,当一个goroutine发生阻塞,Go会自动地把与该goroutine处于同一系统线程的其他goroutines转移到另一个系统线程上去,以使这些goroutines不阻塞。从而实现并行效果。

参考自: https://studygolang.com/articles/5463

猜你喜欢

转载自www.cnblogs.com/fengzaoye/p/9023462.html