c#知识总结-常用的并行库

目录

简介

 使用ConcurrentDictionary

使用ConcurrentQueue实现异步处理 

改变ConcurrentStack异步处理顺序 


简介

本章主要介绍并发编程相关的不同的数据结构。包含

  • 使用ConcurrentDictionary
  • 使用ConcurrentQueue实现异步处理
  • 改变ConcurrentStack异步处理顺序
  • 使用ConcurrentBag创建一个可扩展的爬虫
  • 使用BlockingCollection进行异步处理

ConcurrentQueue集合使用了原子的比较和交换(Compareand Swap,简称CAS)操作,以及SpinWait来保证线程安全。 他实现可一个先进先出的集合,这意味着元素出队列的顺序与加入队列的顺序是一致的。可以调用Enqueue方法向队列中加入元素。TryDequeue方法试图取出队列中的第一个元素,而TryPeek方法则试图得到第一个元素但并不从队列中删除该元素。

ConcurrentStack的实现也没有使用任何锁,只采用了CAS操作。它是一个后进先出( Last In First Out,简称LIFO)的集合,这意味着最近添加的元素会先返回。可以使用Push和PushRange方法添加元素,使用TryPop和TryPopRange方法获取元素,以及使用TryPeek方法检查元素。

ConcurrentBag是一个支持重复元素的无序集合。它针对这样以下情况进行了优化,即多个线程以这样的方式工作:每个线程产生和消费自己的任务,极少与其他线程的任务交互(如果要交互则使用锁)。添加元素使用Add方法,检查元素使用TryPeek方法,获取元素使用 TryTake方法。

请避免使用上面提及的集合的Count属性。实现这些集合使用的是链表,Count操作的时间复杂度为O(N)。如果想检查集合是否为空,请使用IsEmpty属性,其时间复杂度为O(1)。

ConcurrentDictionary是一个线程安全的字典集合的实现。对于读操作无需使用锁。但是对于写操作则需要锁。该并发字典使用多个锁,在字典桶之上实现了一个细粒度的锁模型。使用参数concurrencyLevel可以在构造函数中定义锁的数量,这意味着预估的线程数量将并发地更新该字典。由于并发字典使用锁,所以一些操作需要获取该字典中的所有锁。如果没必要请避免使用以下操作:Count、IsEmpty、Keys、Values、CopyTo及ToArray。

BlockingCollection是对IProducerConsumerCollection泛型接口的实现的一个高级封装。它有很多先进的功能来实现管道场景,即当你有一些步骤需要使用之前步骤运行的结果时。BlockingCollectione类支持如下功能:分块、调整内部集合容量、取消集合操作、从多个块集合中获取元素。

 使用ConcurrentDictionary

我们创建两个集合,其中一个标准的字典集合,另一个是并发字典集合。然后采用锁的机制向标准的字典中添加元素,并测量完成100万次迭代的时间。同样也采用同样的场景来测量ConcurrentDictionary的性能,最后比较从两个集合中获取值的性能。

using System.Collections.Concurrent;

const string item = " dictionary item";
const int Iteations = 1000000;
string currentItem;
var concurrentDic = new ConcurrentDictionary<int, string>();
var dic = new Dictionary<int, string>();

var sw = new Stopwatch();
sw.Start();

for (int i = 0; i < Iteations; i++)
{
    lock (dic)
    {
        dic[i] = item;
    }
}
sw.Stop();
WriteLine($"writing to dictionary with a lock:{sw.Elapsed}");

sw.Restart();
for (int i = 0; i < Iteations; i++)
{
    concurrentDic[i] = item;
}
sw.Stop();
WriteLine($"writing to concurrent dictionary :{sw.Elapsed}");

sw.Restart();
for (int i = 0; i < Iteations; i++)
{
    lock (dic)
    {
        currentItem = dic[i];
    }
}
sw.Stop();
WriteLine($"reading from dictionary with a lock:{sw.Elapsed}");

sw.Restart();
for (int i = 0; i < Iteations; i++)
{
    currentItem = concurrentDic[i];
}
sw.Stop();
WriteLine($"reading from a concurrent dictionary :{sw.Elapsed}");

结果如下,,我们发现ConcurrentDictionary写操作比使用锁的通常的字典要慢得多,而读操作则要快些。因此如果对字典需要大量的线程安全的读操作,ConcurrentDictionary是最好的选择。如果你对字典只需要多线程访问只读元素,则没必要执行线程安全的读操作。在此场景中最好只使用通常的字典或ReadOnlyDictionary集合。

使用ConcurrentQueue实现异步处理 

本节将展示创建能被多个工作者异步处理的一组任务例子。 使用  当程序运行时,我们使用ConcurrentQueue集合实例创建了一个任务队列。然后创建了一个取消标志,它是用来在我们将任务放入队列后停止工作的。接下来启动了一个单独的工作者线程来将任务放入任务队列中。该部分为异步处理产生了工作量。现在定义该程序中消费任务的部分。我们创建了四个工作者,它们会随机等待一段时间,然后从任务队列中获取一个任务,处理该任务,一直重复整个过程直到我们发出取消标志信号。最后,我们启动产生任务的线程,等待该线程完成。然后使用取消标志给消费者发信号我们完成了工作。最后一步将等待所有的消费者完成。

using System.Collections.Concurrent;

var t = RunProgram();
await t;
WriteLine("运行结束");
ReadLine();

async Task RunProgram()
{
    var taskQueue = new ConcurrentQueue<CustomTask>();
    var cts = new CancellationTokenSource();
    var taskSource = Task.Run(() => TaskProducer(taskQueue));
    Task[] processors = new Task[4];
    for (int i = 1; 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);
}

async Task TaskProducer(ConcurrentQueue<CustomTask> queue)
{
    for (int i = 0; i < 20; i++)
    {
        await Task.Delay(50);
        var workItem=new CustomTask { Id = i };
        queue.Enqueue(workItem);
        WriteLine($"Task {workItem.Id} has been posted");
    }
}

async Task TaskProcessor(ConcurrentQueue<CustomTask> queue,string name,CancellationToken token)
{
    CustomTask workItem;
    bool dequeueSucesful = false;

    await GetRandomDelay();
    do
    {
        dequeueSucesful=queue.TryDequeue(out workItem);
        if (dequeueSucesful)
        {
           WriteLine($"Task {workItem.Id} has been prcessed by name");
        }
        await GetRandomDelay();
    }
    while(!token.IsCancellationRequested);
}

Task GetRandomDelay()
{
    int delay = new Random(DateTime.Now.Millisecond).Next(1, 500);
    return Task.Delay(delay);
}

class CustomTask
{
    public int Id { get; set; }
}

结果如下图,我们看到队列中的任务按从前到后的顺序被处理,但一个后面的任务是有可能会比前面的任务先处理的,因为我们有四个工作者独立地运行,而且任务处理时间并不是恒定的。我们看到访问该队列是线程安全的,没有一个元素会被提取两次。

改变ConcurrentStack异步处理顺序 
  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值