最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

淺析在Go語言中如何實現(xiàn)協(xié)程池

 更新時間:2025年06月05日 09:16:07   作者:江湖十年  
gammazero/workerpool?就是用來實現(xiàn)協(xié)程池的?Go?包,本文我們將一起來學習一下其使用方法,并深入其源碼來探究下如何實現(xiàn)一個?Go?協(xié)程池,需要的可以了解下

如果你熟悉 Java、Python 等編程語言,那么你一定聽說或者使用過進程池或線程池。因為進程和線程不是越多越好,過多的進程或線程可能造成資源浪費和性能下降。所以池化技術在這些主流編程語言中非常流行,可以有效控制并發(fā)場景下資源使用量。

而 Go 語言則沒有提供多進程和多線程的支持,僅提供了協(xié)程(goroutine)的概念。在 Go 中開啟 goroutine 的成本非常低,以至于我們在絕大多數(shù)情況下開啟 goroutine 時根本無需考慮協(xié)程數(shù)量,所以也就很少有人提及 Go 的協(xié)程池化技術。不過使用場景少,不代表完全沒用。通過協(xié)程池我們可以來掌控資源使用量,降低協(xié)程泄漏風險。

gammazero/workerpool 就是用來實現(xiàn)協(xié)程池的 Go 包,本文我們一起來學習一下其使用方法,并深入其源碼來探究下如何實現(xiàn)一個 Go 協(xié)程池。

使用示例

workerpool 直譯過來是工作池,在 Go 中就是指代協(xié)程池。workerpool 的用法非常簡單,示例代碼如下:

package main

import (
	"fmt"
	"time"

	"github.com/gammazero/workerpool"
)

func main() {
	wp := workerpool.New(2)
	requests := []string{"alpha", "beta", "gamma", "delta", "epsilon"}

	for _, r := range requests {
		wp.Submit(func() {
			fmt.Printf("%s: Handling request: %s\n", time.Now().Format(time.RFC3339), r)
			time.Sleep(1 * time.Second)
		})
	}

	wp.StopWait()
}

workerpool.New(2) 表示我們創(chuàng)建了一個容量為 2 的協(xié)程池,即同一時刻最多只會有 2 個 goroutine 正在執(zhí)行。wp.Submit() 用來提交一個任務,任務類型為無參數(shù)和返回值的函數(shù) func(),這里我們在 for 循環(huán)中提交了 5 個任務。調用 wp.StopWait() 可以等待所有已提交的任務執(zhí)行完成。

執(zhí)行示例代碼,得到輸出如下:

$ go run main.go
2025-05-08T23:40:16+08:00: Handling request: alpha
2025-05-08T23:40:16+08:00: Handling request: beta
2025-05-08T23:40:17+08:00: Handling request: gamma
2025-05-08T23:40:17+08:00: Handling request: delta
2025-05-08T23:40:18+08:00: Handling request: epsilon

不過這里的輸出內容并不是一下子全部輸出完成的,而是兩行兩行的輸出。

根據(jù)打印的時間可以發(fā)現(xiàn),是先輸出:

2025-05-08T23:40:16+08:00: Handling request: alpha
2025-05-08T23:40:16+08:00: Handling request: beta

接著等待 1s 再輸出:

2025-05-08T23:40:17+08:00: Handling request: gamma
2025-05-08T23:40:17+08:00: Handling request: delta

再次等待 1s 最后輸出:

2025-05-08T23:40:18+08:00: Handling request: epsilon

這個輸出結果符合預期,也就是說同一時刻最多只會有 2 個 goroutine 正在執(zhí)行。

源碼解讀

workerpool 用法非常簡單,接下來我們一起看看其實現(xiàn)原理。

下圖是 workerpool 源碼中實現(xiàn)的全部功能:

WorkerPool 是一個結構體,源碼中圍繞這個結構體定義了很多函數(shù)或方法。這些函數(shù)或方法你不必死記硬背,先有一個宏觀上的認知,接下來我將帶你深入學習其中的核心方法。

WorkerPool 結構體完整定義如下:

