欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Golang 并發(fā)控制模型的實現(xiàn)

 更新時間:2024年08月14日 10:25:43   作者:及爾偕老lp  
Go控制并發(fā)有三種經(jīng)典的方式,使用?channel?通知實現(xiàn)并發(fā)控制、使用 sync 包中的?WaitGroup?實現(xiàn)并發(fā)控制、使用?Context?上下文實現(xiàn)并發(fā)控制,下面就來介紹一下

Go語言的并發(fā)模型是CSP(通信順序進程),提倡通過通信來進行內(nèi)存共享,而不是通過共享內(nèi)存來實現(xiàn)通信。

控制并發(fā)有三種經(jīng)典的方式,使用 channel 通知實現(xiàn)并發(fā)控制、使用 sync 包中的 WaitGroup 實現(xiàn)并發(fā)控制、使用 Context 上下文實現(xiàn)并發(fā)控制。

一、使用 channel 通知實現(xiàn)并發(fā)控制

1、無緩沖通道

無緩沖通道,又叫做阻塞通道。發(fā)送方 (goroutine) 和接收方 (gouroutine) 必須是同步的,同時準備好,如果沒有同時準備好的話,一方就會一直阻塞住,直到另一方準備好為止。

使用無緩沖通道進行通信,將發(fā)送和接收的 goroutine 同步化,因此,無緩沖通道也被稱為同步通道。

ch := make(chan int) // 創(chuàng)建無緩沖通道

在這里插入圖片描述

使用無緩沖通道實現(xiàn)并發(fā)控制:

package main

import "fmt"

func recv(c chan int) {
	fmt.Println("開始接收")
	ret := <-c
	fmt.Println("接收成功", ret)
}

func main() {
	ch := make(chan int)
	go recv(ch) // 啟用 goroutine 從通道接收值
	ch <- 10
	fmt.Println("發(fā)送成功")
}


在這里插入圖片描述

當子協(xié)程從無緩沖 channel 里接收值時,沒有發(fā)送方,子協(xié)程阻塞等待,直到主協(xié)程往無緩沖 channel 里發(fā)送值,子協(xié)程開始執(zhí)行,然后主協(xié)程開始執(zhí)行。

2、有緩沖通道

只要通道的容量大于0,那么該通道就是有緩沖通道,通道的容量表示通道中能最多存放元素的數(shù)量。發(fā)送方在緩沖區(qū)滿的時候阻塞,接收方不阻塞;接收方在緩沖區(qū)為空的時候阻塞,發(fā)送方不阻塞。

ch := make(chan int, 10) // 創(chuàng)建一個緩沖區(qū)為10的有緩沖通道
fmt.Println(len(ch)) // 通過len函數(shù)獲取當前通道內(nèi)元素數(shù)量
fmt.Println(cap(ch)) // 通過cap函數(shù)獲取通道的容量

在這里插入圖片描述

使用緩沖區(qū)為 1 的通道實現(xiàn)并發(fā)控制:

package main

import (
	"fmt"
	"time"
)

func recv(c chan int) {
	fmt.Println("開始接收")
	ret := &lt;-c
	fmt.Println("接收成功", ret)
}

func main() {
	ch := make(chan int, 1)
	ch &lt;- 10
	go recv(ch) // 啟用 goroutine 從通道接收值
	time.Sleep(time.Second)
	fmt.Println("發(fā)送成功")
}


在這里插入圖片描述

當主協(xié)程往緩沖區(qū)為1的 channel 里發(fā)送值時,不阻塞,子協(xié)程啟動,從無緩沖 channel 里接收值,主協(xié)程睡眠1秒,等待子協(xié)程執(zhí)行完,主協(xié)程在執(zhí)行。

二、使用 sync 包中的 WaitGroup 實現(xiàn)并發(fā)控制

1、sync.WaitGroup

在 sync 包中提供了 WaitGroup ,它會等待它收集的所有 goroutine 任務(wù)全部完成。

