[Journey with golang] 5. Concurrent

情到浓时终转凉″ 提交于 2020-01-19 18:20:11

并发和并行是两种不同的概念。

  • 并行意味着程序在任意时刻都是同时运行的
  • 并发意味着程序在单位时间内是同时运行的

并行就是在任一粒度时间内都具备同时执行的能力,最简单的并行就是多机,多台机器并行处理。SMP表面上看是并行的,但由于是共享内存,以及线程间的同步等,不可能完全做到并行。

并发是在规定时间内多个请求都得到执行和处理,强调的是给外界的感觉,实际上内部可能是分时操作的。并发重在避免阻塞,使程序不会因为一个阻塞而停职处理。并发典型的应用场景:分时操作系统就是一种并发设计(忽略多核CPU)。

并行是硬件和操作系统开发者重点考虑的问题,作为应用层的程序员,唯一可以选择的就是充分借助操作系统提供的API和程序语言特性,结合实际需求设计出具有良好并发结构的程序,提升程序的并发处理能力。现代操作系统能够提供的最基础的并发模型就是多线程和多进程;编程语言这一层级可以进一步封装来提升程序的并发处理能力。

在当前的计算机体系下,并行具有瞬时性,并发具有过程性;并发在于结构,并行在于执行。应用程序具备好的并发结构,操作系统才能更好地利用硬件并行执行,同时避免阻塞等待,合理地进行调度,提升CPU利用率。应用层程序员提升程序并发处理能力的一个重要手段就是为程序设计良好的并发结构。

操作系统可以进行线程和进程的调度,本身具备并发处理能力,但进程切换代价还是过于高昂,进程切换需要保存现场,耗费较多的时间。如果应用程序能在用户层再构筑一级调度,将并发的粒度进一步降低,或许可以更大限度地提升程序运行效率。golang的并发就是基于这个思想实现的。golang在语言层面支持这种并发模式。

golang的并发执行称为goroutine,routine的翻译为例程,故goroutine叫go例程会更加合理。golang通过go关键字来启动一个goroutine,go关键字后面必须跟一个函数,不能是语句或其他东西,函数的返回值被忽略。

  • 通过go+匿名函数形式启动goroutine,代码如下:
     1 package main
     2 
     3 import (
     4     "fmt"
     5     "runtime"
     6     "time"
     7 )
     8 
     9 func main() {
    10     go func() {
    11         sum := 0
    12         for i := 0; i < 1000; i++ {
    13             sum += i
    14         }
    15         fmt.Println(sum)
    16         time.Sleep(1 * time.Second)
    17     }()
    18     // NumGoroutine可以返回当前程序的goroutine数目
    19     fmt.Println("NumGoroutine =", runtime.NumGoroutine())
    20     // main goroutine故意sleep几秒防止提前退出
    21     time.Sleep(3 * time.Second)
    22 }
  • 通过go+有名函数形式启动goroutine,代码如下:
     1 package main
     2 
     3 import (
     4     "fmt"
     5     "runtime"
     6     "time"
     7 )
     8 
     9 func sum() {
    10     sum := 0
    11     for i := 0; i < 1000; i++ {
    12         sum += i
    13     }
    14     fmt.Println(sum)
    15     time.Sleep(1 * time.Second)
    16 }
    17 
    18 func main() {
    19     go sum()
    20     fmt.Println("NumGoroutine =", runtime.NumGoroutine())
    21     time.Sleep(3 * time.Second)
    22 }

goroutine有如下特性:

  • go的执行是非阻塞的,不会等待
  • go后面的函数的返回值会被忽略
  • 调度器不能保证多个goroutine的执行次序
  • 没有父子goroutine的概念,所有goroutine是平等地被调度和执行的
  • go程序执行时会单独为main函数创建一个goroutine,遇到其他go关键字时再去创建其他的goroutine
  • go没有暴露goroutine id给用户,所以不能在一个goroutine里面显示地操作另一个goroutine,不过runtime包提供了一些函数访问和设置goroutine的相关信息
  1. func GOMAXPROCS
    1.  func GOMAXPROCS(n int) int 用来设置或查询可以并发执行的goroutine数目,n大于1表示设置GOMAXPROCS值,否则表示查询当前GOMAXPROCS值。
       1 package main
       2 
       3 import (
       4     "fmt"
       5     "runtime"
       6 )
       7 
       8 func main() {
       9     // 获取当前的GOMAXPROCS值
      10     fmt.Println("GOMAXPROCS =", runtime.GOMAXPROCS(0))
      11     // 设置GOMAXPROCS的值为2
      12     runtime.GOMAXPROCS(2)
      13     // 获取当前的GOMAXPROCS值
      14     fmt.Println("GOMAXPROCS =", runtime.GOMAXPROCS(0))
      15 }
  2. func Goexit
    1.  func Goexit() 是结束当前goroutine的运行,Goexit在结束当前goroutine之前会调用当前goroutine已经注册的defer,Goexit并不会产生panic,所以该goroutine defer里面的recover调用都返回nil。
  3. func Gosched
    1.  func Gosched() 是放弃当前调度执行机会,将当前goroutine放到队列中等待下次被调度。

只有goroutine还是不够的,多个goroutine之间还需要通信、同步、协同等功能。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!