C# 并行编程 之 并发集合 (.Net Framework 4.0)

来源:互联网 发布:js定时器setinterval 编辑:程序博客网 时间:2024/06/04 19:43

此文为个人学习《C#并行编程高级教程》的笔记,总结并调试了一些文章中的代码示例。 在以后开发过程中可以加以运用。


对于并行任务,与其相关紧密的就是对一些共享资源,数据结构的并行访问。经常要做的就是对一些队列进行加锁-解锁,然后执行类似插入,删除等等互斥操作。 .NetFramework 4.0 中提供了一些封装好的支持并行操作数据容器,可以减少并行编程的复杂程度。


基本信息

.NetFramework中并行集合的名字空间: System.Collections.Concurrent

并行容器:

  • ConcurrentQueue
  • ConcurrentStack
  • ConcurrentBag : 一个无序的数据结构集,当不需要考虑顺序时非常有用。
  • BlockingCollection : 与经典的阻塞队列数据结构类似
  • ConcurrentDictionary

这些集合在某种程度上使用了无锁技术(CAS Compare-and-Swap和内存屏障 Memory Barrier),与加互斥锁相比获得了性能的提升。但在串行程序中,最好不用这些集合,它们必然会影响性能。

关于CAS: 
  • http://www.tuicool.com/articles/zuui6z
  • http://www.360doc.com/content/11/0914/16/7656248_148221200.shtml
关于内存屏障
  • http://en.wikipedia.org/wiki/Memory_barrier

用法与示例

ConcurrentQueue

其完全无锁,但当CAS面临资源竞争失败时可能会陷入自旋并重试操作。

  • Enqueue:在队尾插入元素
  • TryDequeue:尝试删除队头元素,并通过out参数返回
  • TryPeek:尝试将对头元素通过out参数返回,但不删除该元素。

程序示例:

[csharp] view plain copy
  1. using System;  
  2. using System.Text;  
  3.   
  4. using System.Threading.Tasks;  
  5. using System.Collections.Concurrent;  
  6.   
  7. namespace Sample4_1_concurrent_queue  
  8. {  
  9.     class Program  
  10.     {  
  11.         internal static ConcurrentQueue<int> _TestQueue;  
  12.   
  13.         class ThreadWork1  // producer  
  14.         {  
  15.             public ThreadWork1()  
  16.             { }  
  17.   
  18.             public void run()  
  19.             {  
  20.                 System.Console.WriteLine("ThreadWork1 run { ");  
  21.                 for (int i = 0; i < 100; i++)  
  22.                 {  
  23.                     System.Console.WriteLine("ThreadWork1 producer: " + i);  
  24.                     _TestQueue.Enqueue(i);  
  25.                 }  
  26.                 System.Console.WriteLine("ThreadWork1 run } ");  
  27.             }  
  28.         }  
  29.   
  30.         class ThreadWork2  // consumer  
  31.         {  
  32.             public ThreadWork2()  
  33.             { }  
  34.   
  35.             public void run()  
  36.             {  
  37.                 int i = 0;  
  38.                 bool IsDequeuue = false;  
  39.                 System.Console.WriteLine("ThreadWork2 run { ");  
  40.                 for (; ; )  
  41.                 {  
  42.                     IsDequeuue = _TestQueue.TryDequeue(out i);  
  43.                     if (IsDequeuue)  
  44.                         System.Console.WriteLine("ThreadWork2 consumer: " + i * i + "   =====");  
  45.   
  46.                     if (i == 99)  
  47.                         break;  
  48.                 }  
  49.                 System.Console.WriteLine("ThreadWork2 run } ");  
  50.             }  
  51.         }  
  52.   
  53.         static void StartT1()  
  54.         {  
  55.             ThreadWork1 work1 = new ThreadWork1();  
  56.             work1.run();  
  57.         }  
  58.   
  59.         static void StartT2()  
  60.         {  
  61.             ThreadWork2 work2 = new ThreadWork2();  
  62.             work2.run();  
  63.         }  
  64.         static void Main(string[] args)  
  65.         {  
  66.             Task t1 = new Task(() => StartT1());  
  67.             Task t2 = new Task(() => StartT2());  
  68.   
  69.             _TestQueue = new ConcurrentQueue<int>();  
  70.   
  71.             Console.WriteLine("Sample 3-1 Main {");  
  72.   
  73.             Console.WriteLine("Main t1 t2 started {");  
  74.             t1.Start();  
  75.             t2.Start();  
  76.             Console.WriteLine("Main t1 t2 started }");  
  77.   
  78.             Console.WriteLine("Main wait t1 t2 end {");  
  79.             Task.WaitAll(t1, t2);  
  80.             Console.WriteLine("Main wait t1 t2 end }");  
  81.   
  82.             Console.WriteLine("Sample 3-1 Main }");  
  83.   
  84.             Console.ReadKey();  
  85.         }  
  86.     }  
  87. }  



