日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

22 Go常見的并發(fā)模式和并發(fā)模型

 F2967527 2021-04-28

Go并發(fā)模型 

傳統(tǒng)的編程語言C++ Java Python等,他們的并發(fā)邏輯多事基于操作系統(tǒng)的線程。并發(fā)執(zhí)行單元(線程)之間的通信利用的就是操作系統(tǒng)提供的線程或進程間通信的原語。如:共享內(nèi)存、信號、管道、消息隊列、套接字等。在這些通信原語中,使用最廣泛的就是共享內(nèi)存。 

如果你使用過這種共享內(nèi)存的并發(fā)模型,其實是難用的和容易發(fā)生錯誤的,特別是在大型或復雜的業(yè)務(wù)場景中。 

Go語言從程序設(shè)計當初,就將解決上面?zhèn)鹘y(tǒng)并發(fā)模型問題作為目標,并在新并發(fā)模型設(shè)計中借鑒注明的CSP(Communicationing Sequential Processes-通信順序進程)并發(fā)模型。 

CSP模型目的在于簡化并發(fā)程序的編寫,讓并發(fā)程序的編寫順序與編寫順序程序一樣簡單。 

生產(chǎn)者  —》輸出數(shù)據(jù)輸入/輸出原語》輸出數(shù)據(jù) 

為了實現(xiàn)CSP模型,GO語言引入了Channel.Goroutine可以讀寫channel中的數(shù)據(jù),通過channelgoroutine組合連接在一起。 

Go語言中CSP雖然是主流并發(fā)模型,但是還是支持共享內(nèi)存并發(fā)模型。主要是在sync包中的互斥鎖、讀寫鎖、條件變量、原子操作等。那么我們該如何選擇呢? 

第一種:創(chuàng)建模式 

通常會使用下面的方式: 

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函數(shù)內(nèi)部創(chuàng)建了一個gorutine并且返回了一個channel類型的變量。Do函數(shù)創(chuàng)建的新goroutine與調(diào)用的Do函數(shù)的goroutine之間通過一個channel聯(lián)系了起來,2goroutine可以通過channel進行通訊。Do函數(shù)的實現(xiàn)因為channelGo語言中是一等公民,channel可以像變量一樣初始化、傳遞和賦值。上面的例子Do返回了一個變量,這個變量就是通道,實現(xiàn)了主goroutine和子goroutine的通信。 

第二種:退出模式 

    a) 分離模式 

        分離模式使用最廣泛的是goroutine退出模式。所謂分離模式就是創(chuàng)建它的goroutine不需要關(guān)心它的退出,這類goroutine啟動后與其創(chuàng)建者徹底分離,其生命周期與其執(zhí)行的主函數(shù)相關(guān),函數(shù)返回即goroutine退出。 

        場景1:一次性任務(wù) 

// $GOROOT/src/net/dial.go 

func (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方法中創(chuàng)建了一個goroutine,用來監(jiān)聽量個channel是否有數(shù)據(jù),一旦有數(shù)據(jù),處理后即退出。 

場景2 常駐后臺執(zhí)行一些特定任務(wù),比如常用for{…}for{select{…}}形式,還可以用定時器或事件驅(qū)動執(zhí)行。下面是Go給每個P內(nèi)置的GC goroutine就是這種場景的。

// $GOROOT/src/runtime/mgc.go

func 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創(chuàng)建一個goroutine,以運行gcBgMarkWorker

                        notetsleepg(&work.bgMarkReady, -1)

                        noteclear(&work.bgMarkReady)

                }

        }

}

func gcBgMarkWorker(_p_ *p) {

    gp := getg()

    ... ...

    for { 

            // 處理GC

        ... ...

    }

}

b) join模式

在線程模型中,父線程可以通過pthread join來等待子線程結(jié)束并獲取子線程的結(jié)束狀態(tài)。在Go中,我們有時候也有這種需求:goroutine的創(chuàng)建者需要等待新goroutine的結(jié)果。

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函數(shù)使用典型的goroutine的創(chuàng)建模式創(chuàng)建了一個groutine,maingoroutine作為創(chuàng)建通過Do函數(shù)返回的channel與新goroutine建立關(guān)系,這個channel得用途就是在goroutine之間建立退出時間的信號通信機制。main goroutine在創(chuàng)建完新goroutine后就在該channel上阻塞等待了,直到新的goroutine退出前向該channel發(fā)送了一個信號。

運行代碼,結(jié)果如下:

到下班時間了...

Process finished with exit code 0

獲取goroutine的退出狀態(tài)

如果新goroutine的創(chuàng)建者不僅僅要等待goroutine的退出,還要知道結(jié)束狀態(tài),我們可以通過自定義類型的channel來實現(xiàn)這樣的需求。

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)

}

運行結(jié)果是 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) 

運行結(jié)果 

100 

c) notify-wait模式 

前面的場景中,goroutine的創(chuàng)建者都是在被動地等待新goroutine的退出。有些場景,goroutine的創(chuàng)建者需要主動通知那些新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('等待...') 

    } 

執(zhí)行代碼結(jié)果如下 

開始干活 

通知并等待多個goroutine退出 

下面是通知并等待多個goroutine退出的場景。Go語言的channel有一個特性,那就是當使用close函數(shù)關(guān)閉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('等待...') 

    } 

運行結(jié)果 

func Work... 

worker-1 done 

worker-2 done 

worker-3 done 

worker-4 done 

worker-0 done 

200 

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多