基于以下场景分配线程的最有效方法是什么?

时间:2011-10-04 17:07:55

标签: c# .net multithreading performance api

我可以在任何时间最多同时运行5个线程,这会使用5个独立的硬件来加速某些复杂计算的计算并返回结果。每个硬件的API(仅包含一个方法)不是线程安全的,并且只能在任何时间点在单个线程上运行。计算完成后,可以根据可用性重新使用相同的线程在相同或不同的硬件上启动另一个计算。每个计算都是独立的,并不依赖于其他计算的结果。因此,最多5个线程可以按任何顺序完成其执行。

什么是最有效的C#(使用.Net Framework 2.0)编码解决方案,用于跟踪哪些硬件是空闲/可用的,并将线程分配给适当的硬件API以执行计算?请注意,除了5个并发运行线程的限制之外,我无法控制何时或如何触发线程。

如果我错了,请纠正我,但首选无锁解决方案,因为我相信它会提高效率并提供更具可扩展性的解决方案。

另请注意,这不是作业,尽管它可能听起来像......

4 个答案:

答案 0 :(得分:2)

.NET提供了一个可以使用的线程池。 System.Threading.ThreadPool.QueueUserWorkItem()告诉池中的一个线程为你做一些工作。

如果我设计这个,我不会专注于将线程映射到您的硬件资源。相反,我会为每个HW资源公开一个可锁定的对象 - 这可以只是一个5个对象的数组或队列。然后,对于您拥有的每个计算位,请调用QueueUserWorkItem()。在传递给QUWI的方法内部,找到下一个可用的可锁定对象并锁定它(也就是说,将其出列)。使用HW资源,然后重新入队该对象,退出QUWI方法。

你打电话给QUWI多少次都没关系;最多可以锁定5个锁,每个锁可以访问您特殊硬件设备的一个实例。

doc page for Monitor.Enter()显示了如何创建可由多个工作人员访问的安全(阻塞)队列。在.NET 4.0中,您将使用the builtin BlockingCollection - 它是相同的。

这基本上就是你想要的。除了不要致电Thread.Create()。使用线程池。

引用:Advantage of using Thread.Start vs QueueUserWorkItem


// assume the SafeQueue class from the cited doc page. 
SafeQueue<SpecialHardware> q = new SafeQueue<SpecialHardware>()

// set up the queue with objects protecting the 5 magic stones
private void Setup() 
{
    for (int i=0; i< 5; i++) 
    {
       q.Enqueue(GetInstanceOfSpecialHardware(i));
    }
}


// something like this gets called many times, by QueueUserWorkItem()
public void DoWork(WorkDescription d)
{
    d.DoPrepWork();

    // gain access to one of the special hardware devices
    SpecialHardware shw = q.Dequeue();
    try 
    {
        shw.DoTheMagicThing();
    }
    finally 
    {
        // ensure no matter what happens the HW device is released
        q.Enqueue(shw);
        // at this point another worker can use it.
    }

    d.DoFollowupWork(); 
}

答案 1 :(得分:1)

无锁解决方案仅在计算时间非常短时才有用。

我会为每个作业入队的硬件线程创建一个外观,并在每次作业完成时调用回调。

类似的东西:

public class Job
{
    public string JobInfo {get;set;}
    public Action<Job> Callback {get;set;}
}

public class MyHardwareService
{
    Queue<Job> _jobs = new Queue<Job>();
    Thread _hardwareThread;
    ManualResetEvent _event = new ManualResetEvent(false);

    public MyHardwareService()
    {
        _hardwareThread = new Thread(WorkerFunc);
    }

    public void Enqueue(Job job)
    {
      lock (_jobs)
        _jobs.Enqueue(job);

       _event.Set();
    }

    public void WorkerFunc()
    {
        while(true)
        {
             _event.Wait(Timeout.Infinite);
             Job currentJob;
             lock (_queue)
             {
                currentJob = jobs.Dequeue();
             }

             //invoke hardware here.

             //trigger callback in a Thread Pool thread to be able
             // to continue with the next job ASAP
             ThreadPool.QueueUserWorkItem(() => job.Callback(job));

            if (_queue.Count == 0)
              _event.Reset();

        }
    }
}

答案 2 :(得分:0)

听起来你需要一个包含5个线程的线程池,每个线程在完成后放弃HW并将其添加回某个队列。那会有用吗?如果是这样,.Net使线程池非常容易。

答案 3 :(得分:0)

听起来很像Sleeping barber problem。我相信标准解决方案是使用semaphores