ConcurrentStack

其完全无锁,但当CAS面临资源竞争失败时可能会陷入自旋并重试操作。

  • Push:向栈顶插入元素
  • TryPop:从栈顶弹出元素,并且通过out 参数返回
  • TryPeek:返回栈顶元素,但不弹出。

程序示例:

[csharp] view plain copy
  1. using System;  
  2. using System.Text;  
  3.   
  4. using System.Threading.Tasks;  
  5. using System.Collections.Concurrent;  
  6.   
  7. namespace Sample4_2_concurrent_stack  
  8. {  
  9.     class Program  
  10.     {  
  11.         internal static ConcurrentStack<int> _TestStack;  
  12.   
  13.         class ThreadWork1  // producer  
  14.         {  
  15.             public ThreadWork1()  
  16.             { }  
  17.   
  18.             public void run()  
  19.             {  
  20.                 System.Console.WriteLine("ThreadWork1 run { ");  
  21.                 for (int i = 0; i < 100; i++)  
  22.                 {  
  23.                     System.Console.WriteLine("ThreadWork1 producer: " + i);  
  24.                     _TestStack.Push(i);  
  25.                 }  
  26.                 System.Console.WriteLine("ThreadWork1 run } ");  
  27.             }  
  28.         }  
  29.   
  30.         class ThreadWork2  // consumer  
  31.         {  
  32.             public ThreadWork2()  
  33.             { }  
  34.   
  35.             public void run()  
  36.             {  
  37.                 int i = 0;  
  38.                 bool IsDequeuue = false;  
  39.                 System.Console.WriteLine("ThreadWork2 run { ");  
  40.                 for (; ; )  
  41.                 {  
  42.                     IsDequeuue = _TestStack.TryPop(out i);  
  43.                     if (IsDequeuue)  
  44.                         System.Console.WriteLine("ThreadWork2 consumer: " + i * i + "   =====" + i);  
  45.   
  46.                     if (i == 99)  
  47.                         break;  
  48.                 }  
  49.                 System.Console.WriteLine("ThreadWork2 run } ");  
  50.             }  
  51.         }  
  52.   
  53.         static void StartT1()  
  54.         {  
  55.             ThreadWork1 work1 = new ThreadWork1();  
  56.             work1.run();  
  57.         }  
  58.   
  59.         static void StartT2()  
  60.         {  
  61.             ThreadWork2 work2 = new ThreadWork2();  
  62.             work2.run();  
  63.         }  
  64.         static void Main(string[] args)  
  65.         {  
  66.             Task t1 = new Task(() => StartT1());  
  67.             Task t2 = new Task(() => StartT2());  
  68.   
  69.             _TestStack = new ConcurrentStack<int>();  
  70.   
  71.             Console.WriteLine("Sample 4-1 Main {");  
  72.   
  73.             Console.WriteLine("Main t1 t2 started {");  
  74.             t1.Start();  
  75.             t2.Start();  
  76.             Console.WriteLine("Main t1 t2 started }");  
  77.   
  78.             Console.WriteLine("Main wait t1 t2 end {");  
  79.             Task.WaitAll(t1, t2);  
  80.             Console.WriteLine("Main wait t1 t2 end }");  
  81.   
  82.             Console.WriteLine("Sample 4-1 Main }");  
  83.   
  84.             Console.ReadKey();  
  85.         }  
  86.     }  
  87. }  

测试中一个有趣的现象:

虽然生产者已经在栈中插入值已经到了25,但消费者第一个出栈的居然是4,而不是25。很像是出错了。但仔细想想入栈,出栈和打印语句是两个部分,而且并不是原子操作,出现这种现象应该也算正常。