// WorkerPool 是 Go 協(xié)程的集合池,用于確保同時處理請求的協(xié)程數(shù)量嚴格受控于預設的上限值
type WorkerPool struct {
	maxWorkers   int                 // 最大工作協(xié)程數(shù)
	taskQueue    chan func()         // 任務提交隊列
	workerQueue  chan func()         // 工作協(xié)程消費隊列
	stoppedChan  chan struct{}       // 停止完成通知通道
	stopSignal   chan struct{}       // 停止信號通道
	waitingQueue deque.Deque[func()] // 等待隊列(雙端隊列)
	stopLock     sync.Mutex          // 停止操作互斥鎖
	stopOnce     sync.Once           // 控制只停止一次
	stopped      bool                // 是否已經(jīng)停止
	waiting      int32               // 等待隊列中任務計數(shù)
	wait         bool                // 協(xié)程池退出時是否等待已入隊任務執(zhí)行完成
}

這里屬性很多,其中有 3 個屬性是需要我們重點關注的,taskQueue、workerQueue 以及 waitingQueue,這三者分別代表任務提交隊列、工作隊列和等待隊列。稍后我將通過一個流程圖來講解任務在這 3 個隊列中的傳遞流程,現(xiàn)在我們一起來看一下 WorkerPool 的構造函數(shù):

// New 創(chuàng)建并啟動協(xié)程池
// maxWorkers 參數(shù)指定可以并發(fā)執(zhí)行任務的最大工作協(xié)程數(shù)。
func New(maxWorkers int) *WorkerPool {
	// 至少有一個 worker
	if maxWorkers < 1 {
		maxWorkers = 1
	}

	// 實例化協(xié)程池對象
	pool := &WorkerPool{
		maxWorkers:  maxWorkers,
		taskQueue:   make(chan func()),
		workerQueue: make(chan func()),
		stopSignal:  make(chan struct{}),
		stoppedChan: make(chan struct{}),
	}

	// 啟動任務調度器
	go pool.dispatch()

	return pool
}

New 函數(shù)創(chuàng)建一個指定容量的協(xié)程池對象 WorkerPool,我們已經(jīng)在使用示例中見過其用法了。這里邏輯還是比較簡單的,僅接收一個參數(shù),并初始化了幾個必要的屬性。

值得注意的是,這里通過開啟新的 goroutine 的方式啟動了 dispatch() 方法,這個方法是協(xié)程池最核心的邏輯,用來實現(xiàn)任務的調度執(zhí)行。

為此,我畫了一張流程圖,來分析 WorkerPool 最核心的任務派發(fā)流程:

圖中涉及兩個方法,其中 Submit() 方法用于提交一個任務到協(xié)程池,dispatch 方法則用于派發(fā)任務到 goroutine 中去執(zhí)行。dispatch 方法內部有一個無限循環(huán),實現(xiàn)任務實時派發(fā)執(zhí)行。這個 for 無限循環(huán)中控制著任務在 3 個隊列中的流轉和工作協(xié)程數(shù)量。

只要通過 Submit() 方法提交任務,就一定會進入任務提交隊列 taskQueue 中,而 taskQueue 是一個通過 make(chan func()) 初始化的無緩沖的 channel,所以任務不會在里面停留,要么通過鏈路 ② 下發(fā)到等待隊列 waitingQueue 中,要么通過鏈路 ④ 下發(fā)到工作隊列 workerQueue 中。最終具體會下發(fā)到哪里,是 dispatch 方法中的 for 循環(huán)邏輯來決定的。

dispatchfor 循環(huán)中會處理任務分發(fā),核心邏輯有兩個部分,包含兩種處理模式:

隊列優(yōu)先模式:在 for 循環(huán)中,會優(yōu)先判斷等待隊列 waitingQueue 是否為空,如果不為空,則進入隊列優(yōu)先模式。

  • 此時會優(yōu)先從等待隊列對頭取出任務,然后交給工作隊列 workerQueue,協(xié)程池中的工作協(xié)程(worker)就會不停的從 workerQueue 中拿到任務并執(zhí)行。
  • 如果此時剛好還有新的任務被提交,則新提交的任務自動進入等待隊列尾部。
  • 任務從提交到執(zhí)行的流程是 ① ② ③。

