c# - 如何适当限制多线程应用程序?

标签 c# multithreading sleep throttling

我有一个在64位Windows 2008 r2服务器上运行的C#控制台应用程序,该服务器还承载MSSQL Server 2005。

该应用程序遍历文本文件,读取行,将行值拆分为变量,然后将数据插入在localhost托管的SQL数据库中。

每个Text文件是一个新线程,每行是一个新线程,并且每个SQL insert语句都在一个新线程下执行。

我正在计算每种类型的线程的数量,并在它们完成时递减。我想知道什么是最好的方式来“挂起”将来的线程以免打开...

例如..在打开新的SQL插入线程之前,我正在调用...

while(numberofcurrentthreads > specifiednumberofthreads)
{
// wait
}
new.Thread(insertSQL);

其中指定的线程数估计为不会引发System.OutofMemoryExceptions的值。在为每个过程确定该数字时,已经进行了大量的猜测工作。

我的问题是..是否有更“有效”或适当的方式来做到这一点?有没有一种方法可以读取系统内存,而不是物理内存,并根据指定的资源分配进行等待?

为了说明这个想法...
while(System.Memory < (System.Memory/2) || System.OutofMemory == true)
{
// wait
}
new.Thread(insertSQL);

我正在使用的当前方法可以在相当长的时间内完成并完成..但是它可以做得更好。整个过程中的某些文本文件比其他文件大,并且不一定充分利用系统资源...

例如,如果我说当两个文本文件均小于300KB时,一次处理2个文本文件就可以正常工作。如果一个或两个超过100,000KB,则效果不佳。

似乎还有一个“黄油区”,在这里事物的处理效率最高。某个地方平均约占所有CPU资源的75%。将这些值设置得过高,它将在100%CPU上运行,但处理速度会变慢,因为它无法跟上。

最佳答案

为每个文件,每一行以及每个SQL插入语句创建一个新线程是很疯狂的。使用三个线程和一个链接的生产者-消费者模型可能会好得多,它们全部通过线程安全队列进行通信。在C#中,这将是BlockingCollection

首先,设置两个队列,一个队列用于从文本文件中读取的行,另一个队列用于已处理的行:

const int MaxQueueSize = 10000;
BlockingCollection<string> _lines = new BlockingCollection<string>(MaxQueueSize);
BlockingCollection<DataObject> _dataObjects = new BlockingCollection<DataObject>(MaxQueueSize);

顺便说一句,DataObject是我所说的要插入数据库的对象。你不说那是什么。对于本讨论而言,这实际上并不重要,但是您可以将其替换为用于表示已处理字符串的任何类型。

现在,您创建三个线程:
  • 一个线程,该线程逐行读取文本文件,并将这些行放入_lines队列中。
  • 一种行处理器,它从_lines队列中逐行读取行,对其进行处理,然后创建一个DataObject,然后将其放置在_dataObjects队列中。
  • 读取_dataObjects队列并将其插入数据库的线程。

  • 除了简单性(这很容易组合在一起)之外,此模型还有很多好处。

    首先,同时从磁盘读取多个线程通常会导致性能降低,因为磁盘驱动器一次只能做一件事。多个线程同时命中磁盘只会导致不必要的磁头搜寻。只有一个线程将使您的输入队列充满。

    其次,限制队列的大小将防止内存不足。当磁盘读取线程尝试将第10,001个项目插入队列时,它将等待,直到处理线程删除该项目。那是BlockingCollection的“阻塞”部分。

    您可能会发现,可以通过对SQL插入进行分组并一次发送一堆记录来加快SQL插入的速度,这实际上一次执行了100或1000条记录的批量插入,而不是发送100或1000个单独的事务。

    此解决方案避免了线程过多的问题。您有固定数量的线程,所有线程都尽可能快地运行。并且通过限制队列中可以容纳的事物的数量来限制内存的使用。

    该解决方案也可以很好地扩展。如果您有多个驱动器上的文件,则可以添加第二个文件读取线程以从另一个物理驱动器读取文件,并将这些行放在同一队列中。 BlockingCollection支持多个生产者和多个使用者,因此添加另一个生产者根本没有问题。

    消费者也是如此。如果发现处理步骤是瓶颈,则可以添加另一个处理线程。它也将从_lines队列中读取并写入dataObjects队列。

    但是,线程多于处理器核心可能会使程序变慢。如果您有四核处理器,那么创建8个处理线程将无济于事。这会使事情变慢,因为操作系统将在线程上下文切换上花费大量时间,而不是花在做有用的工作上。

    您必须进行一些微调才能获得最佳性能。队列大小应足够大以支持连续的工作流(因此,没有线程会因工作不足而饿死,或者花费太多时间等待输出队列),但又不能太大,以至于无法填满内存。根据三个阶段的相对速度,一个队列可能必须大于另一个。如果三个阶段之一是瓶颈,则可以在该阶段添加另一个线程来提供帮助。

    我使用文本文件输入和输出创建了此模型的简单示例。对于您的情况,应该很容易扩展。请参阅Simple Multithreading,以及后续内容Part 2

    关于c# - 如何适当限制多线程应用程序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27392634/

    相关文章:

    c# - 由于线程退出或 WCF 中的应用程序请求,I/O 操作已中止

    c# - 检测重复记录,只选择第一个并用 LINQ/C# 计数

    .net - BackgroundWorker:一旦 DoWork() 事件处理程序完成,它就死了吗?

    java - Java 中是否可以进行 "atomic"中断检查?

    java - 为 WindowBuilder 制作 sleep 方法? [Java]

    c# - 这个字符串有什么问题?

    c# - 如何判断枚举属性是否已设置? C#

    Java - 多线程中的多个选择器用于非​​阻塞套接字

    multithreading - Perl ithreads : Do some math instead of sleeping

    Java sleep 行为过早