Nesne havuzu deseni - Object pool pattern

Nesne havuzu model bir yazılımdır yaradılış tasarım deseni başlatıldı bir dizi kullandığı nesnelere kullanıma hazır tutulması - Bir " havuz " - ziyade tahsis ve talep üzerine onları yok daha. Havuzun bir istemcisi, havuzdan bir nesne talep edecek ve döndürülen nesne üzerinde işlemler gerçekleştirecektir. İstemci işini bitirdiğinde, nesneyi yok etmek yerine havuza geri döndürür ; bu manuel veya otomatik olarak yapılabilir.

Nesne havuzları öncelikle performans için kullanılır: bazı durumlarda nesne havuzları performansı önemli ölçüde artırır. Nesne havuzları , bir havuzdan elde edilen ve bir havuza döndürülen nesneler şu anda fiilen oluşturulmadığından veya yok edilmediğinden ve dolayısıyla uygulamada dikkat gerektirdiğinden, nesne ömrünü karmaşıklaştırır .

Açıklama

Örneklenmesi özellikle pahalı olan çok sayıda nesneyle çalışmak gerektiğinde ve her nesneye yalnızca kısa bir süre için ihtiyaç duyulduğunda, tüm uygulamanın performansı olumsuz etkilenebilir. Bu gibi durumlarda bir nesne havuzu tasarım deseni istenebilir.

Nesne havuzu tasarım deseni, yeniden kullanılabilecek bir dizi nesne oluşturur. Yeni bir nesneye ihtiyaç duyulduğunda havuzdan istenir. Önceden hazırlanmış bir nesne mevcutsa, örnekleme maliyetinden kaçınarak hemen iade edilir. Havuzda hiçbir nesne yoksa, yeni bir öğe oluşturulur ve döndürülür. Nesne kullanıldığında ve artık ihtiyaç duyulmadığında, havuza geri döndürülerek, hesaplama açısından pahalı örnekleme sürecini tekrarlamadan gelecekte tekrar kullanılmasına izin verilir. Bir nesne kullanılıp döndürüldüğünde, mevcut referansların geçersiz hale geleceğini unutmamak önemlidir.

Bazı nesne havuzlarında kaynaklar sınırlıdır, bu nedenle maksimum sayıda nesne belirtilir. Bu sayıya ulaşılırsa ve yeni bir öğe istenirse, bir istisna atılabilir veya bir nesne havuza geri bırakılana kadar iş parçacığı engellenir.

Nesne havuzu tasarım deseni, .NET Framework'ün standart sınıflarında çeşitli yerlerde kullanılır. Bir örnek, SQL Server için .NET Framework Veri Sağlayıcısıdır. SQL Server veritabanı bağlantılarının oluşturulması yavaş olabileceğinden, bir bağlantı havuzu korunur. Bir bağlantıyı kapatmak, aslında SQL Server bağlantısını bırakmaz. Bunun yerine bağlantı, yeni bir bağlantı istenirken alınabileceği bir havuzda tutulur. Bu, bağlantı kurma hızını önemli ölçüde artırır.

Faydalar

Nesne havuzu oluşturma, bir sınıf örneğini başlatma maliyetinin yüksek olduğu ve bir sınıfın örneklenme ve yok edilme hızının yüksek olduğu durumlarda önemli bir performans artışı sağlayabilir - bu durumda nesneler sıklıkla yeniden kullanılabilir ve her yeniden kullanım önemli miktarda tasarruf sağlar. zaman. Nesne havuzlama kaynakları gerektirir – bellek ve muhtemelen ağ soketleri gibi diğer kaynaklar ve bu nedenle herhangi bir anda kullanımda olan örnek sayısının düşük olması tercih edilir, ancak bu gerekli değildir.

Havuza alınan nesne, yeni nesnelerin oluşturulmasının (özellikle ağ üzerinden) değişken zaman alabileceği durumlarda öngörülebilir bir sürede elde edilir. Bu faydalar çoğunlukla veritabanı bağlantıları, soket bağlantıları, iş parçacıkları ve yazı tipleri veya bitmapler gibi büyük grafik nesneleri gibi zamana göre pahalı olan nesneler için geçerlidir.

