Objekt pool mönster - Object pool pattern

Den objekt pool mönstret är en programvara Skapande designmönster som använder en uppsättning initieras föremål hålls redo att använda - en " pool " - snarare än att fördela och förstöra dem på begäran. En klient i poolen kommer att begära ett objekt från poolen och utföra operationer på det returnerade objektet. När klienten är klar, returnerar den objektet till poolen istället för att förstöra det ; detta kan göras manuellt eller automatiskt.

Objektpooler används främst för prestanda: under vissa omständigheter förbättrar objektpooler prestanda avsevärt. Objektpooler komplicerar objektets livslängd , eftersom objekt som erhållits från och återförs till en pool inte skapas eller förstörs just nu och därför kräver omsorg vid implementeringen.

Beskrivning

När det är nödvändigt att arbeta med många objekt som är särskilt dyra att instansera och varje objekt bara behövs under en kort tid kan prestandan för en hel applikation påverkas negativt. Ett designmönster för objektpool kan anses önskvärt i fall som dessa.

Objektpoolens designmönster skapar en uppsättning objekt som kan återanvändas. När ett nytt objekt behövs, begärs det från poolen. Om ett tidigare förberett objekt är tillgängligt returneras det omedelbart och undviker instantieringskostnaden. Om inga objekt finns i poolen skapas ett nytt objekt och returneras. När objektet har använts och inte längre behövs, återförs det till poolen, så att det kan användas igen i framtiden utan att upprepa den beräkningsmässigt dyra instansprocessen. Det är viktigt att notera att när ett objekt har använts och returnerats kommer befintliga referenser att bli ogiltiga.

I vissa objektpooler är resurserna begränsade, så ett maximalt antal objekt specificeras. Om detta nummer nås och ett nytt objekt begärs kan ett undantag kastas, eller tråden blockeras tills ett objekt släpps tillbaka i poolen.

Objektpoolens designmönster används på flera ställen i standardklasserna i .NET Framework. Ett exempel är .NET Framework Data Provider för SQL Server. Eftersom SQL Server -databasanslutningar kan vara långsamma att skapa, upprätthålls en pool av anslutningar. Att stänga en anslutning avstår faktiskt inte länken till SQL Server. Istället hålls anslutningen i en pool, från vilken den kan hämtas när du begär en ny anslutning. Detta ökar avsevärt hastigheten på anslutningar.

Fördelar

Objektsamling kan ge en betydande prestandahöjning i situationer där kostnaden för att initiera en klassinstans är hög och graden av instansering och förstörelse av en klass är hög - i det här fallet kan objekt ofta återanvändas och varje återanvändning sparar en betydande mängd tid. Objektsamling kräver resurser - minne och eventuellt andra resurser, till exempel nätverksuttag, och därför är det att föredra att antalet instanser som används vid varje tillfälle är lågt, men detta krävs inte.

Det sammanslagna objektet erhålls i förutsägbar tid när skapandet av de nya objekten (särskilt över nätverket) kan ta variabel tid. Dessa fördelar gäller mest för objekt som är dyra med tiden, till exempel databasanslutningar, socketanslutningar, trådar och stora grafiska objekt som teckensnitt eller bitmappar.

I andra situationer är enkla objektpooler (som inte rymmer externa resurser, men bara upptar minne) kanske inte effektiva och kan minska prestanda. I händelse av enkel minnesansamling är plattan för allokering av minneshantering mer lämpad, eftersom det enda målet är att minimera kostnaden för minnesallokering och deallokation genom att minska fragmenteringen.

Genomförande

Objektpooler kan implementeras automatiskt på språk som C ++ via smarta pekare . I konstruktören för den smarta pekaren kan ett objekt begäras från poolen, och i destruktorn för den smarta pekaren kan objektet släppas tillbaka till poolen. På skräpsamlade språk, där det inte finns några destruktorer (som garanterat kommer att kallas som en del av en stapelvarvning), måste objektpooler implementeras manuellt genom att uttryckligen begära ett objekt från fabriken och returnera objektet genom att anropa en kassationsmetod (som i kassationsmönstret ). Att använda en finalizer för att göra detta är ingen bra idé, eftersom det vanligtvis inte finns några garantier för när (eller om) finaliseringen kommer att köras. Istället bör "försök ... äntligen" användas för att säkerställa att att få och släppa objektet är undantagsneutralt.

