使用併發集合

帶着問題去思考!大家好

併發集合(線程安全),既然是併發集合。那就要知道什麼是併發。

併發:同一時間間隔對資源的共享。

ConcurrentDictionary  線程安全字典集合,對於讀操作無需使用鎖,寫操作則需要鎖。該併發使用多個鎖。
ConcurrentQueue 使用了原子的比較和交換,使用SpanWait來保證線程安全,實現了FIFO.可以調用Enqueue方法向隊列中加入元素。TryDequequ方法試圖取出隊列中的第一個元素,TryPeek方法則試圖得到第一個元素但並不從隊列中刪除元素
ConcurrentStack

 實際中沒有任何鎖,採用CAS操作,LIFO集合,可以用push,pushRange方法添加元素,使用tryPop和TryPopRange方法獲取元素,使用TryPeek方法檢查

ConcurrentBag  支持重複元素無序集合,針對這樣以下情況進行了優化,及多個線程以這樣的方式工作,每個線程產生和消費自己的任務,極少與其他線程的任務交互,Add添加,TryPeek方法,獲取元素用TryTask方法
BlockingCollection  是對IprodicerConsumerCollection泛型接口的實現的一個高級封裝。支持如下功能,分塊,調整內部集合容量,取消集合操作。從多塊中獲取元素

其中ConcurrentQueue,ConcurrentStack,ConcurrentBag避免使用上面提及的集合的Count屬性,實現這些集合使用的是鏈表。Count時間複雜度爲O(N).檢查集合是否爲空,使用IsEmpty屬性,時間複雜度爲O(1).

這裏我們基本介紹下功能:

ConcurrentDictionary

單線程環境中的字典集合與使用併發字典的性能。

const string Item = "Dictionary item";
        public static string CurrentItem;
        /// <summary>
        /// ConcurrentDictionary寫操作比使用鎖的通常的字典要慢得多。而讀操作則要快些。
        /// 因此如果對字典需要大量的線程安全讀操作,concurrentDictionary是最好的選擇。
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            var concurrentDictionary = new ConcurrentDictionary<int, string>(); //併發集合
            var dictionary = new Dictionary<int, string>(); //正常集合

            var sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < 100000; i++)
            {
                //鎖機制向標準的字典中添加元素,並測量完成100萬次迭代的時間。
                lock(dictionary)
                {
                    dictionary[i] = Item;
                }
            }
            sw.Stop();
            Console.WriteLine("Writing to dictionary with a lock :{0}", sw.Elapsed);
            sw.Restart();
            
            for (int i = 0; i < 100000; i++)
            {
                //比較兩個集合中獲取值的性能
                concurrentDictionary[i] = Item;
            }
            sw.Stop();

            Console.WriteLine("Writing to a concurrent dictionary:{0}",sw.Elapsed);
            sw.Restart();
            for (int i = 0; i < 100000; i++)
            {
                lock(dictionary)
                {
                    CurrentItem = dictionary[i];
                }
            }
            sw.Stop();
            Console.WriteLine("Reading from dictionary with a lock {0}",sw.Elapsed);
            sw.Restart();
            for (int i = 0; i < 100000; i++)
            {
                CurrentItem = concurrentDictionary[i];
            }
            sw.Stop();
            Console.WriteLine("Reading from concurrent  dictionary  {0}", sw.Elapsed);
        }
View Code

創建兩個集合,其中一個是標準的字典集合,另一個是新的併發字典集合。採用鎖的機制想標準的字典中添加元素。比較兩者之間。我們發現ConcurrentDictionary寫操作比使用鎖的通常的字典要慢的多,而讀操作則要快些。因此如果對字典需要大量的線程安全的操作。ConcurrentDictionary是最好的選擇。

ConcurrentDictionary的實現使用了細粒度鎖技術,在多線程寫入方面比使用鎖的通常的字典的可伸縮性更好。在本例中,當只用一個線程時,併發字典非常慢。但是擴展到5-6個線程,併發字典的性能會更好

如果你對字典只需要多線程訪問只讀元素,則沒必要執行線程安全的讀操作。在此場景中最好只使用通常的字典或者ReadOnlyDictionary集合。

 ConcurrentQueue

創建能被多個工作者異步處理的一組任務的例子

