Golang 深入淺出協程池設計

使用Go語言實現併發的協程調度池閹割版,本文主要介紹協程池的基本設計思路,目的爲深入淺出快速瞭解協程池工作原理,與真實的企業協程池還有很大差距,本文僅供學習參考。

一、何爲併發,Go又是如何實現併發?

gopool1.jpeg

gopool2.jpeg

並行的好處:

  1. 同一時刻可以處理多個事務
  1. 更加節省時間,效率更高

具有並行處理能力的程序我們稱之爲“併發程序”

併發程序的處理能力優勢體現在哪裏?

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()

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章