在主協(xié)程中調(diào)用 Add() 添加需要執(zhí)行 goroutine 的數(shù)量,在每一個 goroutine 執(zhí)行完成后調(diào)用 Done() ,表示這個 goroutine 已經(jīng)完成,主協(xié)程調(diào)用 Wait() 阻塞等待所有 goroutine 執(zhí)行完成,當所有的 goroutine 都執(zhí)行完成后,主協(xié)程返回。

實現(xiàn)原理:sync.WaitGroup 內(nèi)部維護著一個計數(shù)器,計數(shù)器的值可以增加和減少,當我們啟動 N 個并發(fā)任務(wù)時,將計數(shù)器增加 N,每個任務(wù)通過調(diào)用Done方法將計數(shù)器減1,通過調(diào)用Wait()來等待并發(fā)任務(wù)執(zhí)行完,當計數(shù)器的值為 0 時,表示所有并發(fā)任務(wù)都已經(jīng)完成。

sync.WaitGroup有以下三種方法:

  • Add(N int) : 計數(shù)器 + N
  • Done() : 計數(shù)器 - 1
  • Wait() : 阻塞,直到計數(shù)器變?yōu)?
package main

import (
	"fmt"
	"sync"
)

var wg sync.WaitGroup

func hello() {
	defer wg.Done()
	fmt.Println("Hello Goroutine!")
}
func main() {
	wg.Add(3)
	go hello() // 啟動3個goroutine去執(zhí)行hello函數(shù)
	go hello()
	go hello()
	fmt.Println("main goroutine done!")
	wg.Wait()
}

在這里插入圖片描述

擴展:

在Golang官網(wǎng)中,有這么一句話:

A WaitGroup must not be copied after first use.

意思是,在 WaitGroup 第一次使用后,不能被拷貝。

為什么呢???

通過下面的例子我們淺淺分析一下。

package main

import (
	"fmt"
	"sync"
)

func main() {
	wg := sync.WaitGroup{}
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(wg sync.WaitGroup, i int) {
			fmt.Println(i)
			wg.Done()
		}(wg, i)
	}
	wg.Wait()
}

在這里插入圖片描述

提示所有的 goroutine 都已經(jīng)睡眠了,出現(xiàn)了死鎖。這是因為 wg 值拷貝傳遞到了子 goroutine 中,導致只有 Add 操作,Done 操作是在 wg 的副本執(zhí)行的, wg 的作用域為子協(xié)程,而不是全局,因此主協(xié)程就死鎖了。

改正方法:

  • 指針,將匿名函數(shù)中 wg 的傳入類型改為 *sync.WaitGroup 。
func main() {
	wg := sync.WaitGroup{}
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(wg *sync.WaitGroup, i int) {
			fmt.Println(i)
			wg.Done()
		}(&wg, i)
	}
	wg.Wait()
}
  • 閉包,將匿名函數(shù)中的 wg 的傳入?yún)?shù)去掉,在匿名函數(shù)中可以直接使用外面的 wg 變量。
func main() {
	wg := sync.WaitGroup{}
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(i int) {
			fmt.Println(i)
			wg.Done()
		}(i)
	}
	wg.Wait()
}

2、sync.Once

很多場景下,我們需要確保某些操作在高并發(fā)時只執(zhí)行一次,例如只加載一次配置文件。Go語言中的sync包提供了一個針對只執(zhí)行一次場景的解決方案 sync.Once。

sync.Once只有一個Do方法,Do(f func()) 。

實現(xiàn)原理:sync.Once 內(nèi)部包含一個互斥鎖和一個布爾值,互斥鎖保證布爾值和數(shù)據(jù)的安全,而布爾值用來記錄初始化是否完成,這樣設(shè)計就能保證初始化操作的時候是并發(fā)安全的,并且初始化操作也不會執(zhí)行多次。