Manuella objektpooler är enkla att implementera, men svårare att använda, eftersom de kräver manuell minneshantering av poolobjekt.

Hantering av tomma pooler

Objektpooler använder en av tre strategier för att hantera en begäran när det inte finns några extra objekt i poolen.

  1. Det gick inte att tillhandahålla ett objekt (och returnera ett fel till klienten).
  2. Tilldela ett nytt objekt, vilket ökar poolens storlek. Pooler som gör detta brukar låta dig ställa in högt vattenmärke (det maximala antalet objekt som någonsin använts).
  3. I en multitrådad miljö kan en pool blockera klienten tills en annan tråd returnerar ett objekt till poolen.

Fallgropar

Var noga med att säkerställa att tillståndet för de föremål som återlämnas till poolen återställs till ett förnuftigt tillstånd för nästa användning av objektet, annars kan objektet vara i ett tillstånd som oväntat av klienten, vilket kan få det att misslyckas. Poolen ansvarar för att återställa objekten, inte klienterna. Objektpooler fulla av föremål med farligt gammalt tillstånd kallas ibland föremålsbrunnar och betraktas som ett antimönster .

Föråldrat tillstånd kanske inte alltid är ett problem; det blir farligt när det får föremålet att bete sig oväntat. Till exempel kan ett objekt som representerar autentiseringsinformation misslyckas om flaggan "framgångsrikt autentiserad" inte återställs innan den återanvänds, eftersom det indikerar att en användare är autentiserad (möjligen som någon annan) när de inte är det. Att inte återställa ett värde som endast används för felsökning, till exempel identiteten på den senaste autentiseringsservern som använts, kan dock inte orsaka några problem.

Otillräcklig återställning av objekt kan orsaka informationsläckage. Objekt som innehåller konfidentiell information (t.ex. en användares kreditkortsnummer) måste rensas innan de skickas till nya kunder, annars kan uppgifterna lämnas ut till en obehörig part.

Om poolen används av flera trådar kan den behöva medel för att förhindra parallella trådar från att försöka återanvända samma objekt parallellt. Detta är inte nödvändigt om de sammanslagna föremålen är oföränderliga eller på annat sätt trådsäkra.

Kritik

Vissa publikationer rekommenderar inte att du använder objektpooler med vissa språk, till exempel Java , särskilt för objekt som bara använder minne och inte har några externa resurser (t.ex. anslutningar till databasen). Motståndarna brukar säga att objekttilldelningen är relativt snabb i moderna språk med sopsamlare ; medan operatören newbehöver bara tio instruktioner, den klassiska new- deleteparet som finns i att samla mönster kräver hundratals av dem som det gör mer komplext arbete. De flesta sopsamlare skannar också "levande" objektreferenser, och inte det minne som dessa objekt använder för sitt innehåll. Detta innebär att valfritt antal "döda" föremål utan referenser kan kasseras med liten kostnad. Att hålla ett stort antal "levande" men oanvända föremål ökar däremot skräpinsamlingens varaktighet.

Exempel

Följande Go -kod initierar en resurspool av en viss storlek (samtidig initialisering) för att undvika problem med resurslopp genom kanaler, och i fallet med en tom pool, ställs timeout -bearbetning in för att förhindra att klienter väntar för länge.

// package pool
package pool

import (
	"errors"
	"log"
	"math/rand"
	"sync"
	"time"
)

const getResMaxTime = 3 * time.Second

var (
	ErrPoolNotExist  = errors.New("pool not exist")
	ErrGetResTimeout = errors.New("get resource time out")
)

