【问题标题】:What is the most efficient method for assigning threads based on the following scenario?根据以下场景分配线程的最有效方法是什么?
【发布时间】:2011-10-04 17:07:54
【问题描述】:

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

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

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

还请注意,这不是家庭作业,虽然听起来很像……

【问题讨论】:

  • 5 个独立的硬件是什么意思?您想分发超过 5 台机器吗?
  • 我想象他有 5 个东西,比如板载协处理器设备。也许它们是特殊的硬件加密设备。无论如何 - 他们做一些计算密集型的事情,比如说。他们每个人一次只能做一件事。它们不是远程通用计算机,因此这不是分布式计算的问题。相反,它们是专用资源,由单个程序使用。

标签: c# .net multithreading performance api


【解决方案1】:

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

如果我设计这个,我不会专注于将线程映射到您的硬件资源。相反,我会为每个硬件资源公开一个可锁定的对象——这可以只是一个包含 5 个对象的数组或队列。然后,对于您拥有的每一位计算,请致电QueueUserWorkItem()。在传递给 QUWI 的方法中,找到下一个可用的可锁定对象并将其锁定(也称为出列)。使用硬件资源,然后重新入队对象,退出 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(); 
}

【讨论】:

  • 使用这样的锁会阻塞每个想要访问硬件的线程,直到它可以。因此,如果 20 个不同的线程想要访问相同的硬件,它们将全部阻塞,直到它们获得访问权。我的解决方案将接受一个工作请求,然后让每个调用线程继续进行其他工作。您可能还想详细说明为什么在这种情况下使用专用线程是一件坏事,因为五个线程不会占用太多资源,但会使代码更易于跟踪和调试。
  • Using locks like that would block each thread that want to access the hardware until it can. 我认为这就是目标。一次只允许一个线程访问每个硬件资源。
  • 重点是Hence if 20 different threads want to access the same HW they will all block until they get access。通过使用避免使用的专用线程。
  • 正确:通过使用专用线程,您可以避免 N 个线程中的任何一个阻塞等待访问 M 个资源的情况。但在这种情况下,您必须明确管理一个 jobs 池,这比我不管理任何内容的方法更复杂。我没有看到显式管理作业池的好处,这样您就可以有 5 个线程在工作并且没有线程阻塞。谁在乎线程是否阻塞? QUWI 让您只需将工作转储到线程池中,就可以忘记它。您只需要一个并发队列。
  • 使用线程池处理阻塞操作的问题是它会导致线程池饥饿。对于无需等待即可完成的工作,“一劳永逸”的方法通常要好得多。
【解决方案2】:

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

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

类似:

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();

        }
    }
}

【讨论】:

  • 移动了我对您问题的回答,因为它更适合那里。
  • 使用... 什么之类的锁?除非您有特定的原因来管理自己的线程,否则建议使用 QUWI。原因是众所周知的,并记录在其他地方。基本上:让 .NET 为您完成;您的应用可能对机器的了解不够,无法很好地处理线程调度和优化,因此无论如何也不应该专注于此。
  • 没有。如果线程将等待硬件之类的东西,则不推荐使用线程池。您可能会导致线程池饥饿。
【解决方案3】:

听起来您需要一个包含 5 个线程的线程池,其中每个线程在完成后放弃硬件并将其添加回某个队列。那行得通吗?如果是这样,.Net 使线程池变得非常容易。

【讨论】:

  • 你不需要创建一个包含 5 个线程的池。 .NET 有一个线程池,它有 N 个线程,你通常不在乎有多少。这里重要的是5个资源。您需要 5 个可锁定对象来保护 5 个资源。然后池中的任何线程都可以锁定该对象,使用它保护的资源,然后释放该对象。启动尽可能多的后台工作任务,最多同时运行 5 个。
  • 我认为这是您确实关心线程数的特殊情况。如果任何额外的线程肯定会阻塞,你为什么要创建超过 5 个?收获了什么?
  • 不知道。可能是在锁定硬件资源之前,线程可以完成一些工作。在硬件资源上的工作完成后,线程可能会进行一些后处理。但是让额外的线程在资源锁队列上等待几乎没有成本。这是一个简单的抽象。
【解决方案4】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-20
    • 2016-11-09
    • 1970-01-01
    相关资源
    最近更新 更多