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

Go實現(xiàn)線程池(工作池)的兩種方式實例詳解

 更新時間:2022年04月18日 08:46:46   作者:駿馬金龍  
這篇文章主要介紹了Go實現(xiàn)線程池(工作池)的兩種方式實例詳解,需要的朋友可以參考下

worker pool簡介

worker pool其實就是線程池thread pool。對于go來說,直接使用的是goroutine而非線程,不過這里仍然以線程來解釋線程池。

在線程池模型中,有2個隊列一個池子:任務(wù)隊列、已完成任務(wù)隊列和線程池。其中已完成任務(wù)隊列可能存在也可能不存在,依據(jù)實際需求而定。

只要有任務(wù)進來,就會放進任務(wù)隊列中。只要線程執(zhí)行完了一個任務(wù),就將任務(wù)放進已完成任務(wù)隊列,有時候還會將任務(wù)的處理結(jié)果也放進已完成隊列中。

worker pool中包含了一堆的線程(worker,對go而言每個worker就是一個goroutine),這些線程嗷嗷待哺,等待著為它們分配任務(wù),或者自己去任務(wù)隊列中取任務(wù)。取得任務(wù)后更新任務(wù)隊列,然后執(zhí)行任務(wù),并將執(zhí)行完成的任務(wù)放進已完成隊列。

下圖來自wiki:

在Go中有兩種方式可以實現(xiàn)工作池:傳統(tǒng)的互斥鎖、channel。

傳統(tǒng)互斥鎖機制的工作池

假設(shè)Go中的任務(wù)的定義形式為:

type Task struct {
	...
}

每次有任務(wù)進來時,都將任務(wù)放在任務(wù)隊列中。

使用傳統(tǒng)的互斥鎖方式實現(xiàn),任務(wù)隊列的定義結(jié)構(gòu)大概如下:

type Queue struct{
	M     sync.Mutex
	Tasks []Task
}

然后在執(zhí)行任務(wù)的函數(shù)中加上Lock()和Unlock()。例如:

func Worker(queue *Queue) {
	for {
		// Lock()和Unlock()之間的是critical section
		queue.M.Lock()
		// 取出任務(wù)
		task := queue.Tasks[0]
		// 更新任務(wù)隊列
		queue.Tasks = queue.Tasks[1:]
		queue.M.Unlock()
		// 在此goroutine中執(zhí)行任務(wù)
		process(task)
	}
}

假如在線程池中激活了100個goroutine來執(zhí)行Worker()。Lock()和Unlock()保證了在同一時間點只能有一個goroutine取得任務(wù)并隨之更新任務(wù)列表,取任務(wù)和更新任務(wù)隊列都是critical section中的代碼,它們是具有原子性。然后這個goroutine可以執(zhí)行自己取得的任務(wù)。于此同時,其它goroutine可以爭奪互斥鎖,只要爭搶到互斥鎖,就可以取得任務(wù)并更新任務(wù)列表。當(dāng)某個goroutine執(zhí)行完process(task),它將因為for循環(huán)再次參與互斥鎖的爭搶。

上面只是給出了一點主要的代碼段,要實現(xiàn)完整的線程池,還有很多額外的代碼。

通過互斥鎖,上面的一切操作都是線程安全的。但問題在于加鎖/解鎖的機制比較重量級,當(dāng)worker(即goroutine)的數(shù)量足夠多,鎖機制的實現(xiàn)將出現(xiàn)瓶頸。

通過buffered channel實現(xiàn)工作池

在Go中,也能用buffered channel實現(xiàn)工作池。

示例代碼很長,所以這里先拆分解釋每一部分,最后給出完整的代碼段。

在下面的示例中,每個worker的工作都是計算每個數(shù)值的位數(shù)相加之和。例如給定一個數(shù)值234,worker則計算2+3+4=9。這里交給worker的數(shù)值是隨機生成的[0,999)范圍內(nèi)的數(shù)值。