//Resource
type Resource struct {
	resId int
}

//NewResource Simulate slow resource initialization creation
// (e.g., TCP connection, SSL symmetric key acquisition, auth authentication are time-consuming)
func NewResource(id int) *Resource {
	time.Sleep(500 * time.Millisecond)
	return &Resource{resId: id}
}

//Do Simulation resources are time consuming and random consumption is 0~400ms
func (r *Resource) Do(workId int) {
	time.Sleep(time.Duration(rand.Intn(5)) * 100 * time.Millisecond)
	log.Printf("using resource #%d finished work %d finish\n", r.resId, workId)
}

//Pool based on Go channel implementation, to avoid resource race state problem
type Pool chan *Resource

//New a resource pool of the specified size
// Resources are created concurrently to save resource initialization time
func New(size int) Pool {
	p := make(Pool, size)
	wg := new(sync.WaitGroup)
	wg.Add(size)
	for i := 0; i < size; i++ {
		go func(resId int) {
			p <- NewResource(resId)
			wg.Done()
		}(i)
	}
	wg.Wait()
	return p
}

//GetResource based on channel, resource race state is avoided and resource acquisition timeout is set for empty pool
func (p Pool) GetResource() (r *Resource, err error) {
	select {
	case r := <-p:
		return r, nil
	case <-time.After(getResMaxTime):
		return nil, ErrGetResTimeout
	}
}

//GiveBackResource returns resources to the resource pool
func (p Pool) GiveBackResource(r *Resource) error {
	if p == nil {
		return ErrPoolNotExist
	}
	p <- r
	return nil
}

// package main
package main

import (
	"github.com/tkstorm/go-design/creational/object-pool/pool"
	"log"
	"sync"
)

func main() {
	// Initialize a pool of five resources,
	// which can be adjusted to 1 or 10 to see the difference
	size := 5
	p := pool.New(size)

	// Invokes a resource to do the id job
	doWork := func(workId int, wg *sync.WaitGroup) {
		defer wg.Done()
		// Get the resource from the resource pool
		res, err := p.GetResource()
		if err != nil {
			log.Println(err)
			return
		}
		// Resources to return
		defer p.GiveBackResource(res)
		// Use resources to handle work
		res.Do(workId)
	}

	// Simulate 100 concurrent processes to get resources from the asset pool
	num := 100
	wg := new(sync.WaitGroup)
	wg.Add(num)
	for i := 0; i < num; i++ {
		go doWork(i, wg)
	}
	wg.Wait()
}

C#

I .NET Base Class Library finns några objekt som implementerar detta mönster. System.Threading.ThreadPoolär konfigurerad för att ha ett fördefinierat antal trådar att tilldela. När trådarna returneras är de tillgängliga för en annan beräkning. Således kan man använda trådar utan att betala kostnaden för att skapa och kassera trådar.

Följande visar den grundläggande koden för objektpoolens designmönster implementerat med C#. För korthet deklareras egenskaperna för klasserna med C# 3.0 automatiskt implementerad egenskapssyntax. Dessa kan ersättas med fullständiga fastighetsdefinitioner för tidigare versioner av språket. Poolen visas som en statisk klass, eftersom det är ovanligt att flera pooler krävs. Det är dock lika acceptabelt att använda instansklasser för objektpooler.

namespace DesignPattern.Objectpool 
{
    // The PooledObject class is the type that is expensive or slow to instantiate,
    // or that has limited availability, so is to be held in the object pool.
    public class PooledObject
    {
        private DateTime _createdAt = DateTime.Now;
 
        public DateTime CreatedAt
        {
            get { return _createdAt; }
        }
 
        public string TempData { get; set; }
    }

    // The Pool class controls access to the pooled objects. It maintains a list of available objects and a 
    // collection of objects that have been obtained from the pool and are in use. The pool ensures that released objects 
    // are returned to a suitable state, ready for reuse. 
    public static class Pool
    {
        private static List<PooledObject> _available = new List<PooledObject>();
        private static List<PooledObject> _inUse = new List<PooledObject>();
 
