使用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()
}