樣例如下:延遲一個開銷很大的初始化操作到真正用到它的時候再執(zhí)行。

package main

import (
	"image"
	"sync"
)

var icons map[string]image.Image

var loadIconsOnce sync.Once

func loadIcons() {
	// 加載圖片
	icons = map[string]image.Image{}
}

// Icon 是并發(fā)安全的
func Icon(name string) image.Image {
	loadIconsOnce.Do(loadIcons)
	return icons[name]
}

三、使用 Context 上下文實現(xiàn)并發(fā)控制

1、簡介

在一些簡單場景下使用 channel 和 WaitGroup 已經(jīng)足夠了,但是當面臨一些復(fù)雜多變的網(wǎng)絡(luò)并發(fā)場景下 channel 和 WaitGroup 顯得有些力不從心了。在并發(fā)程序中,由于超時、取消操作或者一些異常情況,往往需要進行搶占操作或者中斷后續(xù)操作。

舉個例子:在 Go http包的Server中,每一個請求在都有一個對應(yīng)的 goroutine 去處理。請求處理函數(shù)通常會啟動額外的 goroutine 用來訪問后端服務(wù),比如數(shù)據(jù)庫和RPC服務(wù),用來處理一個請求的 goroutine 通常需要訪問一些與請求特定的數(shù)據(jù),比如終端用戶的身份認證信息、驗證相關(guān)的token、請求的截止時間。 當一個請求被取消或超時時,所有用來處理該請求的 goroutine 都應(yīng)該迅速中斷退出,然后系統(tǒng)才能釋放這些 goroutine 占用的資源。

所以我們需要一種可以跟蹤 goroutine 的方案,才可以達到控制他們的目的,這就是Go語言為我們提供的 Context,稱之為上下文非常貼切,它就是 goroutine 的上下文。它包括一個程序的運行環(huán)境、現(xiàn)場和快照等。每個程序要運行時,都需要知道當前程序的運行狀態(tài),通常Go 將這些封裝在一個 Context 里,再將它傳給要執(zhí)行的 goroutine 。context 包主要是用來處理多個 goroutine 之間共享數(shù)據(jù),及多個 goroutine 的管理。

context常用的使用場景:

  • 一個請求對應(yīng)多個goroutine之間的數(shù)據(jù)交互
  • 超時控制
  • 上下文控制

2、context 包

context 包的核心是 struct Context,接口聲明如下:

type Context interface {
	// 返回Context的超時時間(超時返回場景)
	Deadline() (deadline time.Time, ok bool)
	// 在Context超時或取消時(即結(jié)束了)返回一個關(guān)閉的channel,取消信號
    // 如果當前Context超時或取消時,Done方法會返回一個channel,然后其他地方就可以通過判斷Done方法是否有返回(channel),如果有則說明Context已結(jié)束
    // 故其可以作為廣播通知其他相關(guān)方本Context已結(jié)束,請做相關(guān)處理。
	Done() <-chan struct{}
	// 返回Context取消的原因
	Err() error
	// 返回Context相關(guān)數(shù)據(jù)
	Value(key any) any
}

Context 對象是線程安全的(底層數(shù)據(jù)結(jié)構(gòu)加了互斥鎖),你可以把一個 Context 對象傳遞給任意個數(shù)的 gorotuine,對它執(zhí)行取消操作時,所有 goroutine 都會接收到取消信號。

一個 Context 不能擁有 Cancel 方法,同時我們也只能 Done channel 接收數(shù)據(jù)。原因是:接收取消信號的函數(shù)和發(fā)送信號的函數(shù)通常不是一個。一個典型的場景是:父操作為子操作操作啟動 goroutine,子操作也就不能取消父操作。

3、繼承 context

context 包提供了一些函數(shù),協(xié)助用戶從現(xiàn)有的 Context 對象創(chuàng)建新的 Context 對象。這些 Context 對象形成一棵樹:當一個 Context 對象被取消時,繼承自它的所有 Context 都會被取消。