直通模式:等待隊列完全清空后,程序自動切換到直通模式。

  • 此時等待隊列 workerQueue 已經(jīng)清空,如果有新任務提交進來,可以直接交給工作隊列 workerQueue,讓工作協(xié)程(worker)來執(zhí)行。
  • 如果此時工作協(xié)程數(shù)量達到了協(xié)程池的上限,則將任務提交到等待隊列 waitingQueue 中。
  • 任務從提交到執(zhí)行的流程是 ① ④。

以上就是協(xié)程池 dispatch 方法的核心調度流程。

接下來,我將對 WorkerPool 核心代碼進行一一解讀,以此來從微觀上更加細致的理解協(xié)程池的設計。

Submit 方法實現(xiàn)如下:

// Submit 將任務函數(shù)提交到工作池隊列等待執(zhí)行,不會等待任務執(zhí)行完成
func (p *WorkerPool) Submit(task func()) {
	if task != nil {
		p.taskQueue <- task
	}
}

這個方法非常簡單,就是將任務提交到 taskQueue 隊列中。

接下來我們看下是最核心也是最復雜的方法 dispatch 是如何實現(xiàn)的:

// 任務派發(fā),循環(huán)的將下一個排隊中的任務發(fā)送給可用的工作協(xié)程(worker)執(zhí)行
func (p *WorkerPool) dispatch() {
	defer close(p.stoppedChan)            // 保證調度器退出時關閉停止通知通道
	timeout := time.NewTimer(idleTimeout) // 創(chuàng)建 2 秒周期的空閑檢測定時器
	var workerCount int                   // 當前活躍 worker 計數(shù)器
	var idle bool                         // 空閑狀態(tài)標識
	var wg sync.WaitGroup                 // 用于等待所有 worker 完成

Loop:
	for { // 主循環(huán)處理任務分發(fā)

		// 隊列優(yōu)先模式:優(yōu)先檢測等待隊列
		if p.waitingQueue.Len() != 0 {
			if !p.processWaitingQueue() {
				break Loop // 等待隊列為空,退出循環(huán)
			}
			continue
		}

		// 直通模式:開始處理提交上來的新任務
		select {
		case task, ok := <-p.taskQueue: // 接收到新任務
			if !ok { // 協(xié)程池停止時會關閉任務通道,如果 !ok 說明協(xié)程池已停止,退出循環(huán)
				break Loop
			}

			select {
			case p.workerQueue <- task: // 嘗試派發(fā)任務
			default: // 沒有空閑的 worker,無法立即派發(fā)任務
				if workerCount < p.maxWorkers { // 如果協(xié)程池中的活躍協(xié)程數(shù)量小于最大值,那么創(chuàng)建一個新的協(xié)程(worker)來執(zhí)行任務
					wg.Add(1)
					go worker(task, p.workerQueue, &wg) // 創(chuàng)建新的 worker 執(zhí)行任務
					workerCount++                       // worker 記數(shù)加 1
				} else { // 已達協(xié)程池容量上限
					p.waitingQueue.PushBack(task)                              // 將任務提交到等待隊列
					atomic.StoreInt32(&p.waiting, int32(p.waitingQueue.Len())) // 原子更新等待計數(shù)
				}
			}
			idle = false // 標記為非空閑
		case <-timeout.C: // 空閑超時處理
			// 在一個空閑超時周期內,存在空閑的 workers,那么停止一個 worker
			if idle && workerCount > 0 {
				if p.killIdleWorker() { // 回收一個 worker
					workerCount-- // worker 計數(shù)減 1
				}
			}
			idle = true                // 標記為空閑
			timeout.Reset(idleTimeout) // 復用定時器
		}
	}

	if p.wait { // 調用了 StopWait() 方法,需要運行等待隊列中的任務,直至隊列清空
		p.runQueuedTasks()
	}

	// 終止所有 worker
	for workerCount > 0 {
		p.workerQueue <- nil // 發(fā)送終止信號給 worker
		workerCount--        // worker 計數(shù)減 1,直至為 0 退出循環(huán)
	}
	wg.Wait() // 阻塞等待所有 worker 完成

	timeout.Stop() // 停止定時器
}

