Go語言之併發示例 Pool 一

2021-09-20 20:48:04 字數 3418 閱讀 2307

這篇文章演示使用有緩衝的通道實現乙個資源池,這個資源池可以管理在任意多個goroutine之間共享的資源,比如網路連線、資料庫連線等,我們在資料庫操作的時候,比較常見的就是資料連線池,也可以基於我們實現的資源池來實現。

可以看出,資源池也是一種非常流暢性的模式,這種模式一般適用於在多個goroutine之間共享資源,每個goroutine可以從資源池裡申請資源,使用完之後再放回資源池裡,以便其他goroutine復用。

好了,老規矩,我們先構建乙個資源池結構體,然後再賦予一些方法,這個資源池就可以幫助我們管理資源了。

//乙個安全的資源池,被管理的資源必須都實現io.close介面

type

pool

struct

這個結構體

pool

有四個字段,其中

m是乙個互斥鎖,這主要是用來保證在多個goroutine訪問資源時,池內的值是安全的。

res欄位是乙個有緩衝的通道,用來儲存共享的資源,這個通道的大小,在初始化

pool

的時候就指定的。注意這個通道的型別是

io.closer

介面,所以實現了這個

io.closer

介面的型別都可以作為資源,交給我們的資源池管理。

factory

這個是乙個函式型別,它的作用就是當需要乙個新的資源時,可以通過這個函式建立,也就是說它是生成新資源的,至於如何生成、生成什麼資源,是由使用者決定的,所以這也是這個資源池靈活的設計的地方。

closed

字段表示資源池是否被關閉,如果被關閉的話,再訪問是會有錯誤的。

現在先這個資源池我們已經定義好了,也知道了每個欄位的含義,下面就開時具體使用。剛剛我們說到關閉錯誤,那麼我們就先定義乙個資源池已經關閉的錯誤。

var errpoolclosed = errors.new("資源池已經關閉。")

非常簡潔,當我們從資源池獲取資源的時候,如果該資源池已經關閉,那麼就會返回這個錯誤。單獨定義它的目的,是和其他錯誤有乙個區分,這樣需要的時候,我們就可以從眾多的

error

型別裡區分出來這個

errpoolclosed。

下面我們就該為建立

pool

專門定乙個函式了,這個函式就是工廠函式,我們命名為

new。

//建立乙個資源池

func

new(

fn func()(

io.closer

,error

),size

uint)(*

pool

,error

)return

&pool

,nil

}

這個函式建立乙個資源池,它接收兩個引數,乙個

fn是建立新資源的函式;還有乙個

size

是指定資源池的大小。

這個函式裡,做了

size

大小的判斷,起碼它不能小於或者等於 0 ,否則就會返回錯誤。如果引數正常,就會使用

size

建立乙個有緩衝的通道,來儲存資源,並且返回乙個資源池的指標。

有了建立好的資源池,那麼我們就可以從中獲取資源了。

//從資源池裡獲取乙個資源

func (p

*pool

)acquire()(

io.closer

,error

)returnr,

nildefault

:log

.println

("acquire:新生成資源"

)returnp.

factory()}

}

acquire

方法可以從資源池獲取資源,如果沒有資源,則呼叫

factory

方法生成乙個並返回。

這裡同樣使用了

select

的多路復用,因為這個函式不能阻塞,可以獲取到就獲取,不能就生成乙個。

這裡的新知識是通道接收的多參返回,如果可以接收的話,第一引數是接收的值,第二個表示通道是否關閉。例子中如果

ok值為

false

表示通道關閉,如果為

true

則表示通道正常。所以我們這裡做了乙個判斷,如果通道關閉的話,返回通道關閉錯誤。

有獲取資源的方法,必然還有對應的釋放資源的方法,因為資源用完之後,要還給資源池,以便復用。在講解釋放資源的方法前,我們先看下關閉資源池的方法,因為釋放資源的方法也會用到它。

關閉資源池,意味著整個資源池不能再被使用,然後關閉存放資源的通道,同時釋放通道裡的資源。

//關閉資源池,釋放資源

func (p

*pool

)close()p

.closed

=true

//關閉通道,不讓寫入了

close(p

.res

)//關閉通道裡的資源

forr

:=range p

.res

}

這個方法裡,我們使用了互斥鎖,因為有個標記資源池是否關閉的字段

closed

需要再多個goroutine操作,所以我們必須保證這個欄位的同步。這裡把關閉標誌置為

true。

然後我們關閉通道,不讓寫入了,而且我們前面的

acquire

也可以感知到通道已經關閉了。同比通道後,就開始釋放通道中的資源,因為所有資源都實現了io.closer介面,所以我們直接呼叫

close

方法釋放資源即可。

關閉方法有了,我們看看釋放資源的方法如何實現。

func (p 

*pool

)release

(r io

.closer

)select

}

釋放資源本質上就會把資源再傳送到緩衝通道中,就是這麼簡單,不過為了更安全的實現這個方法,我們使用了互斥鎖,保證

closed

標誌的安全,而且這個互斥鎖還有乙個好處,就是不會往乙個已經關閉的通道傳送資源。

這是為什麼呢?因為close和release這兩個方法是互斥的,close方法裡對

closed

標誌的修改,release方法可以感知到,所以就直接return了,不會執行下面的select**了,也就不會往乙個已經關閉的通道裡傳送資源了。

如果資源池沒有被關閉,則繼續嘗試往資源通道傳送資源,如果可以傳送,就等於資源又回到資源池裡了;如果傳送不了,說明資源池滿了,該資源就無法重新回到資源池裡,那麼我們就把這個需要釋放的資源關閉,拋棄了。

Go語言之併發程式設計(一)

輕量級執行緒 goroutine 雖然,執行緒池為邏輯編寫者提供了執行緒分配的抽象機制。但是,如果面對隨時隨地可能發生的併發和執行緒處理需求,執行緒池就不是非常直觀和方便了。能否有一種機制 使用者分配足夠多的任務,系統能自動幫助使用者把任務分配到cpu上,讓這些任務盡量併發運作。這種機制在go語言中...

Go語言之併發資源競爭

併發本身並不複雜,但是因為有了資源競爭的問題,就使得我們開發出好的併發程式變得複雜起來,因為會引起很多莫名其妙的問題。package main import fmt runtime sync var count int32 wg sync.waitgroup func main func incco...

Go語言之併發程式設計(四)

同步 go 程式可以使用通道進行多個 goroutine 間的資料交換,但這僅僅是資料同步中的一種方法。通道內部的實現依然使用了各種鎖,因此優雅 的代價是效能。在某些輕量級的場合,原子訪問 atomic包 互斥鎖 sync.mutex 以及等待組 sync.waitgroup 能最大程度滿足需求。當...