🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] ## **基本概念** ### **串行、并发和并行** 串行:一件事情做完再做下一件事情。 并发:同一时间段内做多件事。 并行:同一时刻做多个事情。 ### **进程、线程和协程** 进程(process):程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。 线程(thread):操作系统基于进程开启的轻量级进程,是操作系统调度执行的最小单位。 协程(coroutine):非操作系统而是由用户创建和控制的用户态线程,比线程更轻量级。 ### **并发模型** * 线程&锁模型 * Actor模型 * CSP模型 * Fork&Join模型 Go语言中主要基于CSP(communicating sequential process)的goroutine和channel来实现,也支持使用传统的多线程共享内存的并发方式。 ## **goroutine** Goroutine是Go语言支持并发的核心,一个goroutine会以一个很小的栈开始其生命周期,一般只需要2KB。goroutine是由Go运行时(runtime)负责调度。 Go语言中,只需要在函数或者方法前加上`go`关键字就可以创建一个goroutine,从而让该函数或方法在新创建的goroutine中执行。 ``` go f() //创建一个新的goroutine 运行函数f ``` 匿名函数也支持 ``` go func() { // .... }() ``` 一个goroutine必定对应一个方法,可以创建多个goroutine去执行相同的方法。 ***** 在Go程序启动时,会位main函数创建一个默认的goroutine。在上面的代码中,在main函数中使用go关键字创建另外一个goroutine去执行hello函数,而此时main goroutine还在继续往下执行,此时存在两个并发执行的goroutine。当main函数结束时整个程序也就结束了。main函数退出太快,另外一个goroutine中的函数还未执行完程序就退出了。 ~~~ func hello() { fmt.Println("hello goroutine!") } func main() { go hello() fmt.Println("main goroutine done!") time.Sleep(time.Second) //让程序停顿一会儿,就可以输出了。 } ~~~ **为什么会先打印main函数中的fmt呢?** 因为在程序中创建goroutine执行函数需要一定的开销,而此时main函数所在的goroutine是继续执行的。 ### **sync.WaitGroup** `sync.WaitGroup`是实现等待一组并发操作完成的好方法。 ``` package main import ( "fmt" "sync" ) // 声明全局等待组变量 var wg sync.WaitGroup func hello() { fmt.Println("hello") wg.Done() // 告知当前goroutine完成 } func main() { wg.Add(1) // 登记1个goroutine go hello() fmt.Println("你好") wg.Wait() // 阻塞等待登记的goroutine完成 } ``` ### **启动多个goroutine** ``` 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都结束 } ``` 多次执行上述代码,发现打印数字的顺序都不一致。这是因为10个goroutine是并发执行的,而goroutine的调度是随机的。 ### **goroutine的调度** **动态栈:**操作系统的线程一般有固定的栈内存(通常为2MB),一个goroutine的初始栈空间很小(一般为2KB),goroutine的栈不是固定的,可以根据需要动态地增大或缩小,Go的runtime会自动为goroutine分配合适的栈空间。