C# 与常规线程的并行 我试图理解为什么Auth.After能够在下面的场景中胜过一些线程:考虑一批可以并行处理的作业。在处理这些作业时,可能会添加新的工作,然后也需要处理这些工作。Parallel.For解决方案如下所示: var jobs = new List<Job> { firstJob }; int startIdx = 0, endIdx = jobs.Count; while (startIdx < endIdx) { Parallel.For(startIdx, endIdx, i => WorkJob(jobs[i])); startIdx = endIdx; endIdx = jobs.Count; } var jobs=新列表{firstJob}; int startIdx=0,endIdx=jobs.Count; while(startIdxWorkJob(jobs[i])); startIdx=endIdx;endIdx=jobs.Count; }
这意味着有多次并行.For需要同步。考虑面包优先图算法;同步的数量将相当大。浪费时间,不是吗 在老式的线程方法中尝试同样的方法:C# 与常规线程的并行 我试图理解为什么Auth.After能够在下面的场景中胜过一些线程:考虑一批可以并行处理的作业。在处理这些作业时,可能会添加新的工作,然后也需要处理这些工作。Parallel.For解决方案如下所示: var jobs = new List<Job> { firstJob }; int startIdx = 0, endIdx = jobs.Count; while (startIdx < endIdx) { Parallel.For(startIdx, endIdx, i => WorkJob(jobs[i])); startIdx = endIdx; endIdx = jobs.Count; } var jobs=新列表{firstJob}; int startIdx=0,endIdx=jobs.Count; while(startIdxWorkJob(jobs[i])); startIdx=endIdx;endIdx=jobs.Count; },c#,multithreading,performance,C#,Multithreading,Performance,这意味着有多次并行.For需要同步。考虑面包优先图算法;同步的数量将相当大。浪费时间,不是吗 在老式的线程方法中尝试同样的方法: var queue = new ConcurrentQueue<Job> { firstJob }; var threads = new List<Thread>(); var waitHandle = new AutoResetEvent(false); int numBusy = 0; for (int i = 0; i < maxT
var queue = new ConcurrentQueue<Job> { firstJob };
var threads = new List<Thread>();
var waitHandle = new AutoResetEvent(false);
int numBusy = 0;
for (int i = 0; i < maxThreads; i++)
threads.Add(new Thread(new ThreadStart(delegate {
while (!queue.IsEmpty || numBusy > 0) {
if (queue.IsEmpty)
// numbusy > 0 implies more data may arrive
waitHandle.WaitOne();
Job job;
if (queue.TryDequeue(out job)) {
Interlocked.Increment(ref numBusy);
WorkJob(job); // WorkJob does a waitHandle.Set() when more work was found
Interlocked.Decrement(ref numBusy);
}
}
// others are possibly waiting for us to enable more work which won't happen
waitHandle.Set();
})));
threads.ForEach(t => t.Start());
threads.ForEach(t => t.Join());
var queue=newconcurrentqueue{firstJob};
var threads=newlist();
var waitHandle=新的自动重置事件(false);
int numBusy=0;
对于(int i=0;i0){
if(queue.IsEmpty)
//numbusy>0意味着可能会有更多数据到达
waitHandle.WaitOne();
工作;
if(queue.TryDequeue(out作业)){
联锁增量(参考编号Y);
WorkJob(job);//找到更多工作时,WorkJob执行waitHandle.Set()
联锁减量(参考编号Y);
}
}
//其他人可能正在等待我们完成更多不会发生的工作
waitHandle.Set();
})));
threads.ForEach(t=>t.Start());
threads.ForEach(t=>t.Join());
因为代码当然要干净得多,但我无法理解的是,它甚至更快!任务调度器就是那么好吗?同步被删除了,没有繁忙的等待,但是线程化方法始终较慢(对我来说)。发生什么事?线程方法可以更快吗
编辑:谢谢所有的答案,我希望我能选择多个。我选择了同样显示实际可能改进的代码。这两个代码示例实际上并不相同
Parallel.ForEach()
将使用有限数量的线程并重新使用它们。第二个示例已经落后了很多,因为它必须创建许多线程。这需要时间
而maxThreads
的值是多少?非常关键,在Parallel.ForEach()
中,它是动态的
任务调度器就是那么好吗
非常好。第三方物流使用工作窃取和其他适应性技术。您将很难做得更好。创建一组新线程和并行线程。For是使用线程池。如果使用C#threadpool,您会看到更好的性能,但这样做毫无意义
我会回避推出你自己的解决方案;如果您需要定制,请使用TPL和customize..并行。For实际上不会将项目分解为单个工作单元。它根据计划使用的线程数和要执行的迭代数(早期)分解所有工作。然后让每个线程同步处理该批处理(可能使用工作窃取或保存一些额外的项以在接近结束时实现负载平衡)。通过使用这种方法,工作线程实际上从不互相等待,而您的线程由于每次迭代之前/之后使用的大量同步而不断地互相等待 最重要的是,由于它使用线程池线程,它需要的许多线程可能已经创建,这是它的另一个优势 至于同步,并行的整个要点。因为所有的迭代都可以并行完成,所以几乎不需要进行同步(至少在它们的代码中) 当然还有线程数量的问题。threadpool有很多非常好的算法和启发式方法,可以帮助它根据当前硬件、其他应用程序的负载等,确定此时需要多少线程。可能是使用的线程太多或不够 此外,由于您在开始之前不知道项目的数量,我建议使用
Parallel.ForEach
而不是几个Parallel.For
循环。它只是针对您所处的情况而设计的,因此它的启发式方法将更好地应用。(这也使得代码更加清晰。)
BlockingCollection队列=新建BlockingCollection();
//将作业添加到队列,可能在另一个线程中
//没有更多作业可运行时调用queue.CompleteAdding()
Parallel.ForEach(queue.getconsumineGenumerable(),
job=>job.DoWork());
如果已经有了一个更干净、更快的解决方案,你为什么要尝试让它更快呢?因为我认为有一个明显的缺陷是可以消除的。最后一个问题,你确实意识到这两段代码就像白天和黑夜一样不同,对吗?一方面,在已知数量的静态列表作业(startIdx和endIdx之间)上有一个并行循环,另一方面,有一组线程与队列和可等待事件竞争,其间有两个互锁的排列。换言之,线程代码是一个很好的例子。@Remus:谢谢,另一个很好的例子。线程示例也重新使用它创建的线程。它只启动有限数量的线程,如果你的意思是这样的话,不是每个任务都启动一个线程。请告诉我,使用线程池还是不使用线程池@亨克:我想我会将它设置为2到10之间的某个位置。实际上,这种方法似乎不可能,因为您不知道何时调用queue.completedadding()
。只有当队列都是空的,并且没有人在处理更多的项目时,才会出现这种情况。@FrankRazenberg Nope。您只需调用Comp
BlockingCollection<Job> queue = new BlockingCollection<Job>();
//add jobs to queue, possibly in another thread
//call queue.CompleteAdding() when there are no more jobs to run
Parallel.ForEach(queue.GetConsumingEnumerable(),
job => job.DoWork());