Background 是所有 Context 對象樹的根,它不能被取消。context 包提供了三種context,分別是普通context、超時context、帶值的context:

func Background() Context {
	return backgroundCtx{}
}

// 普通context,通常這樣調(diào)用: 
ctx, cancel := context.WithCancel(context.Background())
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

// 帶超時的context,超時之后會自動close對象的Done,與調(diào)用CancelFunc的效果一樣
// WithDeadline 明確地設(shè)置一個d指定的系統(tǒng)時鐘時間,如果超過就觸發(fā)超時
// WithTimeout 設(shè)置一個相對的超時時間,也就是deadline設(shè)為timeout加上當前的系統(tǒng)時間
// 因為兩者事實上都依賴于系統(tǒng)時鐘,所以可能存在微小的誤差,所以官方不推薦把超時間隔設(shè)置得太小
// 通常這樣調(diào)用:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

// 帶有值的context,沒有CancelFunc,所以它只用于值的多goroutine傳遞和共享
// 通常這樣調(diào)用:
ctx := context.WithValue(context.Background(), "key", myValue)
func WithValue(parent Context, key, val interface{}) Context

WithCancel 和 WithTimeout 函數(shù)會返回繼承的 Context 對象, 這些對象可以比它們的父 Context 更早地取消。當請求處理函數(shù)返回時,與該請求關(guān)聯(lián)的 Context 會被取消。當使用多個副本發(fā)送請求時,可以使用 WithCancel 取消多余的請求。 WithTimeout 在設(shè)置對后端服務(wù)器請求超時時間時非常有用。WithValue 函數(shù)能夠?qū)⒄埱笞饔糜虻臄?shù)據(jù)與 Context 對象建立關(guān)系。

4、context 例子

下面的例子,主要描述的是通過一個 channel 實現(xiàn)一個為循環(huán)次數(shù)為5的循環(huán)。

package main

import (
	"context"
	"fmt"
	"time"
)

func childFunc(cont context.Context, num *int) {
	ctx, _ := context.WithCancel(cont)
	for {
		select {
		case <-ctx.Done():
			fmt.Println("child Done : ", ctx.Err())
			return
		}
	}
}

func main() {
	gen := func(ctx context.Context) <-chan int {
		dst := make(chan int)
		n := 1
		go func() {
			for {
				select {
				case <-ctx.Done():
					fmt.Println("parent Done : ", ctx.Err())
					return // returning not to leak the goroutine
				case dst <- n:
					n++
					go childFunc(ctx, &n)
				}
			}
		}()
		return dst
	}

	ctx, cancel := context.WithCancel(context.Background())
	for n := range gen(ctx) {
		fmt.Println(n)
		if n >= 5 {
			break
		}
	}
	cancel()
	time.Sleep(5 * time.Second)
}

在這里插入圖片描述

在每一個循環(huán)中產(chǎn)生一個goroutine,每一個goroutine中都傳入context,在每個goroutine中通過傳入 ctx 創(chuàng)建一個子Context,并且通過 select 一直監(jiān)控該Context的運行情況,當父 Context 退出的時候,代碼中并沒有明顯調(diào)用子 Context 的 Cancel 函數(shù),但是分析結(jié)果,子 Context 還是被正確合理的關(guān)閉了,這是因為,所有基于這個 Context 或者衍生的子 Context 都會收到通知,這時就可以進行清理操作了,最終釋放 goroutine,這就優(yōu)雅的解決了 goroutine 啟動后不可控的問題。

5、context 使用原則

  • 不要把 context 放在結(jié)構(gòu)體中,要以參數(shù)的方式傳遞。
  • 以 context 作為參數(shù)的函數(shù)方法,應(yīng)該把 context 作為第一個參數(shù),放在第一位。
  • 給一個函數(shù)方法傳遞 context 的時候,不要傳遞nil,如果不知道傳遞什么,就使用context.TODO。
  • context 的 Value 相關(guān)方法應(yīng)該傳遞必須的數(shù)據(jù),不要什么數(shù)據(jù)都使用這個傳遞。
  • context 是線程安全的,底層數(shù)據(jù)結(jié)構(gòu)加了互斥鎖,可以放心的在多個goroutine中傳遞。