Diğer durumlarda, basit nesne havuzu (harici kaynak tutmayan, ancak yalnızca belleği kaplayan) verimli olmayabilir ve performansı düşürebilir. Basit bellek havuzu oluşturma durumunda , tek amaç, parçalanmayı azaltarak bellek tahsisi ve serbest bırakma maliyetini en aza indirmek olduğundan , döşeme tahsisi bellek yönetimi tekniği daha uygundur.

uygulama

Nesne havuzları, akıllı işaretçiler aracılığıyla C++ gibi dillerde otomatik bir şekilde uygulanabilir . Akıllı işaretçinin yapıcısında havuzdan bir nesne istenebilir ve akıllı işaretçinin yıkıcısında nesne havuza geri bırakılabilir. (Yığın bir çile çözme mekanizması parçası olarak çağrılacak garanti edilir) bir yıkıcı vardır çöp toplama diller, nesne havuzları gereken açık bir nesne istenerek, elle uygulanacak fabrika ve atma yöntemi çağırarak nesnesi geri ( imha modelinde olduğu gibi ). Bunu yapmak için bir sonlandırıcı kullanmak iyi bir fikir değildir, çünkü genellikle sonlandırıcının ne zaman (veya çalıştırılıp çalıştırılmayacağı) konusunda hiçbir garanti yoktur. Bunun yerine, nesneyi almanın ve serbest bırakmanın istisnasız olduğundan emin olmak için "deneyin ... nihayet" kullanılmalıdır.

Manuel nesne havuzlarının uygulanması kolaydır, ancak havuz nesnelerinin manuel bellek yönetimini gerektirdiğinden kullanımı daha zordur .

Boş havuzların işlenmesi

Nesne havuzları, havuzda yedek nesne olmadığında bir isteği işlemek için üç stratejiden birini kullanır.

  1. Bir nesne sağlanamadı (ve istemciye bir hata döndürüldü).
  2. Yeni bir nesne tahsis edin, böylece havuzun boyutunu artırın. Bunu yapan havuzlar genellikle yüksek su işareti (şimdiye kadar kullanılan maksimum nesne sayısı) belirlemenize izin verir .
  3. Çok iş parçacıklı bir ortamda, bir havuz, başka bir iş parçacığı havuza bir nesne döndürene kadar istemciyi engelleyebilir.

tuzaklar

Havuza döndürülen nesnelerin durumunun, nesnenin bir sonraki kullanımı için tekrar mantıklı bir duruma sıfırlandığından emin olmak için özen gösterilmelidir, aksi takdirde nesne, istemci tarafından beklenmeyen bir durumda olabilir ve bu da başarısız olmasına neden olabilir. Havuz, istemcilerin değil, nesnelerin sıfırlanmasından sorumludur. Tehlikeli derecede bayat duruma sahip nesnelerle dolu nesne havuzlarına bazen nesne çöplüğü denir ve bir anti-kalıp olarak kabul edilir .

Eski durum her zaman bir sorun olmayabilir; nesnenin beklenmedik şekilde davranmasına neden olduğunda tehlikeli hale gelir. Örneğin, kimlik doğrulama ayrıntılarını temsil eden bir nesne, "kimliği başarıyla doğrulandı" bayrağı yeniden kullanılmadan önce sıfırlanmazsa başarısız olabilir, çünkü bu, bir kullanıcının kimliğinin doğrulanmadığı halde (muhtemelen başka biri olarak) doğrulandığını gösterir. Ancak, kullanılan son kimlik doğrulama sunucusunun kimliği gibi yalnızca hata ayıklama için kullanılan bir değerin sıfırlanmaması herhangi bir sorun teşkil etmeyebilir.

Nesnelerin yetersiz sıfırlanması bilgi sızıntılarına neden olabilir. Gizli verileri içeren nesneler (örneğin bir kullanıcının kredi kartı numaraları) yeni müşterilere verilmeden önce temizlenmelidir, aksi takdirde veriler yetkisiz bir tarafa ifşa edilebilir.

