zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    MySQL mysqldump数据导出详解
    FTP上传下载 C#辅助类
    FastDFS java 辅助类
    Ajax 提交表单【包括文件上传】
    bootstrap-table 基础用法
    MVC dropdownlist 后端设置select属性后前端依然不能默认选中的解决方法
    jQuery实现鼠标移到元素上动态提示消息框效果
    给Jquery动态添加的元素添加事件
    centos7部署mysql5.7一主多从
    iOS浏览器 new Date() 返回 NaN
  • 原文地址:https://www.cnblogs.com/fengzaoye/p/9023462.html
Copyright © 2011-2022 走看看