這個方法代碼量稍微有點多,不過結合我上面畫的流程圖,其實也好理解。我在代碼注釋中也標出了兩種任務處理模式:等待隊列優(yōu)先模式和直通模式。

我們先看等待隊列優(yōu)先模式:

// 隊列優(yōu)先模式:優(yōu)先檢測等待隊列
if p.waitingQueue.Len() != 0 {
    if !p.processWaitingQueue() {
        break Loop // 協(xié)程池已經(jīng)停止
    }
    continue // 隊列不為空則繼續(xù)下一輪循環(huán)
}

如果等待隊列不為空,則優(yōu)先處理等待隊列。p.processWaitingQueue 方法實現(xiàn)如下:

// 處理等待隊列
func (p *WorkerPool) processWaitingQueue() bool {
	select {
	case task, ok := <-p.taskQueue: // 接收到新任務
		if !ok { // 協(xié)程池停止時會關閉任務通道,如果 !ok 說明協(xié)程池已停止,返回 false,不再繼續(xù)處理
			return false
		}
		p.waitingQueue.PushBack(task) // 將新任務加入等待隊列隊尾
	case p.workerQueue <- p.waitingQueue.Front(): // 從等待隊列隊頭獲取任務并放入工作隊列
		p.waitingQueue.PopFront() // 任務已經(jīng)開始處理,所以要從等待隊列中移除任務
	}
	atomic.StoreInt32(&p.waiting, int32(p.waitingQueue.Len())) // 原子修改等待隊列中任務計數(shù)
	return true
}

這個方法中有兩個 case 需要處理:

  • 接收到新任務,直接加入到等待隊列 waitingQueue 的隊尾。
  • 從等待隊列 waitingQueue 的隊頭獲取任務并放入工作隊列 workerQueue

這與前文流程圖中的講解吻合。

任務交給工作隊列 workerQueue 以后,誰來處理 workerQueue 中的任務呢?我們接著往下看直通模式的代碼。

直通模式的代碼中同樣使用 select 多路復用,將邏輯分成了兩個 case 來處理:

// 直通模式:開始處理提交上來的新任務
select {
case task, ok := <-p.taskQueue: // 接收到新任務
    ...
case <-timeout.C: // 空閑超時處理
    ...
}

兩個 case 分別實現(xiàn)任務執(zhí)行和空閑超時處理。

我們先來看處理任務的 case:

case task, ok := <-p.taskQueue: // 接收到新任務
    if !ok { // 協(xié)程池停止時會關閉任務通道,如果 !ok 說明協(xié)程池已停止,退出循環(huán)
        break Loop
    }

    select {
    case p.workerQueue <- task: // 嘗試派發(fā)任務
    default: // 沒有空閑的 worker,無法立即派發(fā)任務
        if workerCount < p.maxWorkers { // 如果協(xié)程池中的活躍協(xié)程數(shù)量小于最大值,那么創(chuàng)建一個新的協(xié)程(worker)來執(zhí)行任務
            wg.Add(1)
            go worker(task, p.workerQueue, &wg) // 創(chuàng)建新的 worker 執(zhí)行任務
            workerCount++                       // worker 記數(shù)加 1
        } else { // 已達協(xié)程池容量上限
            p.waitingQueue.PushBack(task)                              // 將任務提交到等待隊列
            atomic.StoreInt32(&p.waiting, int32(p.waitingQueue.Len())) // 原子更新等待計數(shù)
        }
    }
    idle = false // 標記為非空閑