這個示例有幾個核心功能需要先解釋,也是通過channel實現(xiàn)線程池的一般功能:

  • 創(chuàng)建一個task buffered channel,并通過allocate()函數(shù)將生成的任務(wù)存放到task buffered channel中
  • 創(chuàng)建一個goroutine pool,每個goroutine監(jiān)聽task buffered channel,并從中取出任務(wù)
  • goroutine執(zhí)行任務(wù)后,將結(jié)果寫入到result buffered channel中
  • 從result buffered channel中取出計算結(jié)果并輸出

首先,創(chuàng)建Task和Result兩個結(jié)構(gòu),并創(chuàng)建它們的通道:

type Task struct {
	ID      int
	randnum int
}

type Result struct {
	task    Task
	result  int
}

var tasks = make(chan Task, 10)
var results = make(chan Result, 10)

這里,每個Task都有自己的ID,以及該任務(wù)將要被worker計算的隨機數(shù)。每個Result都包含了worker的計算結(jié)果result以及這個結(jié)果對應(yīng)的task,這樣從Result中就可以取出任務(wù)信息以及計算結(jié)果。

另外,兩個通道都是buffered channel,容量都是10。每個worker都會監(jiān)聽tasks通道,并取出其中的任務(wù)進行計算,然后將計算結(jié)果和任務(wù)自身放進results通道中。

然后是計算位數(shù)之和的函數(shù)process(),它將作為worker的工作任務(wù)之一。

func process(num int) int {
	sum := 0
	for num != 0 {
		digit := num % 10
		sum += digit
		num /= 10
	}
	time.Sleep(2 * time.Second)
	return sum
}

這個計算過程其實很簡單,但隨后還睡眠了2秒,用來假裝執(zhí)行一個計算任務(wù)是需要一點時間的。

然后是worker(),它監(jiān)聽tasks通道并取出任務(wù)進行計算,并將結(jié)果放進results通道。

func worker(wg *WaitGroup){
	defer wg.Done()
	for task := range tasks {
		result := Result{task, process(task.randnum)}
		results <- result
	}
}

上面的代碼很容易理解,只要tasks channel不關(guān)閉,就會一直監(jiān)聽該channel。需要注意的是,該函數(shù)使用指針類型的*WaitGroup作為參數(shù),不能直接使用值類型的WaitGroup作為參數(shù),這樣會使得每個worker都有一個自己的WaitGroup。

然后是創(chuàng)建工作池的函數(shù)createWorkerPool(),它有一個數(shù)值參數(shù),表示要創(chuàng)建多少個worker。

func createWorkerPool(numOfWorkers int) {
	var wg sync.WaitGroup
	for i := 0; i < numOfWorkers; i++ {
		wg.Add(1)
		go worker(&wg)
	}
	wg.Wait()
	close(results)
}

創(chuàng)建工作池時,首先創(chuàng)建一個WaitGroup的值wg,這個wg被工作池中的所有g(shù)oroutine共享,每創(chuàng)建一個goroutine都wg.Add(1)。創(chuàng)建完所有的goroutine后等待所有的groutine都執(zhí)行完它們的任務(wù),只要有一個任務(wù)還沒有執(zhí)行完,這個函數(shù)就會被Wait()阻塞。當(dāng)所有任務(wù)都執(zhí)行完成后,關(guān)閉results通道,因為沒有結(jié)果再需要向該通道寫了。

當(dāng)然,這里是否需要關(guān)閉results通道,是由稍后的range迭代這個通道決定的,不關(guān)閉這個通道會一直阻塞range,最終導(dǎo)致死鎖。

工作池部分已經(jīng)完成了?,F(xiàn)在需要使用allocate()函數(shù)分配任務(wù):生成一大堆的隨機數(shù),然后將Task放進tasks通道。該函數(shù)有一個代表創(chuàng)建任務(wù)數(shù)量的數(shù)值參數(shù):

func allocate(numOfTasks int) {
	for i := 0; i < numOfTasks; i++ {
		randnum := rand.Intn(999)
		task := Task{i, randnum}
		tasks <- task
	}
	close(tasks)
}

