通常、高並行性シナリオでアプリケーションを構築するためにgolangを使用しますが、golangの組み込みGCメカニズムはアプリケーションのパフォーマンスに影響を与えるため、GCを削減するために、golangはオブジェクトの再利用のためのメカニズム、つまりsync.Poolオブジェクトプールを提供します。sync.Poolはスケーラブルであり、同時に安全です。そのサイズはメモリのサイズによってのみ制限され、再利用可能なオブジェクトの値のコンテナと見なすことができます。設計の目的は、割り当てられているが一時的に使用されておらず、必要なときにプールから直接取得されるオブジェクトを格納することです。
任意のストレージ領域の値は、いつでも予告なく削除でき、高負荷のもとで動的に拡張できます。オブジェクトプールは、非アクティブのときに縮小されます。
sync.Poolは最初に2つの構造を宣言します
// Local per-P Pool appendix.
type poolLocalInternal struct {
private interface{} // Can be used only by the respective P.
shared []interface{} // Can be used by any P.
Mutex // Protects shared.
}
type poolLocal struct {
poolLocalInternal
// Prevents false sharing on widespread platforms with
// 128 mod (cache line size) = 0 .
pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}
複数のgoroutineでgoroutineを効率的に使用するために、sync.Poolは各P(CPUに対応)にローカルプールを割り当てます。getまたはput操作を実行すると、goroutineは最初にPサブプールに関連付けられます、そしてサブプールを操作します。各Pのサブプールは、プライベートオブジェクトと共有リストオブジェクトに分かれています。プライベートオブジェクトには特定のPのみがアクセスでき、共有リストオブジェクトは任意のPがアクセスできます。Pは一度に1つのゴルーチンのみを実行できるため、ロックする必要はありませんが、共有リストオブジェクトを操作する場合は、同時に動作する複数のゴルーチンが存在する可能性があるため、ロックする必要があります。
poolLocal構造にパッドメンバーがあることは注目に値します。目的は、誤った共有を防ぐことです。キャッシュの使用における一般的な問題は、誤った共有です。異なるスレッドが同じキャッシュラインで異なるデータを同時に読み書きすると、誤った共有が発生する可能性があります。誤って共有すると、マルチコアプロセッサでシステムパフォーマンスが著しく低下する可能性があります。詳しくは、フォールスシェアリングをご覧ください。
タイプsync.Poolには2つのパブリックメソッドがあり、1つはGetで、もう1つはPutです。まず、Putのソースコードを見てみましょう。
- 値が空の場合は、直接戻ります。
- 現在のゴルーチンがオブジェクトプールのプライベート値を設定しているかどうかを確認し、設定していない場合は、xをそのプライベートメンバーに割り当て、xをnilに設定します。
- 現在のgoroutineプライベート値が設定されている場合、その値は共有リストに追加されます。
// Put adds x to the pool.
func (p *Pool) Put(x interface{}) {
if x == nil {
return
}
if race.Enabled {
if fastrand()%4 == 0 {
// Randomly drop x on floor.
return
}
race.ReleaseMerge(poolRaceAddr(x))
race.Disable()
}
l := p.pin()
if l.private == nil {
l.private = x
x = nil
}
runtime_procUnpin()
if x != nil {
l.Lock()
l.shared = append(l.shared, x)
l.Unlock()
}
if race.Enabled {
race.Enable()
}
}
- ローカルPに対応するローカルプールからオブジェクト値を取得し、ローカルプールから値を削除してください。
- 取得に失敗した場合は、共有プールから取得し、共有キューから値を削除します。
- 取得に失敗した場合は、他のPの共有プールから1を盗み、共有プールの値を削除します(p.getSlow())。
- それでも失敗する場合は、New()を介して戻り値を直接割り当てます。この割り当てられた値はプールに入れられないことに注意してください。New()は、ユーザーが登録したNew関数の値を返します。ユーザーがNewを登録していない場合は、nilを返します。
func (p *Pool) Get() interface{} {
if race.Enabled {
race.Disable()
}
l := p.pin()
x := l.private
l.private = nil
runtime_procUnpin()
if x == nil {
l.Lock()
last := len(l.shared) - 1
if last >= 0 {
x = l.shared[last]
l.shared = l.shared[:last]
}
l.Unlock()
if x == nil {
x = p.getSlow()
}
}
if race.Enabled {
race.Enable()
if x != nil {
race.Acquire(poolRaceAddr(x))
}
}
if x == nil && p.New != nil {
x = p.New()
}
return x
}
init関数
初期化中にPoolCleanup関数を登録し、彼はsync.Pool内のすべてのキャッシュされたオブジェクトをクリアします。この登録関数はGCのたびに実行されるため、sync.Poolの値は2つのGCの中間にのみあります。期間は有効です。
func init() {
runtime_registerPoolCleanup(poolCleanup)
}
例:
package main
import (
"sync"
"time"
"fmt"
)
var bytePool = sync.Pool{
New: func() interface{} {
b := make([]byte, 1024)
return &b
},
}
func main() {
//defer
//debug.SetGCPercent(debug.SetGCPercent(-1))
a := time.Now().Unix()
for i:=0;i<1000000000;i++{
obj := make([]byte, 1024)
_ = obj
}
b := time.Now().Unix()
for j:=0;j<1000000000;j++ {
obj := bytePool.Get().(*[]byte)
_ = obj
bytePool.Put(obj)
}
c := time.Now().Unix()
fmt.Println("without pool ", b - a, "s")
fmt.Println("with pool ", c - b, "s")
}
共有リストが長すぎて時間がかかるため、GCのパフォーマンスへの影響はほとんどないことがわかります。
要約:
上記の解釈を通じて、我々は、参照方法を取得することができ、オブジェクトの値の保証のいずれかの操作を実行するために取得することはできません値はローカルプールに配置されているので、いつでも削除することができるが、発信者を通知することはありません。共有プールに入れられた値は、他のゴルーチンによって盗まれる可能性があります。したがって、オブジェクトプールは、状態にとらわれない一時的なデータの保存には適していますが、データベース接続インスタンスの保存には適していません。オブジェクトプールに保存されている値は、ガベージコレクション中に削除され、データベースに違反する可能性があるためです。接続プール確立の当初の意図。
上記のステートメントによると、Golangのオブジェクトプールは厳密には一時オブジェクトプールであり、goroutine間で共有される一時オブジェクトを格納するのに適しています。主な機能は、GCを削減してパフォーマンスを向上させることです。Golangでの最も一般的な使用シナリオは、fmtパッケージの出力バッファーです。
Golangで接続プーリングの効果を実現したい場合は、コンテナ/リストを使用して実現できます。オープンソースコミュニティには、go-commons-poolなどの既成の実装もあります。特定の読者は、それを自分で理解することができます。