直通模式下,有新的任務提交進來,首先會嘗試直接將其加入工作隊列 workerQueue 中,如果任務下發(fā)失敗,則說明當前時刻沒有空閑的工作協(xié)程(worker),無法立即派發(fā)任務。那么繼續(xù)比較當前正在執(zhí)行的工作協(xié)程數(shù)量(workerCount)和協(xié)程池大?。╩axWorkers),如果協(xié)程池中的活躍協(xié)程數(shù)量小于最大值,那么創(chuàng)建一個新的協(xié)程(worker)來執(zhí)行任務。否則,說明正在執(zhí)行的工作協(xié)程數(shù)量已達協(xié)程池容量上限,那么將任務提交到等待隊列 waitingQueue 中。那么下一次 for 循環(huán)執(zhí)行的時候,檢測到 waitingQueue 中有任務,就會優(yōu)先處理 waitingQueue。這也就實現(xiàn)了兩種模式的切換。

我們再來看下工作協(xié)程 worker 是如何執(zhí)行任務的:

// 工作協(xié)程,執(zhí)行任務并在收到 nil 信號時停止
func worker(task func(), workerQueue chan func(), wg *sync.WaitGroup) {
	for task != nil { // 循環(huán)執(zhí)行任務,直至接收到終止信號 nil
		task()               // 執(zhí)行任務
		task = <-workerQueue // 接收新任務
	}
	wg.Done() // 標記 worker 完成
}

可以發(fā)現(xiàn),這里使用 for 循環(huán)來不停的執(zhí)行提交過來的任務,直至從 workerQueue 中接收到終止信號 nil。那么這個終止信號是何時下發(fā)的呢?往下看你馬上能找到答案。

接下來我們看一下直通模式的另外一個 case 邏輯:

case <-timeout.C: // 空閑超時處理
    // 在一個空閑超時周期內,存在空閑的 workers,那么停止一個 worker
    if idle && workerCount > 0 {
        if p.killIdleWorker() { // 回收一個 worker
            workerCount-- // worker 計數(shù)減 1
        }
    }
    idle = true                // 標記為空閑
    timeout.Reset(idleTimeout) // 復用定時器

這里使用定時器來管理超過特定時間,未收到任務,需要關閉空閑的工作協(xié)程(worker)。

關閉 worker 的方法是 p.killIdleWorker

// 停止一個空閑 worker
func (p *WorkerPool) killIdleWorker() bool {
	select {
	case p.workerQueue <- nil: // 發(fā)送終止信號給工作協(xié)程(worker)
		// Sent kill signal to worker.
		return true
	default:
		// No ready workers. All, if any, workers are busy.
		return false
	}
}

這里正是通過給 workerQueue 發(fā)送 nil 來作為終止信號,以此來實現(xiàn)通知 worker 退出的。

看完了 Submitdispatch 方法源碼,你現(xiàn)在是否對協(xié)程池有了更深入的認知呢?你可以再回顧一下我在前文中畫的任務調度流程圖,加深印象。

workerpool 的源碼就講解到這里,其他方法實現(xiàn)其實都比較簡單,就交給你自己去探索了。你可以參考我的中文注釋版源碼:github.com/jianghushin…。

總結

協(xié)程池作為 Go 中不那么常用的技術,依然有其存在的價值,本文介紹的 workerpool 項目是一個協(xié)程池的實現(xiàn)。

workerpool 用法非常簡單,僅需要通過 workerpool.New(n) 函數(shù)既可創(chuàng)建一個大小為 n 的協(xié)程池,之后通過 wp.Submit(task) 既可以提交任務到協(xié)程池。

workerpool 內部提供了 3 個隊列來對任務進行派發(fā)調度,任務提交隊列 taskQueue 和工作隊列 workQueue 都是使用 channel 實現(xiàn)的,并且無緩沖,真正帶有緩沖效果的隊列是等待隊列 WaitingQueue,這個是真正的隊列實現(xiàn),采用雙端隊列,而非 channel,并且不限制隊列長度。也就是說,無論排隊多少個任務,workerpool 都不會阻止新任務的提交。所以,我們在創(chuàng)建協(xié)程池時需要設置一個合理的大小限制,以防止等待隊列無限增長,任務很長一段時間都得不到執(zhí)行。

此外,workerpool 內部雖然會維護一個協(xié)程池,但超過一定空閑時間沒有任務提交過來,工作協(xié)程是會關閉的,之后新任務進來再次啟動新的協(xié)程,因為啟動新協(xié)程開銷小,所以沒長久駐留協(xié)程。

