Go 语言的并发原理

GoGogoroutine
Gogoroutine

并发与并行

  • 并发: 单位时间段内,多个任务都在执行 (单位时间内不一定同时执行)
  • 并行: 单位时刻内,多个任务同时执行
goroutine
goroutinegoroutinegoroutinegoroutine

一个并发程序可以在一个处理器或者内核上使用多个线程来执行任务,但是只有同一个程序在某个时间点同时运行在多核或者多处理器上才是真正的并行并行是一种通过使用多处理器以提高速度的能力。所以并发程序可以是并行的,也可以不是。

并行可以通过增加CPU核心数对性能进行调优。

主流的并发模型实现

Node.js

Goroutine 生命周期(运行步骤)

goroutinegoroutinegoroutine

指定CPU核心数

flag
import (
    "fmt"
    "time"
)

func longWait()  {
    fmt.Println("开始longWait()")
    time.Sleep(5 * 1e9) //5秒
    fmt.Println("结束longWait()")
}

func shortWait()  {
    fmt.Println("开始shortWait()")
    time.Sleep(2 * 1e9) //2秒
    fmt.Println("结束shortWait()")
}

func main()  {
    fmt.Println("这里是main()开始的地方:")
    go longWait() //5秒
    go shortWait() //2秒

    fmt.Println("挂起main()")
    //挂起时间以纳秒ns为单位
    time.Sleep(10 * 1e9) //10秒
    fmt.Println("这里是main()结束的地方:") 
}

/*
这里是main()开始的地方:
挂起main()
开始longWait()
开始shortWait()
结束shortWait()
结束longWait()
这里是main()结束的地方:
*/
main()longWait()shortWait()time.Sleep()ns1e9 ns1s10^9
longWait()shortWait()main()main()longWait()5shortWait()2main()10这里是main()结束的地方
main()3shortWait()longWait()main()mainmainserver()

另外,协程是独立的运行单元,我们无法确定它是什么时候开始被执行的,代码逻辑必须独立于协程调用的顺序

Go
func longWait()  {
    fmt.Println("开始longWait()")
    time.Sleep(5 * 1e9) //5秒
    fmt.Println("结束longWait()")
}

func shortWait()  {
    fmt.Println("开始shortWait()")
    time.Sleep(2 * 1e9)     //2秒
    fmt.Println("结束shortWait()")
}

func main()  {
    fmt.Println("这里是main()开始的地方:")
    longWait()         //5秒
    shortWait()     //2秒

    fmt.Println("挂起main()")
    //挂起时间以纳秒ns为单位
    time.Sleep(10 * 1e9)     //10秒
    fmt.Println("这里是main()结束的地方:")     //等候了17秒才执行
}
longWait()5shortWait()2main()10这里是main()结束的地方17

多线程会带来的问题?

并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏上下文切换死锁还有受限于硬件和软件的资源闲置问题

使用多线程的应用难以做到准确,最主要的问题是内存中的数据共享,它们会被多线程以无法预知的方式进行操作,导致一些无法重现或者随机的结果,称作 竞态。不要使用全局变量或者共享内存,它们会给你的代码在并发运算的时候带来危险。解决办法在于同步不同的线程对数据加锁,这样同时就只有一个线程可以变更数据

sync.Mutex

上下文切换

多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换

GogoroutineCPUGoruntime

启动 goroutine

goroutineGoGogoroutinegoroutinethread
goroutineGoruntimego
go hello (a,b,c) #启动一个routine
runtimeruntime.Gosched()goroutineCPUgoroutineCPU
import (
    "fmt"
    "runtime"  //标准库:包含与Go的运行时系统进行交互的操作,例如用于控制协程的函数
)

func say(s string){
    for i:= 0;i<5;i++{
        runtime.Gosched() // 资源
        fmt.Println(s)
    }
}

func main()  {
    go say("hello") //创建一个goroutine开始执行
    say("hello") //当前goroutine开始执行
}

/*
hello
hello
hello
hello
hello
hello
hello
hello
hello
*/
hellomain
func hello(){
    fmt.Println("hello")
}

func main(){
    hello() //顺序执行
    fmt.Println("main")
}

/*
hello
main
*/
gogoroutinemaingoroutinehello函数goroutinegoroutine
func hello(){
    fmt.Println("hello") //goroutine结束,不打印
}

func main(){
    go hello() //创建一个单独的goroutine并开始执行hello函数
    fmt.Println("main") 
}

/*
main
*/
goroutineiiforii
func main(){
    for i:=0;i<10000000 ; i++ {
        //go hello(i) //创建一个goroutine并开始执行hello函数
        go func() { //匿名函数
            fmt.Println(i) //闭包,调用的是函数外部的i
        }()
    }
    fmt.Println("main")
    time.Sleep(1*1e9) 
}

/*
... 
999 重复的i值
999
984
984
...
*/
i
func main(){
    for i:=0;i<100000000 ; i++ {
        //go hello(i) //创建一个goroutine并开始执行hello函数
        go func(i int) { 
            fmt.Println(i) //调用匿名函数参数的i
        }(i)
    }
    fmt.Println("main")
    time.Sleep(1*1e9)
}
goroutine

### math/rand( ) 方法

mathrandUnix
func f(){
    rand.Seed(time.Now().UnixNano()) //Unix时间戳作为随机种子
    for i:= 0 ;  i < 5 ; i++  {
        r1 := rand.Int() //Int64
        r2 := rand.Intn(10) //[0:10)
        fmt.Println(r1,r2)
    }
}

func main(){
    f()
}

/*
5573427335287695331 1
5618001700544303732 2
4984746980924766804 8
2085722169286122110 4
1098102190678553769 9
*/

sync.WaitGroup( ) 方法

synctype WaitGroup()structtime.Sleep()
wg.Add()wg.Wait()wg.Done()
func f(){
    rand.Seed(time.Now().UnixNano()) //Unix时间戳作为随机种子
    for i:= 0 ;  i < 5 ; i++  {
        r1 := rand.Int() //Int64
        r2 := rand.Intn(10) //[0:10)
        fmt.Println(r1,r2)
    }
}

func f1(i int){
    defer wg.Done() //线程结束后执行
    time.Sleep(time.Millisecond * time.Duration(rand.Intn(300))) //使用time.Duration()强转类型
    fmt.Println(i)
}

var wg sync.WaitGroup //WaitGroup方法用于等待一组线程的结束,属于值类型,不能被复制

func main(){
    for i:= 0; i<10 ; i++ { //启动10次goroutine
        wg.Add(1) //启动一个goroutine计数器加1
        go f1(i)
    }
    //time.Sleep()
    wg.Wait() //等待计数器减至0
}