限制运行线程的安全方法C#

时间:2015-04-22 16:05:37

标签: c# multithreading locking

我正在编写一个可以执行各种任务的程序。我已经设置了我称之为"任务队列"在其中我将不断抓取下一个要处理的任务(如果有的话)并启动一个新线程来处理该任务。但是,出于明显的原因,我想限制一次可以生成的线程数量。我创建了一个变量来跟上要生成的最大线程和当前线程数的一个。我正在考虑使用锁来尝试并准​​确地跟上当前的线程数。这是我的一般想法。

SELECT   ProductCode, 
         COUNT (CASE WHEN MONTH(EntryDate) = @Month THEN Quantity ELSE NULL END) 
           AS MonthCount,
         COUNT (*) AS TotalConount
FROM     ProductMaster
GROUP BY ProductCode

因为我在两个地方锁定以访问同一个变量(在启动新线程时递增并在结束时递减)是否有可能等待锁定减少的线程可能挂起并且永远不会结束?从而达到最大线程数,永远无法启动另一个?对我的方法有任何其他建议吗?

1 个答案:

答案 0 :(得分:1)

最简单的问题......:)

  

...等待锁定减少的线程是否有可能挂起并永远不会结束?

不,不在您发布的代码中。在等待计数改变时,没有代码持有锁,或类似的东西。您只需要锁定,然后修改计数或发出消息,并立即释放锁定。所以没有线程会无限期地持有锁,也没有嵌套锁(如果做错了可能会导致死锁)。

现在,说:从你发布的代码和你的问题来看,这里的意图并不完全清楚。写入的代码确实会限制创建的线程数。但是一旦达到这个限制(并且它会快速完成),主循环就会旋转,报告"Max Thread Count Reached."

实际上,如果总循环次数为100,我认为整个循环可能在第一个线程运行之前完成,这取决于系统上CPU内核的占用情况。如果某些线程确实运行并且它们中的一些线程的持续时间非常短,那么您可能会在稍后的几个线程中潜入。但是循环的大多数迭代都会看到线程计数达到最大值,报告已达到限制并继续循环的下一次迭代。

你在评论中写道(你应该把它真正放在问题中本身,如果你认为它是相关的)"主线程永远不应该被阻止&#34 ;。当然,问题是,什么是主要线程在没有被阻止时做什么?主线程如何知道是否以及何时尝试安排新线程?

如果你想要一个非常有用的答案,这些是重要的细节。

请注意,您已获得使用信号量的建议(具体而言,SemaphoreSlim)。这个可能是一个好主意,但请注意,该类通常用于协调所有竞争相同资源的多个线程。为了使它有用,你实际上拥有更多而不是10个线程,信号量确保在给定时间内只有10个线程运行。

在您的情况下,在我看来,您实际上是在询问如何避免首先创建额外的线程。即你希望主循环检查计数,如果达到最大计数则根本不创建一个线程。在这种情况下,一种可能的解决方案可能是直接使用Monitor类:

private static void StartNewThread() {
    lock(mobjLock) {
        while (mintThreadCount >= mintMaxThreadCount) {
           Console.WriteLine("Max Thread Count Reached.");
           Monitor.Wait(mobjLock);
        }

        Thread newThread = new Thread(StartTask);
        newThread.Start(mintThreadCount);
        mintThreadCount++;
        }
    }
}

以上将导致StartNewThread()方法等到计数低于最大值,然后将始终创建一个新线程。

当然,每个线程都需要发出信号,告知它已经更新了计数,以便上述循环可以从等待中释放并检查计数:

private readonly Random _rnd = new Random();

private static void StartTask(object iCurrentThreadCount) {
    int id = _rnd.Next(0, 1000000);
    Console.WriteLine("New Thread with id of: " + id.ToString() + " Started. Current Thread count: " + ((int)iCurrentThreadCount).ToString());
    Thread.Sleep(_rnd.Next(0, 3000));
    lock(mobjLock) {
        Console.WriteLine("Ending thread with id of: " + id.ToString() + " now.");
        mintThreadCount--;
        Console.WriteLine("Thread space release by id of: " + id.ToString() + " . Thread count now at: " + mintThreadCount);
        Monitor.Pulse(mobjLock);
    }
}

上述问题是阻止主循环。如果我理解正确,你就不想要了。

注意:您的代码中存在一个常见但严重的错误,因为每次需要随机数时都会创建一个新的Random对象。要使用{ {1}}类正确,你必须创建一个实例并重新使用它,因为你想要新的随机数。我已经调整了上面的代码示例来解决这个问题。)

上述问题和原始版本的其他问题之一是每个新任务都分配了一个全新的线程。线程的创建成本很高,甚至只是存在,这就是存在线程池的原因。根据您的实际情况,您应该使用例如<{1}},RandomParallel来管理您的任务。

但是如果你真的想明确地这样做,一个选择就是简单地启动十个线程,并让它们从ParallelEnumerable检索数据。由于你开始只有十个线程,你知道你永远不会有更多的线程。当所有十个线程都有足够的工作忙时,它们就会存在。否则,队列将为空,部分或全部将等待队列中显示的新数据。空闲,但不使用任何CPU资源。

例如:

Task

您只需在某处调用BlockingCollection<T>,而不是多次调用其他private BlockingCollection<int> _queue = new BlockingCollection<int>(); private static void StartThreads() { for (int i = 0; i < mintMaxThreadCount; i++) { new Thread(StartTask).Start(); } } private static void StartTask() { // NOTE: a random number can't be a reliable "identification", as two or // more threads could theoretically get the same "id". int id = new Random().Next(0, 1000000); Console.WriteLine("New Thread with id of: " + id.ToString() + " Started."); foreach (int i in _queue) { Thread.Sleep(i); } Thread.Sleep(new Random().Next(0, 3000)); } 方法。据推测,在你提到的StartThreads()循环之前。

然后,由于需要处理某个任务,您只需将数据添加到队列中,例如:

StartNewThread()

当您希望线程全部退出时(例如,在主循环退出后,无论如何发生):

while (true)

这会导致每个_queue.Add(_rnd.Next(0, 3000)); 循环结束,让每个线程退出。

当然,_queue.CompleteAdding(); 的{​​{1}}类型参数可以是任何内容。据推测,在你的情况下,它实际上代表了一个&#34;任务&#34;。我使用foreach,只是因为这实际上是你的任务&#34;在你的例子中(即线程应该睡眠的毫秒数)。

然后你的主线程可以正常做任何事情,调用T方法根据需要将新工作分配给你的消费者线程。

同样,在没有更多细节的情况下,我无法评论这种方法是否比使用.NET中的内置任务运行机制更好 。但考虑到你到目前为止所解释的内容,它应该运作良好。

相关问题