到此這篇關于淺析在Go語言中如何實現(xiàn)協(xié)程池的文章就介紹到這了,更多相關Go協(xié)程池內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 一文詳解Go語言中方法的指針接收者與值接收者

    一文詳解Go語言中方法的指針接收者與值接收者

    在 Go 語言里,方法是一種特殊的函數(shù),它與特定的類型關聯(lián),方法可以使用值接收者或者指針接收者,理解這兩種接收者的區(qū)別和使用場景,對于編寫高效、正確的 Go 代碼至關重要,本文將深入剖析指針接收者和值接收者的特點,需要的朋友可以參考下
    2025-06-06
  • GO開發(fā)之項目打包部署過程

    GO開發(fā)之項目打包部署過程

    在main.go目錄執(zhí)行go?build,用-o指定輸出文件名(如gotest.exe),交叉編譯需設置GOOS和GOARCH環(huán)境變量,支持多平臺,注意-o參數(shù)不能用于交叉編譯,上傳后修改權限即可運行
    2025-08-08
  • 使用golang獲取linux上文件的訪問/創(chuàng)建/修改時間

    使用golang獲取linux上文件的訪問/創(chuàng)建/修改時間

    這篇文章主要介紹了使用golang獲取linux上文件的訪問/創(chuàng)建/修改時間,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-08-08
  • golang中命令行庫cobra的使用方法示例

    golang中命令行庫cobra的使用方法示例

    這篇文章主要給大家介紹了關于golang中命令行庫cobra的使用方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2018-08-08
  • 詳解Golang開啟http服務的三種方式

    詳解Golang開啟http服務的三種方式

    這篇文章主要介紹了詳解Golang開啟http服務的三種方式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-06-06
  • Go語言開發(fā)k8s之Service操作解析

    Go語言開發(fā)k8s之Service操作解析

    這篇文章主要為大家介紹了Go語言開發(fā)k8s之Service操作解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-06-06
  • GOLang?IO接口與工具使用方法講解

    GOLang?IO接口與工具使用方法講解

    這篇文章主要介紹了GOLang?IO接口與工具使用方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧
    2023-03-03
  • Golang?channel關閉后是否可以讀取剩余的數(shù)據(jù)詳解

    Golang?channel關閉后是否可以讀取剩余的數(shù)據(jù)詳解

    這篇文章主要介紹了Golang?channel關閉后是否可以讀取剩余的數(shù)據(jù),文章通過一個測試例子給大家詳細的介紹了是否可以讀取剩余的數(shù)據(jù),需要的朋友可以參考下
    2023-09-09
  • Golang實現(xiàn)事務型內存數(shù)據(jù)庫的方法詳解

    Golang實現(xiàn)事務型內存數(shù)據(jù)庫的方法詳解

    內存數(shù)據(jù)庫經(jīng)我們經(jīng)常用到,例如Redis,那么如何從零實現(xiàn)一個內存數(shù)據(jù)庫呢,本文旨在介紹如何使用Golang編寫一個KV內存數(shù)據(jù)庫MossDB
    2023-03-03
  • 重學Go語言之如何使用Modules

    重學Go語言之如何使用Modules

    Go語言在Go.1.11版本發(fā)布了Go?Modules,這是一種新的Go項目依賴管理解決方案,可以讓Go項目的依賴包關系更加清晰,也更容易管理,下面就來看看Modules是如何使用的吧
    2023-07-07

最新評論

金塔县| 望城县| 班戈县| 康定县| 奈曼旗| 红桥区| 西平县| 连山| 彭泽县| 石棉县| 富民县| 基隆市| 凌云县| 古蔺县| 措美县| 嘉峪关市| 龙州县| 荣昌县| 大竹县| 民丰县| 台湾省| 喜德县| 沙雅县| 米林县| 织金县| 恩施市| 新郑市| 黑龙江省| 咸丰县| 突泉县| 志丹县| 东方市| 林州市| 蒙阴县| 蕲春县| 松阳县| 苏尼特左旗| 万全县| 定边县| 登封市| 伊宁市|