实现并发操作的最佳方式

时间:2017-04-22 11:56:26

标签: c# multithreading asynchronous async-await task-parallel-library

我有两个包含不同长度字符串的列表。我需要使用算法将两个列表相互比较,让我们说AlgorithmX将确定是否应该保留ListA中的字符串。这些清单很长(每个清单超过5万个项目),长度可以从1个字符到5000个字符不等。

所以我们至少要查看50 000 * 50 000个比较(而不是直接比较,列表中的每个字符串都通过AlgorthmX

现在我的多线程知识相当低。但我一直在阅读它。但是在.Net中有各种各样的实现。标准线程,任务,等待异步。

使用AlgorithmX使用多线程比较列表的最佳方法是什么?即我应该使用等待异步,任务库吗?

我是否应该按CPU平均分割ListA的项目并针对ListB运行算法?我应该在拆分列表上使用concurrentQueue吗?

[编辑] ConcurrentQueue似乎是我正在寻找的..但欢迎任何添加 http://www.erikbergman.net/2016/03/17/high-speed-applications-parallelism-in-net-part-2/

欢迎任何指示!

2 个答案:

答案 0 :(得分:1)

一种可能的实现,它使用ConcurrentQueue来存储算法决定保留的字符串(注意:没有特别的理由不使用其他一些线程安全的集合)

此方法使用Parallel.ForEach来遍历ListA

private static void Main()
{
    var listA = new[] {"the", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog"};
    var listB = new List<string>(new[] {"fox", "dog", "cat", "mouse"});
    var stringsToKeep = new ConcurrentQueue<string>();

    Parallel.ForEach(listA, a =>
    {
        var shouldKeep = AlgorithmX(a, listB);
        if (shouldKeep)
        {
            stringsToKeep.Enqueue(a);
        }
    });

    Console.WriteLine($"Matching strings: {string.Join(", ", stringsToKeep.ToArray())}");
}

假设:

  1. 在进行比较时,不需要修改ListAListB。只要这些没有被修改,并发访问就没有问题(在多线程环境中对List<>的只读访问很好 - 只有在修改列表时才会出现并发问题 - 例如Insert /删除/等。)。
  2. AlgorithmX没有副作用(即方法是“纯粹的”,因此线程安全 - 可以同时调用,没有问题)
  3. ListA中字符串的顺序不需要反映在结果中(即您不关心算法决定保留的字符串的顺序)。

答案 1 :(得分:0)

  

然而.Net中有[多线程]的各种实现。标准线程,任务,等待异步。

     

使用AlgorithmX使用多线程比较列表的最佳方法是什么?即我应该使用等待异步,任务库吗?

如果您退后一步,可以看到此处async / await不合适。 &#34;并发&#34;一次只做一件事。异步是一种并发形式,使用专用线程进行操作 - 您可以将其视为更基于事件的。并行性是一种并发形式,它使用多个线程进行操作。异步和并行是两种形式的并发,但它们在实现细节方面是相反的。因此,异步通常用于I / O绑定或其他与CPU无关的任务,如定时器;并行性通常用于CPU工作,就像运行一百万次计算算法一样。

关于并行性,那么有a few tools you have available。一般来说,最好使用具有最高抽象的工具。直接使用手动线程,就像直接使用线程池一样。

在您的情况下,您希望使用Parallel或Parallel LINQ,它们都构建在任务并行库之上,后者又使用线程池。

有一个great answer using Parallel already,但我发现如果你需要一个操作的结果,Parallel LINQ通常会产生更简单的代码。在这种情况下:

static List<string> RunAlgorithm(List<string> listA, List<string> listB)
{
  return listA.AsParallel()
      .Where(a => AlgorithmX(a, listB)).ToList();
}

此代码与其他答案中的Parallel.ForEach代码具有相同的限制(即,RunAlgorithm期间未修改列表,AlgorithmX是线程安全的,以及生成的字符串的顺序没有保留。)

并行LINQ在具有输出的并行算法方面更好一些,并且还有一些专门用于管理这些输出的辅助程序。例如,保留原始订单:

static List<string> RunAlgorithm(List<string> listA, List<string> listB)
{
  return listA.AsParallel().AsOrdered()
      .Where(a => AlgorithmX(a, listB)).ToList();
}