Pe această pagină
Salut 👋
În acest articol vorbim despre pattern-ul Object Pool în Golang.
Pattern-ul Object Pool este un design pattern folosit în situațiile în care construirea obiectelor este o operație costisitoare, de exemplu construirea unui obiect HTTPClient sau DatabaseClient poate dura ceva timp.
Printr-un pool de resurse, resursele sunt cerute din pool atunci când sunt necesare și apoi returnate când nu mai sunt folosite, ca să poată fi reutilizate mai târziu.
Programele pot beneficia de acest pattern pentru că, odată construit obiectul, când îl vei mai avea nevoie vei lua doar o instanță existentă, în loc să-l construiești din nou de la zero.
În Golang acest pattern se implementează ușor cu sync.Pool. Având o structură Resource, pentru a implementa un object pool trebuie să transmitem funcția NewResource către pool.
Pentru a urmări câte instanțe active avem ale obiectului Resource, folosim variabila counter.
Resource
var logger = log.Default()var counter = 0 type Resource struct { id string} func NewResource() *Resource { logger.Printf("NewResource called") counter += 1 return &Resource{id: fmt.Sprintf("Resource-%d", counter)}} func (r *Resource) doWork() { logger.Printf("%s doing work", r.id)} Hai să demonstrăm sync.Pool!
Demo 1️⃣
În primul demo, luăm resursa din pool, facem o muncă oarecare și apoi o punem înapoi. Procedând pas cu pas, la final vom avea doar o singură instanță Resource.
func demo1() { println("demo1") theResourcePool := sync.Pool{New: func() any { return NewResource() }} for i := 0; i < 10; i++ { item := theResourcePool.Get().(*Resource) item.doWork() theResourcePool.Put(item) } println("done", counter)}Output
demo12022/08/17 22:38:59 NewResource called2022/08/17 22:38:59 Resource-1 doing work2022/08/17 22:38:59 Resource-1 doing work2022/08/17 22:38:59 Resource-1 doing work2022/08/17 22:38:59 Resource-1 doing work2022/08/17 22:38:59 Resource-1 doing work2022/08/17 22:38:59 Resource-1 doing work2022/08/17 22:38:59 Resource-1 doing work2022/08/17 22:38:59 Resource-1 doing work2022/08/17 22:38:59 Resource-1 doing work2022/08/17 22:38:59 Resource-1 doing workdone 1Resource-1 este singura instanță care lucrează.
Demo 2️⃣
În demo2 pornim 10 goroutine-uri care folosesc pool-ul. Deoarece toate goroutine-urile pornesc aproximativ în același timp și au nevoie de o resursă ca să apeleze doWork, la final pool-ul va avea 10 instanțe Resource.
func demo2() { println("demo2") wg := sync.WaitGroup{} theResourcePool := sync.Pool{New: func() any { return NewResource() }} for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() item := theResourcePool.Get().(*Resource) item.doWork() theResourcePool.Put(item) }() } wg.Wait() println("done", counter)}Output
demo22022/08/17 22:41:12 NewResource called2022/08/17 22:41:12 NewResource called2022/08/17 22:41:12 NewResource called2022/08/17 22:41:12 Resource-3 doing work2022/08/17 22:41:12 NewResource called2022/08/17 22:41:12 Resource-4 doing work2022/08/17 22:41:12 NewResource called2022/08/17 22:41:12 Resource-5 doing work2022/08/17 22:41:12 NewResource called2022/08/17 22:41:12 Resource-6 doing work2022/08/17 22:41:12 NewResource called2022/08/17 22:41:12 Resource-7 doing work2022/08/17 22:41:12 NewResource called2022/08/17 22:41:12 Resource-8 doing work2022/08/17 22:41:12 NewResource called2022/08/17 22:41:12 NewResource called2022/08/17 22:41:12 Resource-1 doing work2022/08/17 22:41:12 Resource-2 doing work2022/08/17 22:41:12 Resource-9 doing work2022/08/17 22:41:12 Resource-10 doing workdone 10Demo 3️⃣
În demo3 facem același lucru ca în demo2, dar cu pauze aleatorii între pași: unele goroutine-uri sunt mai rapide, altele mai lente. Goroutine-urile mai rapide vor returna resursa mai repede în pool, iar cele mai lente, care pornesc mai târziu, vor refolosi resursa în loc să creeze una nouă.
func demo3() { println("demo2") wg := sync.WaitGroup{} theResourcePool := sync.Pool{New: func() any { return NewResource() }} for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() time.Sleep(time.Duration(rand.Intn(900)+100) * time.Millisecond) item := theResourcePool.Get().(*Resource) item.doWork() time.Sleep(time.Duration(rand.Intn(100)+100) * time.Millisecond) theResourcePool.Put(item) }() } wg.Wait() println("done", counter)}Output
demo22022/08/17 22:42:35 NewResource called2022/08/17 22:42:35 Resource-1 doing work2022/08/17 22:42:35 NewResource called2022/08/17 22:42:35 Resource-2 doing work2022/08/17 22:42:35 NewResource called2022/08/17 22:42:35 Resource-3 doing work2022/08/17 22:42:36 Resource-1 doing work2022/08/17 22:42:36 Resource-2 doing work2022/08/17 22:42:36 Resource-3 doing work2022/08/17 22:42:36 Resource-1 doing work2022/08/17 22:42:36 NewResource called2022/08/17 22:42:36 Resource-4 doing work2022/08/17 22:42:36 NewResource called2022/08/17 22:42:36 Resource-5 doing work2022/08/17 22:42:36 Resource-2 doing workdone 5În acest moment au fost create doar 5 instanțe Resource.
Concluzie
Pattern-ul object pool este foarte util atunci când trebuie să reutilizezi o instanță a unui obiect. Construirea obiectului de fiecare dată poate fi lentă.
În Go avem sync.pool, care implementează pentru noi pattern-ul Object Pool; trebuie doar să-i oferim o funcție New care returnează un pointer.
Mulțumesc că ai citit! 📚
Referințe
Cod complet
package mainimport ( "fmt" "log" "math/rand" "sync" "time")var logger = log.Default()var counter = 0type Resource struct { id string}func NewResource() *Resource { logger.Printf("NewResource called") counter += 1 return &Resource{id: fmt.Sprintf("Resource-%d", counter)}}func (r *Resource) doWork() { logger.Printf("%s doing work", r.id)}func demo1() { println("demo1") theResourcePool := sync.Pool{New: func() any { return NewResource() }} for i := 0; i < 10; i++ { item := theResourcePool.Get().(*Resource) item.doWork() theResourcePool.Put(item) } println("done", counter)}func demo2() { println("demo2") wg := sync.WaitGroup{} theResourcePool := sync.Pool{New: func() any { return NewResource() }} for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() item := theResourcePool.Get().(*Resource) item.doWork() theResourcePool.Put(item) }() } wg.Wait() println("done", counter)}func demo3() { println("demo2") wg := sync.WaitGroup{} theResourcePool := sync.Pool{New: func() any { return NewResource() }} for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() time.Sleep(time.Duration(rand.Intn(900)+100) * time.Millisecond) item := theResourcePool.Get().(*Resource) item.doWork() time.Sleep(time.Duration(rand.Intn(100)+100) * time.Millisecond) theResourcePool.Put(item) }() } wg.Wait() println("done", counter)}func main() { demo1() //demo2() //demo3()}