static async Task RunProgram()
        {
            var taskQueue = new ConcurrentQueue<CustomerTask>();//任務隊列
            var cts = new CancellationTokenSource(); //取消標誌
            var taskSource = Task.Run(() => TaskProducer(taskQueue));
            Task[] processors = new Task[4];
            for (int i = 0; i < 4; i++)
            {
                string processorId = i.ToString();
                processors[i - 1] = Task.Run(() => TaskProcessor(taskQueue, "Processor" + processorId, cts.Token));
                await taskSource;
                cts.CancelAfter(TimeSpan.FromSeconds(2));
                await Task.WhenAll(processors);
            }
        }

        private static async Task TaskProducer(ConcurrentQueue<CustomerTask> taskQueue)
        {
            for (int i = 0; i <= 20; i++)
            {
                await Task.Delay(50);
                var workItem = new CustomerTask { Id = i };
                taskQueue.Enqueue(workItem);
                Console.WriteLine("Task {0} has been posted", workItem.Id);
            }
        }
        private static async Task TaskProcessor(ConcurrentQueue<CustomerTask> queue, string name, CancellationToken token)
        {
            CustomerTask customerTask;
            bool dequeueSuccesful = false;
            await GetRandomDelay();
            do
            {
                dequeueSuccesful = queue.TryDequeue(out customerTask);
                if (dequeueSuccesful)
                {
                    Console.WriteLine("Task {0} has been processed by {1}", customerTask.Id, name);
                }
                await GetRandomDelay();
            } while (!token.IsCancellationRequested);
        }
        static Task GetRandomDelay()
        {
            int delay = new Random(DateTime.Now.Millisecond).Next(1, 500);
            return Task.Delay(delay);
        }

        public class CustomerTask
        {
            public int Id { get; set; }
        }
        static void Main(string[] args)
        {
            Task t = RunProgram();
            t.Wait();
        }
View Code

 

我們使用ConcurrentQueue集合實例創建了一個任務隊列,然後一個取消標誌,用來在我們將任務放入隊列後停止工作的。接下來啓動了一個單獨的工作線程來將任務放入任務隊列中。現在定義該程序中消費任務的部分。我們創建了四個工作者,它們會隨時等待一段時間,然後從任務中獲取一個任務,處理該任務,一直重複整個過程直到我們發出取消標誌信號。

ConcurrentStack異步處理

創建了被多個工作者異步處理的一組任務。

     static async Task RunProgram()
        {
            var tasks = new ConcurrentStack<CustomerTask>();//任務
            var cts = new CancellationTokenSource(); //取消標誌
            var taskSource = Task.Run(() => TaskProducer(tasks));
            Task[] processors = new Task[4];
            for (int i = 0; i < 4; i++)
            {
                string processorId = i.ToString();
                processors[i - 1] = Task.Run(() => TaskProcessor(tasks, "Processor" + processorId, cts.Token));
                await taskSource;
                cts.CancelAfter(TimeSpan.FromSeconds(2));
                await Task.WhenAll(processors);
            }
        }

        private static async Task TaskProducer(ConcurrentStack<CustomerTask> tasks)
        {
            for (int i = 0; i <= 20; i++)
            {
                await Task.Delay(50);
                var workItem = new CustomerTask { Id = i };
                tasks.Push(workItem);
                Console.WriteLine("Task {0} has been posted", workItem.Id);
            }
        }
        private static async Task TaskProcessor(ConcurrentStack<CustomerTask> queue, string name, CancellationToken token)
        {
            CustomerTask customerTask;
            bool dequeueSuccesful = false;
            await GetRandomDelay();
            do
            {
                dequeueSuccesful = queue.TryPop(out customerTask);
                if (dequeueSuccesful)
                {
                    Console.WriteLine("Task {0} has been processed by {1}", customerTask.Id, name);
                }
                await GetRandomDelay();
            } while (!token.IsCancellationRequested);
        }
        static Task GetRandomDelay()
        {
            int delay = new Random(DateTime.Now.Millisecond).Next(1, 500);
            return Task.Delay(delay);
        }

        public class CustomerTask
        {
            public int Id { get; set; }
        }
        static void Main(string[] args)
        {
            Task t = RunProgram();
            t.Wait();
        }
View Code

 

 與之前的代碼幾乎一樣。唯一不同之處是我們對併發堆棧使用Push和TryPop方法。而對併發隊列使用Enqueue和TryDequeue方法。

處理的順序被改變了了、堆棧是一個LIFO集合,工作者先處理最近的任務。在併發隊列中,任務被處理的順序與被添加的順序幾乎一致。在堆棧中,早先創建的任務具有較低的優先級。而且直到生產者停止向堆棧中放入更多的任務後,該任務纔有可能停止。

ConcurrentBag

多個獨立的既可以生產工作又可消費工作的工作者如果擴展工作量。
具體可以借鑑https://www.cnblogs.com/InCerry/p/9497729.html

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章