Go并发模型

传统的编程语言C++ Java Python等,他们的并发逻辑多事基于操作系统的线程。并发执行单元(线程)之间的通信利用的就是操作系统提供的线程或过程间通信的原语。如:共享内存、信号、管道、音讯队列、套接字等。在这些通信原语中,应用最宽泛的就是共享内存。

如果你应用过这种共享内存的并发模型,其实是难用的和容易产生谬误的,特地是在大型或简单的业务场景中。

Go语言从程序设计当初,就将解决下面传统并发模型问题作为指标,并在新并发模型设计中借鉴注明的CSP(Communicationing Sequential Processes-通信顺序进程)并发模型。

CSP模型目标在于简化并发程序的编写,让并发程序的编写程序与编写顺序程序一样简略。

生产者 —》 输入数据 — 输出/输入原语 —》输入数据

为了实现CSP模型,GO语言引入了Channel.Goroutine能够读写channel中的数据,通过channel将goroutine组合连贯在一起。

Go语言中CSP尽管是支流并发模型,然而还是反对共享内存并发模型。次要是在sync包中的互斥锁、读写锁、条件变量、原子操作等。那么咱们该如何抉择呢?

第一种:创立模式

通常会应用上面的形式:

type Worker struct {}func Do(f func()) chan Worker {    w:= make(chan Worker)    go func() {        f()        w<-Worker{}    }()    return w}func main() {    c:=Do(func() {        fmt.Print("到下班时间了...")    })    <-c}

Do函数外部创立了一个gorutine并且返回了一个channel类型的变量。Do函数创立的新goroutine与调用的Do函数的goroutine之间通过一个channel分割了起来,2个goroutine能够通过channel进行通信。Do函数的实现因为channel在Go语言中是一等公民,channel能够像变量一样初始化、传递和赋值。下面的例子Do返回了一个变量,这个变量就是通道,实现了主goroutine和子goroutine的通信。

第二种: 退出模式

##### a) 拆散模式
拆散模式应用最宽泛的是goroutine退出模式。所谓拆散模式就是创立它的goroutine不须要关怀她的退出,这类goroutine启动后与其创建者彻底拆散,其生命周期与其执行的主函数相干,函数返回即goroutine退出。
场景1:一次性工作

// $GOROOT/src/net/dial.gofunc (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn, error) {   ... ...       if oldCancel := d.Cancel; oldCancel != nil {               subCtx, cancel := context.WithCancel(ctx)               defer cancel()               go func() {                       select {                       case <-oldCancel:                               cancel()                       case <-subCtx.Done():                       }               }()               ctx = subCtx       }   ... ...}

在DialContext办法中创立了一个goroutine,用来监听量个channel是否有数据,一旦有数据,解决后即退出。

场景2 常驻后盾执行一些特定工作,比方罕用for{…}或for{select{…}}模式,还能够用定时器或事件驱动执行。上面是Go给每个P内置的GC goroutine就是这种场景的。

// $GOROOT/src/runtime/mgc.gofunc gcBgMarkStartWorkers() {        // Background marking is performed by per-P G's. Ensure that        // each P has a background GC G.        for _, p := range allp {                if p.gcBgMarkWorker == 0 {                        go gcBgMarkWorker(p) // 每个P创立一个goroutine,以运行gcBgMarkWorker                        notetsleepg(&work.bgMarkReady, -1)                        noteclear(&work.bgMarkReady)                }        }}func gcBgMarkWorker(_p_ *p) {    gp := getg()    ... ...    for {             // 解决GC事宜        ... ...    }}

##### b) join模式
在线程模型中,父线程能够通过pthread join来期待子线程完结并获取子线程的完结状态。在Go中,咱们有时候也有这种需要:goroutine的创建者须要期待新goroutine的后果。

type Worker struct {}func Do(f func()) chan Worker {   w:= make(chan Worker)   go func() {       f()       w<-Worker{}   }()   return w}func main() {   c:=Do(func() {       fmt.Print("到下班时间了...")   })   <-c}

咱们还是看刚刚下面的这个例子,Do函数应用典型的goroutine的创立模式创立了一个groutine,main的goroutine作为创立通过Do函数返回的channel与新goroutine建设关系,这个channel得用处就是在文革goroutine之间建设退出工夫的“信号”通信机制。main goroutine在创立完新goroutine后就在该channel上阻塞期待了,晓得新的goroutine退出前向该channel发送了一个”信号”。

运行代码,后果如下:

到下班时间了...
Process finished with exit code 0

获取goroutine的退出状态

如果新goroutine的创建者不仅仅要期待goroutine的退出,还要晓得完结状态,咱们能够通过自定义类型的channel来实现这样的需要。

func add(a,b int) int{    return a+b}func Do(f func(a,b int) int,a,b int) chan int{    c:=make(chan int)    go func() {        r:=f(a,b)        c<-r    }()    return c}func main() {    c:=Do(add,1,5)    fmt.Println(<-c)}运行后果是 6

期待多个goroutine退出

func add(a,b int) int{    return a+b}func Do(f func(a,b int) int,a,b,n int) chan int{    c:=make(chan int)    var wg sync.WaitGroup    for i:=0;i<n;i++{        wg.Add(1)        go func() {            r:=f(a,b)            fmt.Println(r)            wg.Done()        }()    }    go func() {        wg.Wait()        c<-100    }()    go func() {    }()    return c}func main() {    c:=Do(add,1,5,5)    fmt.Println(<-c)}运行后果66666100

##### c) notify-wait模式
后面的场景中,goroutine的创建者都是在被动地期待新goroutine的退出。有些场景,goroutine的创建者须要被动告诉那些新goroutine退出。

告诉并期待一个goroutine的退出

func add(a, b int) int {    return a + b}func Do(f func(a, b int) int, a, b int) chan int {    quit := make(chan int)    go func() {        var job chan string        for {            select {            case x := <-job:                f(a, b)                fmt.Println(x)            case y := <-quit:                quit <- y            }        }    }()    return quit}func main() {    c := Do(add, 1, 5)    fmt.Println("开始干活")    time.Sleep(1 * time.Second)    c <- 0    timer := time.NewTimer(time.Second * 10)    defer timer.Stop()    select {    case status := <-c:        fmt.Println(status)    case <-timer.C:        fmt.Println("期待...")    }}

执行代码后果如下
开始干活
0

告诉并期待多个goroutine退出

上面是告诉并期待多个goroutine退出的场景。Go语言的channel有一个个性,那就是当应用close函数敞开channel时,所有阻塞到该channel上的goroutine都会失去告诉。

func worker(x int)  {    time.Sleep(time.Second * time.Duration(x))}func Do(f func(a int), n int) chan int {    quit := make(chan int)    job:=make(chan int)    var wg sync.WaitGroup    for i:=0;i<n;i++ {        wg.Add(1)        go func(i int) {            defer wg.Done()            name := fmt.Sprintf("worker-%d",i)            for {                j,ok:=<-job                if !ok{                    fmt.Println(name,"done")                    return                }                worker(j)            }        }(i)    }    go func() {        <-quit        close(job)        wg.Wait()        quit<-200    }()    return quit}func main() {    quit:=Do(worker,5)    fmt.Println("func Work...")    quit<-1    timer := time.NewTimer(time.Second * 10)    defer timer.Stop()    select {    case status := <-quit:        fmt.Println(status)    case <-timer.C:        fmt.Println("期待...")    }}运行后果func Work...worker-1 doneworker-2 doneworker-3 doneworker-4 doneworker-0 done200