Go语言并发编程(一)

swallowing_ · · 2848 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

  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语言并发与并行学习笔记(二)


有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:swallowing_

查看原文:Go语言并发编程(一)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2848 次点击  
加入收藏 微博
1 回复  |  直到 2000-01-01 00:00:00
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传