到此這篇關(guān)于Golang 并發(fā)控制模型的實現(xiàn)的文章就介紹到這了,更多相關(guān)Golang 并發(fā)控制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家! 

相關(guān)文章

  • golang頻率限制 rate詳解

    golang頻率限制 rate詳解

    這篇文章主要介紹了golang頻率限制 rate詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • golang?recover函數(shù)使用中的一些坑解析

    golang?recover函數(shù)使用中的一些坑解析

    這篇文章主要為大家介紹了golang?recover函數(shù)使用中的一些坑解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-02-02
  • Golang設(shè)計模式工廠模式實戰(zhàn)寫法示例詳解

    Golang設(shè)計模式工廠模式實戰(zhàn)寫法示例詳解

    這篇文章主要為大家介紹了Golang 工廠模式實戰(zhàn)寫法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • go cron定時任務(wù)的基本使用講解

    go cron定時任務(wù)的基本使用講解

    這篇文章主要為大家介紹了gocron定時任務(wù)的基本使用講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-06-06
  • 關(guān)于go平滑重啟庫overseer實現(xiàn)原理詳解

    關(guān)于go平滑重啟庫overseer實現(xiàn)原理詳解

    這篇文章主要為大家詳細介紹了關(guān)于go平滑重啟庫overseer實現(xiàn)原理,文中的示例代碼講解詳細,具有一定的參考價值,有需要的小伙伴可以參考下
    2023-11-11
  • 使用Go和Tesseract實現(xiàn)驗證碼識別的流程步驟

    使用Go和Tesseract實現(xiàn)驗證碼識別的流程步驟

    驗證碼主要用于區(qū)分人類用戶和機器程序,Tesseract 是一個開源的光學字符識別(OCR)引擎,支持多種語言和字體,并具有較高的識別準確率,它由 Google 維護,并且可以通過多種編程語言調(diào)用,本文給大家介紹了使用Go和Tesseract實現(xiàn)驗證碼識別的流程步驟
    2025-01-01
  • Golang判斷兩個鏈表是否相交的方法詳解

    Golang判斷兩個鏈表是否相交的方法詳解

    這篇文章主要為大家詳細介紹了如何通過Golang判斷兩個鏈表是否相交,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2023-03-03
  • 一文教你如何用好GO語言變長參數(shù)

    一文教你如何用好GO語言變長參數(shù)

    對于函數(shù)重載相信編碼過的?xdm?肯定不會陌生,那么我們一起分別來看看?C?語言,C++?語言,GO?語言?如何去模擬和使用重載,感興趣的可以學習一下
    2023-09-09
  • golang實現(xiàn)通過smtp發(fā)送電子郵件的方法

    golang實現(xiàn)通過smtp發(fā)送電子郵件的方法

    這篇文章主要介紹了golang實現(xiàn)通過smtp發(fā)送電子郵件的方法,實例分析了Go語言基于SMTP協(xié)議發(fā)送郵件的相關(guān)技巧,需要的朋友可以參考下
    2016-07-07
  • 一文帶大家了解Go語言中的內(nèi)聯(lián)優(yōu)化

    一文帶大家了解Go語言中的內(nèi)聯(lián)優(yōu)化

    內(nèi)聯(lián)優(yōu)化是一種常見的編譯器優(yōu)化策略,通俗來講,就是把函數(shù)在它被調(diào)用的地方展開,這樣可以減少函數(shù)調(diào)用所帶來的開銷,本文主要為大家介紹了Go中內(nèi)聯(lián)優(yōu)化的具體使用,需要的可以參考下
    2023-05-05

最新評論