Havuz birden çok iş parçacığı tarafından kullanılıyorsa, paralel iş parçacıklarının aynı nesneyi paralel olarak yeniden kullanmaya çalışmasını önlemek için araçlara ihtiyaç duyabilir. Havuza alınan nesneler değişmezse veya başka bir şekilde iş parçacığı için güvenliyse bu gerekli değildir.

eleştiri

Bazı yayınlar , özellikle yalnızca bellek kullanan ve harici kaynaklar (veritabanı bağlantıları gibi) tutmayan nesneler için Java gibi belirli dillerle nesne havuzunun kullanılmasını önermez . Muhalifler genellikle, çöp toplayıcıları olan modern dillerde nesne tahsisinin nispeten hızlı olduğunu söylüyorlar ; Operatör ise newsadece on talimatları ihtiyacı klasik new- deletedaha karmaşık çalışır olarak tasarımlar havuzlaması bulundu çifti bunlardan yüzlerce gerektirir. Ayrıca çoğu çöp toplayıcı, bu nesnelerin içerikleri için kullandığı belleği değil, "canlı" nesne referanslarını tarar. Bu, referansları olmayan herhangi bir sayıda "ölü" nesnenin çok az maliyetle atılabileceği anlamına gelir. Buna karşılık, çok sayıda "canlı" ancak kullanılmayan nesneyi tutmak çöp toplama süresini uzatır.

Örnekler

Gitmek

Aşağıdaki Go kodu, kanallar aracılığıyla kaynak yarışı sorunlarını önlemek için belirtilen boyutta bir kaynak havuzunu (eşzamanlı başlatma) başlatır ve boş bir havuz olması durumunda, istemcilerin çok uzun süre beklemesini önlemek için zaman aşımı işlemesini ayarlar.

// 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#

.NET Base Class Library'de bu kalıbı uygulayan birkaç nesne vardır. System.Threading.ThreadPooltahsis edilecek önceden tanımlanmış sayıda iş parçacığına sahip olacak şekilde yapılandırılmıştır. İş parçacıkları döndürüldüğünde, başka bir hesaplama için kullanılabilirler. Böylece, iş parçacığı oluşturma ve elden çıkarma maliyetini ödemeden iş parçacıklarını kullanabilirsiniz.

Aşağıda, C# kullanılarak uygulanan nesne havuzu tasarım modelinin temel kodu gösterilmektedir. Kısaca, sınıfların özellikleri, otomatik olarak uygulanan özellik sözdizimi C# 3.0 kullanılarak bildirilir. Bunlar, dilin önceki sürümleri için tam özellik tanımlarıyla değiştirilebilir. Birden çok havuzun gerekli olması olağandışı olduğundan, havuz statik bir sınıf olarak gösterilir. Ancak, nesne havuzları için örnek sınıfları kullanmak da aynı derecede kabul edilebilir.

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;
        }
    }
}

Yukarıdaki kodda, PooledObject oluşturulduğu zamana ait özelliklere ve istemci tarafından değiştirilebilen, PooledObject havuza geri bırakıldığında sıfırlanan özelliklere sahiptir. Gösterilen, bir nesnenin serbest bırakılması üzerine, havuzdan tekrar talep edilmeden önce geçerli bir durumda olmasını sağlayan temizleme işlemidir.

Java

Java, ve diğer ilgili sınıflar aracılığıyla iş parçacığı havuzunu destekler java.util.concurrent.ExecutorService. Yürütücü hizmeti, hiçbir zaman atılmayan belirli sayıda "temel" iş parçacığına sahiptir. Tüm iş parçacıkları meşgulse, hizmet, belirli bir süre sonu için kullanılmazsa daha sonra atılacak izin verilen fazladan iş parçacığı sayısını tahsis eder. Daha fazla iş parçacığına izin verilmiyorsa, görevler kuyruğa yerleştirilebilir. Son olarak, eğer bu kuyruk çok uzarsa, istekte bulunan iş parçacığını askıya alacak şekilde yapılandırılabilir.

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);
	}
}

Ayrıca bakınız

Notlar

Referanslar

Dış bağlantılar