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

  • 相关阅读:
    MDX Step by Step 读书笔记(六) Building Complex Sets (复杂集合的处理) Filtering Sets
    在 Visual Studio 2012 开发 SSIS,SSAS,SSRS BI 项目
    微软BI 之SSIS 系列 在 SSIS 中读取 SharePoint List
    MDX Step by Step 读书笔记(五) Working with Expressions (MDX 表达式) Infinite Recursion 和 SOLVE_ORDER 原理解析
    MDX Step by Step 读书笔记(五) Working with Expressions (MDX 表达式)
    使用 SQL Server 2012 Analysis Services Tabular Mode 表格建模 图文教程
    MDX Step by Step 读书笔记(四) Working with Sets (使用集合) Limiting Set and AutoExists
    SQL Server 2012 Analysis Services Tabular Model 读书笔记
    Microsoft SQL Server 2008 MDX Step by Step 学习笔记连载目录
    2011新的开始,介绍一下AgileEAS.NET平台在新的一年中的发展方向
  • 原文地址:https://www.cnblogs.com/fengzaoye/p/9023462.html
Copyright © 2011-2022 走看看