我必须处理大约 170.000 个文件,并且想使用多个线程。文件的名称按照年份-编号格式顺序排列,并在文件夹中按年份排序。(但它们可以都在同一个文件夹中)。不同的年份有不同的文件数。文件很小,每个只有几个 (10<size<20) KB。
它们的处理顺序无关紧要,因为处理任务的输出将存储在 SQL 数据库中。最好的方法是什么?不打开同一个文件两次?
我必须处理大约 170.000 个文件,并且想使用多个线程。文件的名称按照年份-编号格式顺序排列,并在文件夹中按年份排序。(但它们可以都在同一个文件夹中)。不同的年份有不同的文件数。文件很小,每个只有几个 (10<size<20) KB。
它们的处理顺序无关紧要,因为处理任务的输出将存储在 SQL 数据库中。最好的方法是什么?不打开同一个文件两次?
一种可能的解决方案是使用生产者/消费者设计模式。
您的生产者将获得文件列表并提供一些ProducerConsumer
队列。您的消费者将处理从队列中取出的文件(或文件路径)并处理它(插入到您的数据库中)。使用这种方法,每个文件只会被处理一次。
C# producer/consumer SO question中描述了ProducerConsumer
队列问题。
编辑
但是,任务可能会变得复杂,例如
我会说每年1个线程。每个“年份线程”读取以该年份编号开头的文件,并按顺序读取它们。至于去数据库,我建议你要么
另一种解决方案是让线程生成对文件的插入语句,然后执行该文件以进行插入,或者您可以使用批量插入工具。但这取决于表结构和您的 DBMS
这是一个小例子:
public static class FilesProcessor
{
private static List<FileProcessor> m_FileProcessors;
public static void Start()
{
m_FileProcessors = new List<FileProcessor>();
for (Int32 year = 2005; year < DateTime.Now.Year; ++year)
InstanciateFileProcessor(year);
while (!FinishedLoading())
Application.DoEvents();
}
public static void Stop()
{
foreach (FileProcessor processor in m_FileProcessors)
processor.Stop()
m_FileProcessors.Clear();
m_FileProcessors = null;
}
private static Boolean FinishedLoading()
{
foreach (FileProcessor processor in m_FileProcessors)
{
if (processor.IsAlive() && !processor.FinishedLoading())
return false;
}
return true;
}
private static void InstanciateFileProcessor(Int32 year)
{
FileProcessor processor = new FileProcessor(year);
processor.Start();
m_FileProcessors.Add(processor);
}
}
然后是 FileProcessor 类:
public sealed class FileProcessor
{
private Int32 m_Year;
public Boolean IsAlive()
{
return ((m_Thread != null) && m_Thread.IsAlive);
}
public Boolean FinishedLoading()
{
return ((m_Thread == null) || m_Thread.Join(10));
}
public FileProcessor(Int32 year)
{
m_Year = year;
m_Thread = new Thread(Load);
m_Thread.Name = "Background File Processor";
}
public void Start()
{
if (m_Thread != null)
m_Thread.Start();
}
public void Stop()
{
if ((m_Thread != null) && m_Thread.IsAlive)
m_Thread.Abort();
}
private void Load()
{
// Browse the Year folder...
// Get and read all fines one by one...
}
}
我可以在这里看到两种可能的方法。
首先,将您的问题一分为二。1 - 确定要处理的内容,2 - 进行处理。第 1 部分可能必须自己运行,因此您最终会得到一个 100% 准确的需要处理内容的列表。然后,您可以在拆分列表和引入多个线程方面实现花哨(或不太花哨)的逻辑。
其次,做一些类似于@CarlosGrappa 建议的事情。所以基本上你用自己的“预编程”过滤器创建每个线程。正如卡洛斯建议的那样,这可能是一年。或者,您可以创建 24 个线程,每个文件时间戳一个小时。或 60 个线程,每个线程查看一个小时后的特定分钟。它基本上可以是任何可以为您提供明确标准的东西(a)尽可能均匀地分配负载,以及(b)保证数据文件只处理一次。
显然,这些方法中的第二种会运行得更快,但是您必须对如何拆分文件进行一些额外的思考。使用第一种方法,一旦你得到了完整的列表,你基本上可以一次在你的处理器上处理 100、1000 或 10000 个等文件,而不会过于聪明地知道你是如何做到的。
使用 .Net 的并行类有什么问题?
只需将一个集合传递给并行的 foreach 循环。.Net 为您完成所有分配。您还可以传入自定义分区器,以便使用块分区。块分区导致线程不断请求更多任务。如果你不使用块分区,所有的工作都将被预先分配,当一些任务比其他任务花费更长的时间时会导致一些性能下降(这可能导致一些线程空闲而一个线程仍有工作要做)。