Sample 3-1 Main {
Main t1 t2 started {
Main t1 t2 started }
Main wait t1 t2 end {
ThreadWork1 run {
ThreadWork1 producer: 0
ThreadWork2 run {
ThreadWork1 producer: 1
ThreadWork1 producer: 2
ThreadWork1 producer: 3
ThreadWork1 producer: 4
ThreadWork1 producer: 5
ThreadWork1 producer: 6
ThreadWork1 producer: 7
ThreadWork1 producer: 8
ThreadWork1 producer: 9
ThreadWork1 producer: 10
ThreadWork1 producer: 11
ThreadWork1 producer: 12
ThreadWork1 producer: 13
ThreadWork1 producer: 14
ThreadWork1 producer: 15
ThreadWork1 producer: 16
ThreadWork1 producer: 17
ThreadWork1 producer: 18
ThreadWork1 producer: 19
ThreadWork1 producer: 20
ThreadWork1 producer: 21
ThreadWork1 producer: 22
ThreadWork1 producer: 23
ThreadWork1 producer: 24
ThreadWork1 producer: 25
ThreadWork2 consumer: 16   =====4
ThreadWork2 consumer: 625   =====25
ThreadWork2 consumer: 576   =====24
ThreadWork2 consumer: 529   =====23
ThreadWork1 producer: 26
ThreadWork1 producer: 27
ThreadWork1 producer: 28



ConcurrentBag

一个无序的集合,程序可以向其中插入元素,或删除元素。
在同一个线程中向集合插入,删除元素的效率很高。

  •  Add:向集合中插入元素
  •  TryTake:从集合中取出元素并删除
  •  TryPeek:从集合中取出元素,但不删除该元素。

  • 程序示例:

    [csharp] view plain copy
    1. using System;  
    2. using System.Text;  
    3.   
    4. using System.Threading.Tasks;  
    5. using System.Collections.Concurrent;  
    6.   
    7. namespace Sample4_3_concurrent_bag  
    8. {  
    9.     class Program  
    10.     {  
    11.         internal static ConcurrentBag<int> _TestBag;  
    12.   
    13.         class ThreadWork1  // producer  
    14.         {  
    15.             public ThreadWork1()  
    16.             { }  
    17.   
    18.             public void run()  
    19.             {  
    20.                 System.Console.WriteLine("ThreadWork1 run { ");  
    21.                 for (int i = 0; i < 100; i++)  
    22.                 {  
    23.                     System.Console.WriteLine("ThreadWork1 producer: " + i);  
    24.                     _TestBag.Add(i);  
    25.                 }  
    26.                 System.Console.WriteLine("ThreadWork1 run } ");  
    27.             }  
    28.         }  
    29.   
    30.         class ThreadWork2  // consumer  
    31.         {  
    32.             public ThreadWork2()  
    33.             { }  
    34.   
    35.             public void run()  
    36.             {  
    37.                 int i = 0;  
    38.                 int nCnt = 0;  
    39.                 bool IsDequeuue = false;  
    40.                 System.Console.WriteLine("ThreadWork2 run { ");  
    41.                 for (;;)  
    42.                 {  
    43.                     IsDequeuue = _TestBag.TryTake(out i);  
    44.                     if (IsDequeuue)  
    45.                     {  
    46.                         System.Console.WriteLine("ThreadWork2 consumer: " + i * i + "   =====" + i);  
    47.                         nCnt++;  
    48.                     }  
    49.   
    50.                     if (nCnt == 99)  
    51.                         break;  
    52.                 }  
    53.                 System.Console.WriteLine("ThreadWork2 run } ");  
    54.             }  
    55.         }  
    56.   
    57.         static void StartT1()  
    58.         {  
    59.             ThreadWork1 work1 = new ThreadWork1();  
    60.             work1.run();  
    61.         }  
    62.   
    63.         static void StartT2()  
    64.         {  
    65.             ThreadWork2 work2 = new ThreadWork2();  
    66.             work2.run();  
    67.         }  
    68.         static void Main(string[] args)  
    69.         {  
    70.             Task t1 = new Task(() => StartT1());  
    71.             Task t2 = new Task(() => StartT2());  
    72.   
    73.             _TestBag = new ConcurrentBag<int>();  
    74.   
    75.             Console.WriteLine("Sample 4-3 Main {");  
    76.   
    77.             Console.WriteLine("Main t1 t2 started {");  
    78.             t1.Start();  
    79.             t2.Start();  
    80.             Console.WriteLine("Main t1 t2 started }");  
    81.   
    82.             Console.WriteLine("Main wait t1 t2 end {");  
    83.             Task.WaitAll(t1, t2);  
    84.             Console.WriteLine("Main wait t1 t2 end }");  
    85.   
    86.             Console.WriteLine("Sample 4-3 Main }");  
    87.   
    88.             Console.ReadKey();  
    89.         }  
    90.     }  
    91. }  



    BlockingCollection

    一个支持界限和阻塞的容器

    • Add :向容器中插入元素
    • TryTake:从容器中取出元素并删除
    • TryPeek:从容器中取出元素,但不删除。
    • CompleteAdding:告诉容器,添加元素完成。此时如果还想继续添加会发生异常。
    • IsCompleted:告诉消费线程,生产者线程还在继续运行中,任务还未完成。

    示例程序:

    程序中,消费者线程完全使用  while (!_TestBCollection.IsCompleted) 作为退出运行的判断条件。
    在Worker1中,有两条语句被注释掉了,当i 为50时设置CompleteAdding,但当继续向其中插入元素时,系统抛出异常,提示无法再继续插入。

    [csharp] view plain copy
    1. using System;  
    2. using System.Text;  
    3.   
    4. using System.Threading.Tasks;  
    5. using System.Collections.Concurrent;  
    6.   
    7.   
    8. namespace Sample4_4_concurrent_bag  
    9. {  
    10.     class Program  
    11.     {  
    12.         internal static BlockingCollection<int> _TestBCollection;  
    13.   
    14.         class ThreadWork1  // producer  
    15.         {  
    16.             public ThreadWork1()  
    17.             { }  
    18.   
    19.             public void run()  
    20.             {  
    21.                 System.Console.WriteLine("ThreadWork1 run { ");  
    22.                 for (int i = 0; i < 100; i++)  
    23.                 {  
    24.                     System.Console.WriteLine("ThreadWork1 producer: " + i);  
    25.                     _TestBCollection.Add(i);  
    26.                     //if (i == 50)  
    27.                     //    _TestBCollection.CompleteAdding();  
    28.                 }  
    29.                 _TestBCollection.CompleteAdding();  
    30.   
    31.                 System.Console.WriteLine("ThreadWork1 run } ");  
    32.             }  
    33.         }  
    34.   
    35.         class ThreadWork2  // consumer  
    36.         {  
    37.             public ThreadWork2()  
    38.             { }  
    39.   
    40.             public void run()  
    41.             {  
    42.                 int i = 0;  
    43.                 int nCnt = 0;  
    44.                 bool IsDequeuue = false;  
    45.                 System.Console.WriteLine("ThreadWork2 run { ");  
    46.                 while (!_TestBCollection.IsCompleted)  
    47.                 {  
    48.                     IsDequeuue = _TestBCollection.TryTake(out i);  
    49.                     if (IsDequeuue)  
    50.                     {  
    51.                         System.Console.WriteLine("ThreadWork2 consumer: " + i * i + "   =====" + i);  
    52.                         nCnt++;  
    53.                     }  
    54.                 }  
    55.                 System.Console.WriteLine("ThreadWork2 run } ");  
    56.             }  
    57.         }  
    58.   
    59.         static void StartT1()  
    60.         {  
    61.             ThreadWork1 work1 = new ThreadWork1();  
    62.             work1.run();  
    63.         }  
    64.   
    65.         static void StartT2()  
    66.         {  
    67.             ThreadWork2 work2 = new ThreadWork2();  
    68.             work2.run();  
    69.         }  
    70.         static void Main(string[] args)  
    71.         {  
    72.             Task t1 = new Task(() => StartT1());  
    73.             Task t2 = new Task(() => StartT2());  
    74.   
    75.             _TestBCollection = new BlockingCollection<int>();  
    76.   
    77.             Console.WriteLine("Sample 4-4 Main {");  
    78.   
    79.             Console.WriteLine("Main t1 t2 started {");  
    80.             t1.Start();  
    81.             t2.Start();  
    82.             Console.WriteLine("Main t1 t2 started }");  
    83.   
    84.             Console.WriteLine("Main wait t1 t2 end {");  
    85.             Task.WaitAll(t1, t2);  
    86.             Console.WriteLine("Main wait t1 t2 end }");  
    87.   
    88.             Console.WriteLine("Sample 4-4 Main }");  
    89.   
    90.             Console.ReadKey();  
    91.         }  
    92.     }  
    93. }  


    当然可以尝试在Work1中注释掉 CompleteAdding 语句,此时Work2陷入循环无法退出。

    ConcurrentDictionary

    对于读操作是完全无锁的,当很多线程要修改数据时,它会使用细粒度的锁。

    • AddOrUpdate:如果键不存在,方法会在容器中添加新的键和值,如果存在,则更新现有的键和值。
    • GetOrAdd:如果键不存在,方法会向容器中添加新的键和值,如果存在则返回现有的值,并不添加新值。
    • TryAdd:尝试在容器中添加新的键和值。
    • TryGetValue:尝试根据指定的键获得值。
    • TryRemove:尝试删除指定的键。
    • TryUpdate:有条件的更新当前键所对应的值。
    • GetEnumerator:返回一个能够遍历整个容器的枚举器。


    程序示例:

    [csharp] view plain copy
    1. using System;  
    2. using System.Text;  
    3.   
    4. using System.Threading.Tasks;  
    5. using System.Collections.Concurrent;  
    6.   
    7.   
    8. namespace Sample4_5_concurrent_dictionary  
    9. {  
    10.     class Program  
    11.     {  
    12.         internal static ConcurrentDictionary<intint> _TestDictionary;  
    13.   
    14.         class ThreadWork1  // producer  
    15.         {  
    16.             public ThreadWork1()  
    17.             { }  
    18.   
    19.             public void run()  
    20.             {  
    21.                 System.Console.WriteLine("ThreadWork1 run { ");  
    22.                 for (int i = 0; i < 100; i++)  
    23.                 {  
    24.                     System.Console.WriteLine("ThreadWork1 producer: " + i);  
    25.                     _TestDictionary.TryAdd(i, i);  
    26.                 }  
    27.   
    28.                 System.Console.WriteLine("ThreadWork1 run } ");  
    29.             }  
    30.         }  
    31.   
    32.         class ThreadWork2  // consumer  
    33.         {  
    34.             public ThreadWork2()  
    35.             { }  
    36.   
    37.             public void run()  
    38.             {  
    39.                 int i = 0, nCnt = 0;  
    40.                 int nValue = 0;  
    41.                 bool IsOk = false;  
    42.                 System.Console.WriteLine("ThreadWork2 run { ");  
    43.                 while (nCnt < 100)  
    44.                 {  
    45.                     IsOk = _TestDictionary.TryGetValue(i, out nValue);  
    46.                     if (IsOk)  
    47.                     {  
    48.                         System.Console.WriteLine("ThreadWork2 consumer: " + i * i + "   =====" + i);  
    49.                         nValue = nValue * nValue;  
    50.                         _TestDictionary.AddOrUpdate(i, nValue, (key, value) => { return value = nValue; });  
    51.                         nCnt++;  
    52.                         i++;  
    53.                     }  
    54.                 }  
    55.                 System.Console.WriteLine("ThreadWork2 run } ");  
    56.             }  
    57.         }  
    58.   
    59.         static void StartT1()  
    60.         {  
    61.             ThreadWork1 work1 = new ThreadWork1();  
    62.             work1.run();  
    63.         }  
    64.   
    65.         static void StartT2()  
    66.         {  
    67.             ThreadWork2 work2 = new ThreadWork2();  
    68.             work2.run();  
    69.         }  
    70.         static void Main(string[] args)  
    71.         {  
    72.             Task t1 = new Task(() => StartT1());  
    73.             Task t2 = new Task(() => StartT2());  
    74.             bool bIsNext = true;  
    75.             int  nValue = 0;  
    76.   
    77.             _TestDictionary = new ConcurrentDictionary<intint>();  
    78.   
    79.             Console.WriteLine("Sample 4-5 Main {");  
    80.   
    81.             Console.WriteLine("Main t1 t2 started {");  
    82.             t1.Start();  
    83.             t2.Start();  
    84.             Console.WriteLine("Main t1 t2 started }");  
    85.   
    86.             Console.WriteLine("Main wait t1 t2 end {");  
    87.             Task.WaitAll(t1, t2);  
    88.             Console.WriteLine("Main wait t1 t2 end }");  
    89.   
    90.             foreach (var pair in _TestDictionary)  
    91.             {  
    92.                 Console.WriteLine(pair.Key + " : " + pair.Value);  
    93.             }  
    94.   
    95.             System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<intint>>   
    96.                 enumer = _TestDictionary.GetEnumerator();  
    97.   
    98.             while (bIsNext)  
    99.             {  
    100.                 bIsNext = enumer.MoveNext();  
    101.                 Console.WriteLine("Key: " + enumer.Current.Key +  
    102.                                   "  Value: " + enumer.Current.Value);  
    103.   
    104.                 _TestDictionary.TryRemove(enumer.Current.Key, out nValue);  
    105.             }  
    106.   
    107.             Console.WriteLine("\n\nDictionary Count: " + _TestDictionary.Count);  
    108.   
    109.             Console.WriteLine("Sample 4-5 Main }");  
    110.   
    111.             Console.ReadKey();  
    112.         }  
    113.     }  

    阅读全文
    0 0
    原创粉丝点击