深入淺出Golang的協程池設計
教程製作
: IT無崖子(劉丹冰)
教程簡介
:本教程主要針對具有一定程式設計基礎的學員,懂得基本的程式設計語法。
使用Go語言實現併發的協程排程池閹割版,本文主要介紹協程池的基本設計思路,目的為深入淺出快速瞭解協程池工作原理,與真實的企業協程池還有很大差距,本文僅供學習參考。
一、何為併發,Go又是如何實現併發?

gopool1.jpeg

gopool2.jpeg
並行的好處:
- 同一時刻可以處理多個事務
- 更加節省時間,效率更高
具有並行處理能力的程式我們稱之為“併發程式”
併發程式的處理能力優勢體現在哪裡?

goPool3.jpeg
二、Go語言如何實現併發?
package main import "fmt" import "time" func go_worker(name string) { for i := 0; i < 10; i++ { fmt.Println("我是一個go協程, 我的名字是 ", name, "----") time.Sleep(1 * time.Second) } fmt.Println(name, " 執行完畢!") } func main() { go go_worker("小黑")//建立一個goroutine協程去執行 go_worker("小黑") go go_worker("小白")//建立一個goroutine協程去執行 go_worker("小白") //防止main函式執行完畢,程式退出 for { time.Sleep(1 * time.Second) } }
那麼多個goroutine之前如何通訊呢?
package main import "fmt" func worker(c chan int) { //從channel中去讀資料 num := <-c fmt.Println("foo recv channel ", num) } func main() { //建立一個channel c := make(chan int) go worker(c) //main協程 向一個channel中寫資料 c <- 1 fmt.Println("send 1 -> channel over") }
三、協程池的設計思路
為什麼需要協程池?
雖然go語言在排程Goroutine已經優化的非常完成,並且Goroutine作為輕量級執行流程,也不需要CPU排程器的切換,我們一般在使用的時候,如果想處理一個分支流程,直接 go
一下即可。
但是,如果無休止的開闢Goroutine依然會出現高頻率的排程Groutine,那麼依然會浪費很多上下文切換的資源,導致做無用功。所以設計一個Goroutine池限制Goroutine的開闢個數在大型併發場景還是必要的。
四、快速實現併發協程通訊池
package main import ( "fmt" "time" ) /* 有關Task任務相關定義及操作 */ //定義任務Task型別,每一個任務Task都可以抽象成一個函式 type Task struct { f func() error //一個無參的函式型別 } //通過NewTask來建立一個Task func NewTask(f func() error) *Task { t := Task{ f: f, } return &t } //執行Task任務的方法 func (t *Task) Execute() { t.f() //呼叫任務所繫結的函式 } /* 有關協程池的定義及操作 */ //定義池型別 type Pool struct { //對外接收Task的入口 EntryChannel chan *Task //協程池最大worker數量,限定Goroutine的個數 worker_num int //協程池內部的任務就緒佇列 JobsChannel chan *Task } //建立一個協程池 func NewPool(cap int) *Pool { p := Pool{ EntryChannel: make(chan *Task), worker_num:cap, JobsChannel:make(chan *Task), } return &p } //協程池建立一個worker並且開始工作 func (p *Pool) worker(work_ID int) { //worker不斷的從JobsChannel內部任務佇列中拿任務 for task := range p.JobsChannel { //如果拿到任務,則執行task任務 task.Execute() fmt.Println("worker ID ", work_ID, " 執行完畢任務") } } //讓協程池Pool開始工作 func (p *Pool) Run() { //1,首先根據協程池的worker數量限定,開啟固定數量的Worker, //每一個Worker用一個Goroutine承載 for i := 0; i < p.worker_num; i++ { go p.worker(i) } //2, 從EntryChannel協程池入口取外界傳遞過來的任務 //並且將任務送進JobsChannel中 for task := range p.EntryChannel { p.JobsChannel <- task } //3, 執行完畢需要關閉JobsChannel close(p.JobsChannel) //4, 執行完畢需要關閉EntryChannel close(p.EntryChannel) } //主函式 func main() { //建立一個Task t := NewTask(func() error { fmt.Println(time.Now()) return nil }) //建立一個協程池,最大開啟3個協程worker p := NewPool(3) //開一個協程 不斷的向 Pool 輸送列印一條時間的task任務 go func() { for { p.EntryChannel <- t } }() //啟動協程池p p.Run() }
五、獲取更多 Go語言
與 區塊鏈
相關學習資料
QQ技術討論群:

gopool5.jpeg