注意,最后需要關(guān)閉tasks通道,因為所有任務(wù)都分配完之后,沒有任務(wù)再需要分配。當(dāng)然,這里之所以需要關(guān)閉tasks通道,是因為worker()中使用了range迭代tasks通道,如果不關(guān)閉這個通道,worker將在取完所有任務(wù)后一直阻塞,最終導(dǎo)致死鎖。

再接著的是取出results通道中的結(jié)果進行輸出,函數(shù)名為getResult():

func getResult(done chan bool) {
	for result := range results {
		fmt.Printf("Task id %d, randnum %d , sum %d\n", result.task.id, result.task.randnum, result.result)
	}
	done <- true
}

getResult()中使用了一個done參數(shù),這個參數(shù)是一個信號通道,用來表示results中的所有結(jié)果都取出來并處理完成了,這個通道不一定要用bool類型,任何類型皆可,它不用來傳數(shù)據(jù),僅用來返回可讀,所以上面直接close(done)的效果也一樣。通過下面的main()函數(shù),就能理解done信號通道的作用。

最后還差main()函數(shù):

func main() {
	// 記錄起始終止時間,用來測試完成所有任務(wù)耗費時長
	startTime := time.Now()
	
	numOfWorkers := 20
	numOfTasks := 100
	// 創(chuàng)建任務(wù)到任務(wù)隊列中
	go allocate(numOfTasks)
	// 創(chuàng)建工作池
	go createWorkerPool(numOfWorkers)
	// 取得結(jié)果
	var done = make(chan bool)
	go getResult(done)

	// 如果results中還有數(shù)據(jù),將阻塞在此
	// 直到發(fā)送了信號給done通道
	<- done
	endTime := time.Now()
	diff := endTime.Sub(startTime)
	fmt.Println("total time taken ", diff.Seconds(), "seconds")
}

上面分配了20個worker,這20個worker總共需要處理的任務(wù)數(shù)量為100。但注意,無論是tasks還是results通道,容量都是10,意味著任務(wù)隊列最長只能是10個任務(wù)。

下面是完整的代碼段:

package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

type Task struct {
	id      int
	randnum int
}
type Result struct {
	task   Task
	result int
}

var tasks = make(chan Task, 10)
var results = make(chan Result, 10)

func process(num int) int {
	sum := 0
	for num != 0 {
		digit := num % 10
		sum += digit
		num /= 10
	}
	time.Sleep(2 * time.Second)
	return sum
}
func worker(wg *sync.WaitGroup) {
	defer wg.Done()
	for task := range tasks {
		result := Result{task, process(task.randnum)}
		results <- result
	}
}
func createWorkerPool(numOfWorkers int) {
	var wg sync.WaitGroup
	for i := 0; i < numOfWorkers; i++ {
		wg.Add(1)
		go worker(&wg)
	}
	wg.Wait()
	close(results)
}
func allocate(numOfTasks int) {
	for i := 0; i < numOfTasks; i++ {
		randnum := rand.Intn(999)
		task := Task{i, randnum}
		tasks <- task
	}
	close(tasks)
}
func getResult(done chan bool) {
	for result := range results {
		fmt.Printf("Task id %d, randnum %d , sum %d\n", result.task.id, result.task.randnum, result.result)
	}
	done <- true
}
func main() {
	startTime := time.Now()
	numOfWorkers := 20
	numOfTasks := 100

	var done = make(chan bool)
	go getResult(done)
	go allocate(numOfTasks)
	go createWorkerPool(numOfWorkers)
	// 必須在allocate()和getResult()之后創(chuàng)建工作池
	<-done
	endTime := time.Now()
	diff := endTime.Sub(startTime)
	fmt.Println("total time taken ", diff.Seconds(), "seconds")
}

執(zhí)行結(jié)果:

Task id 19, randnum 914 , sum 14
Task id 9, randnum 150 , sum 6
Task id 15, randnum 215 , sum 8
............
Task id 97, randnum 315 , sum 9
Task id 99, randnum 641 , sum 11
total time taken  10.0174705 seconds

