Go语言并发编程(一)

本文介绍了Go语言的并发机制,通过goroutine和channel实现并发。并发并不等于并行,并发是多个任务交替执行,而并行是多个任务同时执行。goroutine作为轻量级线程,可以在单线程中实现并发效果,通过`runtime.GOMAXPROCS`或`runtime.Gosched`可以控制和调度goroutine。文章提供了代码示例,解析了goroutine的执行原理,以及如何实现真正的并行执行。
摘要由CSDN通过智能技术生成

  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不阻塞。从而实现并行效果。


参考文档: Go语言并发与并行学习笔记(二)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值