        public static PooledObject GetObject()
        {
            lock(_available)
            {
                if (_available.Count != 0)
                {
                    PooledObject po = _available[0];
                    _inUse.Add(po);
                    _available.RemoveAt(0);
                    return po;
                }
                else
                {
                    PooledObject po = new PooledObject();
                    _inUse.Add(po);
                    return po;
                }
            }
        }
 
        public static void ReleaseObject(PooledObject po)
        {
            CleanUp(po);
 
            lock (_available)
            {
                _available.Add(po);
                _inUse.Remove(po);
            }
        }
 
        private static void CleanUp(PooledObject po)
        {
            po.TempData = null;
        }
    }
}

I koden ovan har PooledObject egenskaper för den tid den skapades, och en annan, som kan ändras av klienten, som återställs när PooledObject släpps tillbaka till poolen. Rengöringsprocessen visas när ett objekt släpps och ser till att det är i ett giltigt tillstånd innan det kan begäras från poolen igen.

Java

Java stöder trådpooling via java.util.concurrent.ExecutorServiceoch andra relaterade klasser. Exekutörstjänsten har ett visst antal "grundläggande" trådar som aldrig kastas. Om alla trådar är upptagna tilldelar tjänsten det tillåtna antalet extra trådar som senare kasseras om de inte används under viss utgångstid. Om inga fler trådar är tillåtna kan uppgifterna placeras i kön. Slutligen, om den här kön kan bli för lång kan den konfigureras att avbryta den begärande tråden.

public class PooledObject {
	public String temp1;
	public String temp2;
	public String temp3;
	
	public String getTemp1() {
		return temp1;
	}
	public void setTemp1(String temp1) {
		this.temp1 = temp1;
	}
	public String getTemp2() {
		return temp2;
	}
	public void setTemp2(String temp2) {
		this.temp2 = temp2;
	}
	public String getTemp3() {
		return temp3;
	}
	public void setTemp3(String temp3) {
		this.temp3 = temp3;
	}
}
public class PooledObjectPool {
	private static long expTime = 6000;//6 seconds
	public static HashMap<PooledObject, Long> available = new HashMap<PooledObject, Long>();
	public static HashMap<PooledObject, Long> inUse = new HashMap<PooledObject, Long>();
	
	public synchronized static PooledObject getObject() {
		long now = System.currentTimeMillis();
		if (!available.isEmpty()) {
			for (Map.Entry<PooledObject, Long> entry : available.entrySet()) {
				if (now - entry.getValue() > expTime) { //object has expired
					popElement(available);
				} else {
					PooledObject po = popElement(available, entry.getKey());
					push(inUse, po, now); 
					return po;
				}
			}
		}

		// either no PooledObject is available or each has expired, so return a new one
		return createPooledObject(now);
	}	
	
	private synchronized static PooledObject createPooledObject(long now) {
		PooledObject po = new PooledObject();
		push(inUse, po, now);
		return po;
    }

	private synchronized static void push(HashMap<PooledObject, Long> map,
			PooledObject po, long now) {
		map.put(po, now);
	}

	public static void releaseObject(PooledObject po) {
		cleanUp(po);
		available.put(po, System.currentTimeMillis());
		inUse.remove(po);
	}
	
	private static PooledObject popElement(HashMap<PooledObject, Long> map) {
		 Map.Entry<PooledObject, Long> entry = map.entrySet().iterator().next();
		 PooledObject key= entry.getKey();
		 //Long value=entry.getValue();
		 map.remove(entry.getKey());
		 return key;
	}
	
	private static PooledObject popElement(HashMap<PooledObject, Long> map, PooledObject key) {
		map.remove(key);
		return key;
	}
	
	public static void cleanUp(PooledObject po) {
		po.setTemp1(null);
		po.setTemp2(null);
		po.setTemp3(null);
	}
}

Se även

Anteckningar

Referenser

externa länkar