總共花費10秒。

可以試著將任務(wù)數(shù)量、worker數(shù)量修改修改,看看它們的性能比例情況。例如,將worker數(shù)量設(shè)置為99,將需要4秒,將worker數(shù)量設(shè)置為10,將需要20秒。

更多關(guān)于創(chuàng)建GO線程池的問題請查看下面的相關(guān)鏈接

相關(guān)文章

  • golang 函數(shù)以及函數(shù)和方法的詳解及區(qū)別

    golang 函數(shù)以及函數(shù)和方法的詳解及區(qū)別

    這篇文章主要介紹了golang 函數(shù)以及函數(shù)和方法的區(qū)別的相關(guān)資料,需要的朋友可以參考下
    2017-05-05
  • Golang橋接模式講解和代碼示例

    Golang橋接模式講解和代碼示例

    橋接是一種結(jié)構(gòu)型設(shè)計模式,可將業(yè)務(wù)邏輯或一個大類拆分為不同的層次結(jié)構(gòu),從而能獨立地進行開發(fā),本文將通過代碼示例詳細給大家介紹一下Golang橋接模式,需要的朋友可以參考下
    2023-06-06
  • 一文帶你了解Go語言中接口的使用

    一文帶你了解Go語言中接口的使用

    這篇文章主要和大家分享一下Go語言中的接口的使用,文中的示例代碼講解詳細,對我們學(xué)習(xí)Go語言有一定的幫助,需要的小伙伴可以參考一下
    2022-12-12
  • Golang對MongoDB數(shù)據(jù)庫的操作簡單封裝教程

    Golang對MongoDB數(shù)據(jù)庫的操作簡單封裝教程

    mongodb官方?jīng)]有關(guān)于go的mongodb的驅(qū)動,因此只能使用第三方驅(qū)動,mgo就是使用最多的一種。下面這篇文章主要給大家介紹了關(guān)于利用Golang對MongoDB數(shù)據(jù)庫的操作簡單封裝的相關(guān)資料,需要的朋友可以參考下
    2018-07-07
  • Go語言中命令行參數(shù)解析工具pflag的使用指南

    Go語言中命令行參數(shù)解析工具pflag的使用指南

    在使用?Go?進行開發(fā)的過程中,命令行參數(shù)解析是我們經(jīng)常遇到的需求,于是?Go?社區(qū)中出現(xiàn)了一個叫?pflag?的第三方包,功能更加全面且足夠強大,下面我們就來看看它的具體使用吧
    2024-11-11
  • Go語言帶緩沖的通道的使用

    Go語言帶緩沖的通道的使用

    Go語言中有緩沖的通道是一種在被接收前能存儲一個或者多個值的通道,本文就來介紹一下Go語言帶緩沖的通道的使用,具有一定的參考價值,感興趣的可以了解一下
    2024-01-01
  • goland2020.2.x永久激活碼破解詳細教程親測可用(Windows Linux Mac)

    goland2020.2.x永久激活碼破解詳細教程親測可用(Windows Linux Mac)

    這篇文章主要介紹了goland2020.2.x永久激活碼破解詳細教程親測可用(Windows Linux Mac) ,對goland激活碼注冊碼相關(guān)知識感興趣的朋友跟隨小編一起看看吧
    2020-11-11
  • go-micro微服務(wù)domain層開發(fā)示例詳解

    go-micro微服務(wù)domain層開發(fā)示例詳解

    這篇文章主要為大家介紹了go-micro微服務(wù)domain層開發(fā)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-01-01
  • 使用go在mangodb中進行CRUD操作

    使用go在mangodb中進行CRUD操作

    這篇文章主要介紹了使用go在mangodb中進行CRUD操作,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-10-10
  • goland 設(shè)置注釋模板的過程圖文詳解

    goland 設(shè)置注釋模板的過程圖文詳解

    這篇文章主要介紹了goland 設(shè)置注釋模板的過程,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2020-12-12

最新評論