《C#并行编程高级教程》第4章 并发集合 笔记

这一章主要介绍了System.Collections.Concurrent下的几个类。

ConcurrentQueue<T>

并发队列。完全无锁,使用CAS(compare-and-swap)比较并交换和自旋重试来实现线程安全。
//加入队尾
public void Enqueue(T item)
//尝试删除队头,并将元素通过out返回,返回值表示是否操作成功
public bool TryDequeue( out T result)
//尝试获取队头,通过out返回元素,返回值为代表是否操作成功
public bool TryPeek( out T result)

ConcurrentStack<T>

并发栈,完全无锁,使用CAS(compare-and-swap)比较并交换和自旋重试来实现线程安全。
 
public void Push(T item)
public void PushRange(T[] items)
public void PushRange(T[] items, int startIndex, int count)
 
public bool TryPeek( out T result)
 
public bool TryPop( out T result)
public int TryPopRange(T[] items)
public int TryPopRange(T[] items, int startIndex, int count)

ConcurrentBag<T>

这是一个无序的对象集合,而且支持对象重复。在同一线程下添加和删除效率高,又是需要锁。
public void Add(T item)
public bool TryPeek( out T result)
public bool TryTake( out T result)

ConcurrentDictionary<TKey,TValue>

并发的键值对,读是没有锁的,写会有细粒度的锁。
 
public TValue GetOrAdd(TKey key,TValue value)
public bool TryAdd(TKey key,TValue value)
public bool TryGetValue(TKey key, out TValue value)
public bool TryRemove(TKey key, out TValue value)
public bool TryUpdate(TKey key,TValue newValue,TValue comparisonValue)

IProducerConsumer<T>与BlockingCollection<T>

IProducerConsumerCollection<T>是对生产者-消费者模型的一个操作抽象,BlockingCollection<T>是一个实现。
针对生产者消费者模型,给出了很多方便的功能。
构造的时候可以设置最大容量,当集合达到最大容量,添加请求会被阻塞。
添加完成的判断也不需要自己在维护标识符
更有GetConsumingEnumerable(),获取集合迭代器,可以使用foreach,如果集合为空着会阻塞,并等待添加新的元素,如果集合没有元素并且IsAddingCompleted为true那么循环终止,可以省去不必要的自旋。
还支持超时和取消功能
 
private const int NUM_SENTENCES = 2000000;
private static BlockingCollection < string > _sentencesBC;
private static BlockingCollection < string > _capWordsInSentencesBC;
private static BlockingCollection < string > _finalSentencesBC;
 
private static void ProduceSentences(System.Threading.CancellationToken ct)
{
     //...
     if ( !_sentencesBC.TryAdd(newSentence, 2000, ct))
    {
         throw new TimeoutException(...);
    }
     //...
    _sentencesBC.CompleteAdding();
}
 
private static void CapitalizeWordsInSentences()
{
     //...
     while ( !_sentencesBC.IsCompleted)
    {
         string sentence;
         if (_sentencesBC.TryTake( out sentence))
        {
            _capWordsInSentencesBC.Add(...);
        }
    }
    _capWordsInSentencesBC.CompleteAdding();
}
 
private static void RemoveLettersInSentences()
{
     //...
     foreach (var sentence in _capWordsInSentencesBC.GetConsumingEnumerable())
    {
        _finalSentencesBC.Add(RemoveLetters(letterChars, sentence));
    }
    _finalSentencesBC.CompleteAdding();
}
 
static void Main( string[] args)
{
    _sentencesBC = new BlockingCollection < string >(NUM_SENTENCES);
    _capWordsInSentencesBC = new BlockingCollection < string >(NUM_SENTENCES);
    _finalSentencesBC = new BlockingCollection < string >(NUM_SENTENCES);
 
    var cts = new System.Threading.CancellationTokenSource();
    var ct = cts.Token;
    var deferredCancelTask = Task.Factory.StartNew(() = >
    {
        System.Threading.Thread.Sleep( 500);
        cts.Cancel();
    });
 
    Parallel.Invoke(
        () = > ProduceSentences(ct),
        () = > CapitalizeWordsInSentences(),
        () = > RemoveLettersInSentences()
        );
}

任务计数器

常常需要知道还在运行的Task的数量。所以需要对计数器进行原子的加减
可以在任务新建的时候使用System.Threading.Interlocked.Increment(ref tasksRunning)
在任务结束后System.Threading.Interlocked.Decrement(ref tasksRunning);
private static int tasksRunning = 0;
 
//inti method
for ( int i = 0; i < taskMax; i ++)
{
    System.Threading.Interlocked.Increment( ref tasksRunning);
    tasks[i] = Task.Factory.StartNew(() = >
    {
         try
        {
             //...
        }
         finally
        {
            System.Threading.Interlocked.Decrement( ref tasksRunning);
        }
    });
}
 
//other method
while ((tasksRunning > 0))
{
     //...
}

并发集合和不安全集合互转

并发集合可以结构一个IEnumerable接口的集合做构造函数参数。
例如
string[] _invalidHexValues = { "AF", "BD", "BF", "CF", "DA", "FA", "FE", "FF" };
var invalidHexValuesStack = new ConcurrentStack < string >(_invalidHexValues);
要把并发集合转成不安全的可以使用CopyTo ToArray等方法

volatile关键字

使用volatile可以确保在不同的线程中进行访问的时候,可以得到最新值。这些变量不会被编译器按照只在一个线程中的进行访问的假定进行优化。
private static volatile bool _flag = false;
 

 



你可能感兴趣的:(C#)