如何在.NET中最有效地利用多个内核进行简短计算?
下面是上下文:我正在用C#编写一个名为Heron的小编程语言的解释器,它有一些可以并行执行的原始列表操作。
我面临的最大挑战之一是,只要遇到可并行化的操作,就可以有效地将评估者完成的工作分配到不同的核心。 这可以是短期或长期操作,很难提前确定。
我不必担心的一件事是同步数据:显然不允许并行操作修改数据。
所以我的主要问题是:
- 跨线程分配工作的最有效方法是什么,这样我才能保证计算机将工作分配到两个核心?
我也对相关问题感兴趣:
- 在我们开始克服将工作分离到另一个线程的开销之前,操作需要多长时间?
如果你想通过并行操作做很多事情,那么你将需要从.Net 4.0开始。 这是.Net文档的并行编程 。 你想要从这里开始 。 .Net 4.0在多核利用方面增加了很多。 这是一个简单的例子:
当前3.5串口方法:
for(int i = 0; i < 30000; i++) { doSomething(i); }
新的.Net 4.0并行方法:
Parallel.For(0, 30000, (i) => doSomething(i));
Parallel.For方法会自动扩展可用内核的数量,您可以看到开始利用它的速度有多快。 框架中有许多新库,支持完整的线程/任务管理,例如您的示例(包括用于同步,取消等的所有管道)。
有并行LINQ(PLINQ) , 任务工厂 , 任务调度程序和其他一些库。 简而言之,对于你提出的具体任务.Net 4.0对你有很大的好处,我会继续前进并获得免费的beta 2 ( RC即将推出 )并开始使用。 (不,我不为微软工作......但我很少看到即将推出的版本如此完美地满足需求,所以我强烈建议您使用.Net 4.0)
因为我不想使用VS 2010进行开发,我发现ThreadPool
没有最佳的性能来分配跨核心的工作(我认为因为它启动/停止了太多的线程)我最终滚动了自己的。 希望其他人认为这有用:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace HeronEngine { /// /// Represents a work item. /// public delegate void Task(); /// /// This class is intended to efficiently distribute work /// across the number of cores. /// public static class Parallelizer { /// /// List of tasks that haven't been yet acquired by a thread /// static List allTasks = new List (); /// /// List of threads. Should be one per core. /// static List threads = new List (); /// /// When set signals that there is more work to be done /// static ManualResetEvent signal = new ManualResetEvent(false); /// /// Used to tell threads to stop working. /// static bool shuttingDown = false; /// /// Creates a number of high-priority threads for performing /// work. The hope is that the OS will assign each thread to /// a separate core. /// /// public static void Initialize(int cores) { for (int i = 0; i < cores; ++i) { Thread t = new Thread(ThreadMain); // This system is not designed to play well with others t.Priority = ThreadPriority.Highest; threads.Add(t); t.Start(); } } /// /// Indicates to all threads that there is work /// to be done. /// public static void ReleaseThreads() { signal.Set(); } /// /// Used to indicate that there is no more work /// to be done, by unsetting the signal. Note: /// will not work if shutting down. /// public static void BlockThreads() { if (!shuttingDown) signal.Reset(); } /// /// Returns any tasks queued up to perform, /// or NULL if there is no work. It will reset /// the global signal effectively blocking all threads /// if there is no more work to be done. /// /// public static Task GetTask() { lock (allTasks) { if (allTasks.Count == 0) { BlockThreads(); return null; } Task t = allTasks.Peek(); allTasks.Pop(); return t; } } /// /// Primary function for each thread /// public static void ThreadMain() { while (!shuttingDown) { // Wait until work is available signal.WaitOne(); // Get an available task Task task = GetTask(); // Note a task might still be null becaue // another thread might have gotten to it first while (task != null) { // Do the work task(); // Get the next task task = GetTask(); } } } /// /// Distributes work across a number of threads equivalent to the number /// of cores. All tasks will be run on the available cores. /// /// public static void DistributeWork(List localTasks) { // Create a list of handles indicating what the main thread should wait for WaitHandle[] handles = new WaitHandle[localTasks.Count]; lock (allTasks) { // Iterate over the list of localTasks, creating a new task that // will signal when it is done. for (int i = 0; i < localTasks.Count; ++i) { Task t = localTasks[i]; // Create an event used to signal that the task is complete ManualResetEvent e = new ManualResetEvent(false); // Create a new signaling task and add it to the list Task signalingTask = () => { t(); e.Set(); }; allTasks.Add(signalingTask); // Set the corresponding wait handler handles[i] = e; } } // Signal to waiting threads that there is work ReleaseThreads(); // Wait until all of the designated work items are completed. Semaphore.WaitAll(handles); } /// /// Indicate to the system that the threads should terminate /// and unblock them. /// public static void CleanUp() { shuttingDown = true; ReleaseThreads(); } } }
我会选择线程池,即使它有问题,MS正在投资改进它,似乎.NET 4将有一个改进的。 在这一点上,我认为最好的方法是使用包装在您自己的对象中的线程池并等待决定您自己的实现