.NET Core 3.0之深入原始碼理解ObjectPool(一)
寫在前面
物件池是一種比較常用的提高系統性能的軟體設計模式,它維護了一系列相關物件列表的容器物件,這些物件可以隨時重複使用,物件池節省了頻繁建立物件的開銷。
它使用取用/歸還-重複取用的操作模式,如下圖所示:
本文將主要介紹物件池的基本概念、物件池的優勢及其工作機制,下一篇文件將從原始碼角度介紹.NET Core 3.0是如何實現物件池的。
物件池基礎
物件池的基本概念
物件池的核心概念是容器,其表示形式可以認為是列表。每當有新的物件建立請求進入時,都會通過從池中分配一個物件來滿足該請求。當我們需要獲取某個物件時,可以從池中獲取。既然有了物件池,那麼也就很方便我們就很容易建立起物件的管理與追蹤了了。
物件池的優勢
我們知道一旦應用程式啟動並執行,記憶體使用就會受到系統所需物件的數量和大小的影響。
我們知道建立一個物件的例項,是需要消耗一定的系統資源,尤其是該物件的構造十分複雜的時候,再加上需要頻繁建立的時候,其例項化所消耗的資源更加昂貴。如果我們能有一種辦法減少這種昂貴的系統開銷,這對系統性能的提升是十分有幫助的。
物件池理念的出現,有助於我們解決複雜物件的重複建立所引發的資源開銷問題。物件儲存在某種型別的列表或者說陣列中,我們可以和獲取陣列中的子項一樣獲取已經存在在物件池中的物件。
物件池的最大優點是,它可以自主管理內部已經建立的物件,包括回收和重複使用物件。程式在使用完某個物件後,會將其發還至物件池,而不是在記憶體中銷燬他們。
物件池通過資源的分配,因而也就減少了應用程式所需的垃圾回收數量。這對於需要頻繁建立同一物件的功能來說,物件池最大程度地減少了系統資源的消耗。
簡單來說,物件池的設計目標就是要使物件可以得到重複使用,而不是被垃圾回收器回收。
物件池的工作機制
當客戶端程式需要某個物件時,物件池首先嚐試提供一個已經建立的物件。如果沒有可用的物件,則會建立一個新物件。這類似於一個GetOrAdd的操作。同時物件池中物件的數量就會減少,直到該物件已經使用完,那麼它就會被放回到物件池池中以等待使用。這就是為什麼物件池有助於重用性、並減少了在獲取物件時建立物件所涉及的開銷的原因。
另外,需要注意的是,只要池中至少有一個物件,該池就會一直保留在記憶體中。只要物件池還在,裡面的物件也會一直存在。
當物件池用於併發操作時,需要確保物件池是執行緒安全的,而且其本身還要有很高的效能。
ConcurrentBag物件池解決方案
這個解決方案來自於MSDN,ConcurrentBag <T>用於儲存物件,因為它支援快速插入和刪除,尤其是在同一執行緒同時新增和刪除專案時。該示例可以進一步擴充套件為圍繞IProducerConsumerCollection <T>構建,該資料由bag資料結構實現,ConcurrentQueue <T>和ConcurrentStack <T>也是如此。
1: using System;
2: using System.Collections.Concurrent;
3: using System.Threading;
4: using System.Threading.Tasks;
5:
6:
7: namespace ObjectPoolExample
8: {
9: public class ObjectPool<T>
10: {
11: private ConcurrentBag<T> _objects;
12: private Func<T> _objectGenerator;
13:
14: public ObjectPool(Func<T> objectGenerator)
15: {
16: if (objectGenerator == null) throw new ArgumentNullException("objectGenerator");
17: _objects = new ConcurrentBag<T>();
18: _objectGenerator = objectGenerator;
19: }
20:
21: public T GetObject()
22: {
23: T item;
24: if (_objects.TryTake(out item)) return item;
25: return _objectGenerator();
26: }
27:
28: public void PutObject(T item)
29: {
30: _objects.Add(item);
31: }
32: }
33:
34: class Program
35: {
36: static void Main(string[] args)
37: {
38: CancellationTokenSource cts = new CancellationTokenSource();
39:
40: // Create an opportunity for the user to cancel.
41: Task.Run(() =>
42: {
43: if (Console.ReadKey().KeyChar == 'c' || Console.ReadKey().KeyChar == 'C')
44: cts.Cancel();
45: });
46:
47: ObjectPool<MyClass> pool = new ObjectPool<MyClass> (() => new MyClass());
48:
49: // Create a high demand for MyClass objects.
50: Parallel.For(0, 1000000, (i, loopState) =>
51: {
52: MyClass mc = pool.GetObject();
53: Console.CursorLeft = 0;
54: // This is the bottleneck in our application. All threads in this loop
55: // must serialize their access to the static Console class.
56: Console.WriteLine("{0:####.####}", mc.GetValue(i));
57:
58: pool.PutObject(mc);
59: if (cts.Token.IsCancellationRequested)
60: loopState.Stop();
61:
62: });
63: Console.WriteLine("Press the Enter key to exit.");
64: Console.ReadLine();
65: cts.Dispose();
66: }
67:
68: }
69:
70: // A toy class that requires some resources to create.
71: // You can experiment here to measure the performance of the
72: // object pool vs. ordinary instantiation.
73: class MyClass
74: {
75: public int[] Nums {get; set;}
76: public double GetValue(long i)
77: {
78: return Math.Sqrt(Nums[i]);
79: }
80: public MyClass()
81: {
82: Nums = new int[1000000];
83: Random rand = new Random();
84: for (int i = 0; i < Nums.Length; i++)
85: Nums[i] = rand.Next();
86: }
87: }
88: }
參考連結:https://docs.microsoft.com/en-us/dotnet/standard/collections/thread-safe/how-to-create-an-object-pool