青训营Xgo语言的并发调度 | Go语音进阶之并发

115 阅读3分钟

狭义的并发是指多个任务在单核上运行,并行是指多个任务在多核上运行,

广义的并发是指多个任务同时运行,所以可以认为并行是并发的一个子集

在go语言中,go关键字可以并发的执行一个新的线程,在需要并发执行的函数前加上go关键字,就代表这个函数需要并发执行。

在Go的并发过程中,需要通过调度器调度G,P,M。G是函数,P是缓冲器,M是系统线程

我们可以将缓冲器理解为一个队列。将同时并发运行的G编入P中,取出一个G与M对接运行,然后系统调度器在合适的时候(G暂停或结束)分开G与M,同时从P中取出新的G与M结合。

而这整个并发过程发生在go执行后。

每一个独立的go程序运行时都会自动启用一个主goroutine。

当程序执行到go语句时,系统会将go语句后的函数包装到一个新的goroutine中(也就是G),再把G放到一个可运行的G队列中,也就是缓冲区P,而这个过程是异步进行的,接下来才是整个的并发过程。

也就是说,并发调度是在go语句执行后由异步的由Go运行时系统自动完成。

这时候可能出现一个问题,由于go的并发模型原理问题,在go语句执行完后程序关闭前,系统并没有执行并发调度,主goroutine直接退出,而原来G中的函数还没来得及等到调度,这就导致并发函数没有实际输出。

针对这个问题有一个简单原始的解决方法,就是引入time包,通过在程序运行后等待一段时间即可

time.Sleep(time.Second)

当然有更高级的方法,通过Golang的开发工具包来让程序及时的退出并且 goroutine 得到调度。

下面是一个小例子 `package main

import (

    "fmt"

    "time"

)

func main(){

    for i:=0; i < 10; i++ {

        go func() {

            fmt.Println(i)

        }()

    }

    time.Sleep(time.Second)

}` 上述代码可能在不同的情况输出不同的结果,可能会输出1~10,也可能重复输出某个数字,甚至输出10个10

image.png 因为并发函数共享同一个变量,并且在函数执行时该变量的值可能会发生变化,那么就可能会出现竞态条件(race condition),这可能导致输出的结果是乱序的。

在上述代码中,for循环中的i变量被多个goroutine共享。当这些goroutine被调度执行时,i的值可能已经被循环更新了,因此每个goroutine打印的i值可能是不同的,这就导致了输出的乱序。我们可以通过构造临时变量,再将临时变量赋值给goroutine来解决这个问题 `package main

import ( "fmt" "time" )

func main(){

for i:=0; i < 10; i++ { go func(k int) {

fmt.Println(k)

}(i) }

time.Sleep(time.Second)

}`

上述代码不可能输出重复的数字。 但是上面两个代码都有可能输出乱序的1-10。如何解决这个问题,来使代码按顺序正确输出1~10呢? 我还没学,等我学后把链接附上,O(∩_∩)O哈哈~