跳到主要内容位置

并发编程 1 (Goroutine)

1. 并发基础概念

串行/并行/并发

  1. 串行:我们都是先读小学,小学毕业后再读初中,读完初中再读高中。

  2. 并发:同一时间段内执行多个任务(你在用微信和两个朋友聊天)。

  3. 并行:同一时刻执行多个任务(你和你朋友都在用微信和指定朋友聊天)。

进程、线程和协程

  1. 进程(process):程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。

  2. 线程(thread):操作系统基于进程开启的轻量级进程,是操作系统调度执行的最小单位。

  3. 协程(coroutine):非操作系统提供而是由用户自行创建和控制的用户态‘线程’,比线程更轻量级。

并发机制简述

Go 语言中的并发程序主要是通过基于 CSP(communicating sequential processes)的 goroutine 和 channel 来实现,当然也支持使用传统的多线程共享内存的并发方式。

  1. Goroutine 是 Go 语言支持并发的核心,在一个 Go 程序中同时创建成百上千个 goroutine 是非常普遍的,一个 goroutine 会以一个很小的栈开始其生命周期,一般只需要 2KB。

  2. 区别于操作系统线程由系统内核进行调度, goroutine 是由 Go 运行时(runtime)负责调度。

  3. 例如 Go 运行时会智能地将 m 个 goroutine 合理地分配给 n 个操作系统线程,实现类似 m:n 的调度机制,不再需要 Go 开发者自行在代码层面维护一个线程池。

2. 线程启动

关键

Go 语言中使用 goroutine 非常简单,只需要在函数或方法调用前加上 go 关键字就可以创建一个 goroutine ,从而让该函数或方法在新创建的 goroutine 中执行。


go f() // 创建一个新的 goroutine 运行函数f

go func(){
// ...
}()//或者这样,注意避免形成闭包函数,出现value不合理的问题

2.1 启动单一线程

package main

import (
"fmt"
"sync"
)

// 声明全局等待组变量
var wg sync.WaitGroup

func hello() {
fmt.Println("hello")
wg.Done() // 告知当前goroutine完成,计数拍-1
}

func main() {
wg.Add(1) // 登记1个goroutine
go hello()
fmt.Println("你好")
wg.Wait() // 阻塞等待登记的goroutine完成(等到计数排为0是才会释放阻塞)
}

2.2 启动多个线程

package main

import (
"fmt"
"sync"
)

var wg sync.WaitGroup

func hello(i int) {
defer wg.Done() // goroutine结束就登记-1
fmt.Println("hello", i)
}
func main() {
for i := 0; i < 10; i++ {
wg.Add(1) // 启动一个goroutine就登记+1
go hello(i)
}
wg.Wait() // 等待所有登记的goroutine都结束
}

3. 线程的相关理论

线程动态栈空间
  1. 操作系统的线程一般都有固定的栈内存(通常为 2MB),而 Go 语言中的 goroutine 非常轻量级,一个 goroutine 的初始栈空间很小(一般为 2KB),
  2. 所以在 Go 语言中一次创建数万个 goroutine 也是可能的。
  3. 并且 goroutine 的栈不是固定的,可以根据需要动态地增大或缩小, Go 的 runtime 会自动为 goroutine 分配合适的栈空间。
底层实现
  1. Go 运行时的调度器使用 GOMAXPROCS 参数来确定需要使用多少个 OS 线程来同时执行 Go 代码。默认值是机器上的 CPU 核心数。例如在一个 8 核心的机器上,GOMAXPROCS 默认为 8.
  2. Go 语言中可以通过 runtime.GOMAXPROCS 函数设置当前程序并发时占用的 CPU 逻辑核心数。(Go1.5 版本之前,默认使用的是单核心执行。Go1.5 版本之后,默认使用全部的 CPU 逻辑核心数。) :::