如何在.NET中最有效地利用多个内核进行简短计算?

时间:2010-02-07 04:19:15

标签: c# .net performance multithreading multicore

以下是上下文:我在C#中为一个小programming language called Heron编写一个解释器,它有一些可以并行执行的原始列表操作。

我面临的最大挑战之一是,只要遇到可并行化的操作,就可以有效地将评估者完成的工作分配到不同的核心。这可以是短期或长期操作,很难提前确定。

我不必担心的一件事是同步数据:显然不允许并行操作修改数据。

所以我的主要问题是:

  • 跨线程分发工作的最有效方法是什么,以便我可以保证计算机将工作分配到两个核心?

我也对相关问题感兴趣:

  • 在我们开始克服将工作分离到另一个线程的开销之前,操作需要多长时间?

3 个答案:

答案 0 :(得分:15)

如果您想对并行操作做很多事情,那么您将需要从.Net 4.0开始。这是Parallel Programming for .Net documentation。你会想start here though。 .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方法会自动扩展可用内核数量,您可以看到开始利用这一点的速度有多快。框架中有许多新的库,支持完整的线程/任务管理,就像您的示例一样(包括用于同步,取消等的所有管道)。

Parallel LINQ (PLINQ)Task FactoriesTask Schedulers和其他一些图书馆。简而言之,对于您制定的特定任务.Net 4.0对您有很大的好处,我go ahead and grab the free beta 2RC coming soon)并开始使用。 (不,我不为微软工作......但我很少看到即将推出的版本如此完美地满足需求,所以我强烈建议您使用.Net 4.0)

答案 1 :(得分:5)

因为我不想使用VS 2010进行开发,而且我发现ThreadPool没有在跨核心分配工作方面具有最佳性能(我认为因为它启动/停止了太多线程)我最终滚动我自己。希望其他人认为这有用:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace HeronEngine
{
    /// <summary>
    /// Represents a work item.
    /// </summary>
    public delegate void Task();

    /// <summary>
    /// This class is intended to efficiently distribute work 
    /// across the number of cores. 
    /// </summary>
    public static class Parallelizer 
    {
        /// <summary>
        /// List of tasks that haven't been yet acquired by a thread 
        /// </summary>
        static List<Task> allTasks = new List<Task>();

        /// <summary>
        /// List of threads. Should be one per core. 
        /// </summary>
        static List<Thread> threads = new List<Thread>();

        /// <summary>
        /// When set signals that there is more work to be done
        /// </summary>
        static ManualResetEvent signal = new ManualResetEvent(false);

        /// <summary>
        /// Used to tell threads to stop working.
        /// </summary>
        static bool shuttingDown = false;

        /// <summary>
        /// Creates a number of high-priority threads for performing 
        /// work. The hope is that the OS will assign each thread to 
        /// a separate core.
        /// </summary>
        /// <param name="cores"></param>
        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();
            }
        }

        /// <summary>
        /// Indicates to all threads that there is work
        /// to be done.
        /// </summary>
        public static void ReleaseThreads()
        {
            signal.Set();
        }

        /// <summary>
        /// Used to indicate that there is no more work 
        /// to be done, by unsetting the signal. Note: 
        /// will not work if shutting down.
        /// </summary>
        public static void BlockThreads()
        {
            if (!shuttingDown)
                signal.Reset();
        }

        /// <summary>
        /// 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.
        /// </summary>
        /// <returns></returns>
        public static Task GetTask()
        {
            lock (allTasks)
            {
                if (allTasks.Count == 0)
                {
                    BlockThreads();
                    return null;
                }
                Task t = allTasks.Peek();
                allTasks.Pop();
                return t;
            }
        }

        /// <summary>
        /// Primary function for each thread
        /// </summary>
        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();
                }
            }
        }

        /// <summary>
        /// Distributes work across a number of threads equivalent to the number 
        /// of cores. All tasks will be run on the available cores. 
        /// </summary>
        /// <param name="localTasks"></param>
        public static void DistributeWork(List<Task> 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);
        }

        /// <summary>
        /// Indicate to the system that the threads should terminate
        /// and unblock them.
        /// </summary>
        public static void CleanUp()
        {
            shuttingDown = true;
            ReleaseThreads();
        }
    }    
}

答案 2 :(得分:2)

我会使用线程池,即使它有问题,MS正在投资改进它,似乎.NET 4将有一个改进的。在这一点上,我认为最好的方法是使用包装在您自己的对象中的线程池并等待决定您自己的实现