C# 使用.NET进行多线程文件处理
有一个包含1000个小文本文件的文件夹。我的目标是在文件夹中填充更多文件的同时解析和处理所有这些文件。我的意图是对这个操作进行多线程处理,因为单线程原型需要六分钟来处理1000个文件 我喜欢读写线程如下。当读线程读取文件时,我希望有写线程来处理它们。一旦读卡器开始读取文件,我想将其标记为正在处理,例如重命名它。读取后,将其重命名为completed 如何处理这样的多线程应用程序 使用分布式哈希表还是队列更好 我应该使用哪种数据结构来避免锁C# 使用.NET进行多线程文件处理,c#,.net,multithreading,architecture,C#,.net,Multithreading,Architecture,有一个包含1000个小文本文件的文件夹。我的目标是在文件夹中填充更多文件的同时解析和处理所有这些文件。我的意图是对这个操作进行多线程处理,因为单线程原型需要六分钟来处理1000个文件 我喜欢读写线程如下。当读线程读取文件时,我希望有写线程来处理它们。一旦读卡器开始读取文件,我想将其标记为正在处理,例如重命名它。读取后,将其重命名为completed 如何处理这样的多线程应用程序 使用分布式哈希表还是队列更好 我应该使用哪种数据结构来避免锁 有更好的方法实现此方案吗?您可以有一个中央队列,在将内存
有更好的方法实现此方案吗?您可以有一个中央队列,在将内存中的内容推送到队列的过程中,读卡器线程将需要写访问权限。处理线程需要对此中央队列进行读取访问,才能弹出下一个要处理的内存流。通过这种方式,您可以最大限度地减少花在锁上的时间,并且不必处理无锁代码的复杂性 编辑:理想情况下,您应该优雅地处理所有异常/错误条件(如果有),这样就不会出现故障点
作为替代方案,您可以有多个线程,每个线程通过在处理之前重命名文件来“声明”一个文件,因此文件系统成为锁定访问的实现。不知道这是否比我原来的答案更有效,只有测试才能证明。一般来说,1000个小文件(多小,顺便说一下?)不需要6分钟的处理时间。作为一个快速测试,在包含文件的目录中执行一个
查找“foobar”*
(引号中的第一个参数不重要;它可以是任何参数),然后查看处理每个文件所需的时间。如果超过一秒钟,我会失望的
假设这个测试证实了我的怀疑,那么这个进程是受CPU限制的,将读取分离到自己的线程中不会有任何改进。你应该:
你可以考虑一个文件队列来处理。通过在启动时扫描目录来填充队列一次,并使用更新来更新队列,以高效地向队列中添加新文件,而无需不断地重新扫描目录
如果可能,可以读取和写入不同的物理磁盘。这将为您提供最大的IO性能如果有许多文件的初始突发需要处理,然后添加新文件的不均匀速度,并且所有这些文件都发生在同一磁盘上(读/写),则可以考虑将处理过的文件缓冲到内存,直到两种条件之一应用:
- (暂时)没有新文件
- 您缓冲了太多文件,导致 您不想使用更多的内存来存储 缓冲(理想情况下是可配置的 阈值)
- ConcurrentQueue:
- 阻止收集:
另外,我不明白单点故障和分布式哈希表的使用有什么关系?我知道使用DHT听起来很酷,但我会先尝试传统的方法,除非你想解决一个特定的问题。由于评论中对.NET 4如何处理这个问题很好奇,下面就是这种方法。抱歉,这可能不是OP的选项。免责声明:这不是一个高度科学的分析,只是表明有明显的性能优势。根据硬件的不同,您的里程数可能差异很大 下面是一个快速测试(如果您在这个简单的测试中看到一个大错误,这只是一个示例。请评论,我们可以将其修复为更有用/更准确)。为此,我只是将12000~60KB的文件作为示例放入一个目录中(启动;您可以自己免费玩它!) 只需稍微更改循环即可并行化查询 大多数简单的情况。我所说的“简单”主要是指一个动作的结果不会影响下一个动作。最需要记住的是一些集合,例如我们的handy is,所以在并行场景中使用它不是一个好主意:)Lu
var files =
Directory.GetFiles("C:\\temp", "*.*", SearchOption.AllDirectories).ToList();
var sw = Stopwatch.StartNew(); //start timer
files.ForEach(f => File.ReadAllBytes(f).GetHashCode()); //do work - serial
sw.Stop(); //stop
sw.ElapsedMilliseconds.Dump("Run MS - Serial"); //display the duration
sw.Restart();
files.AsParallel().ForAll(f => File.ReadAllBytes(f).GetHashCode()); //parallel
sw.Stop();
sw.ElapsedMilliseconds.Dump("Run MS - Parallel");
class Program
{
static void Main(string[] args)
{
Supervisor super = new Supervisor();
super.LaunchWaitingThreads();
while (!super.Done) { Thread.Sleep(200); }
Console.WriteLine("\nDone");
Console.ReadKey();
}
}
public delegate void StartCallbackDelegate(int idArg, Worker workerArg);
public delegate void DoneCallbackDelegate(int idArg);
public class Supervisor
{
Queue<Thread> waitingThreads = new Queue<Thread>();
Dictionary<int, Worker> runningThreads = new Dictionary<int, Worker>();
int maxThreads = 20;
object locker = new object();
public bool Done {
get {
lock (locker) {
return ((waitingThreads.Count == 0) && (runningThreads.Count == 0));
}
}
}
public Supervisor()
{
// queue up a thread for each file
Directory.GetFiles("C:\\folder").ToList().ForEach(n => waitingThreads.Enqueue(CreateThread(n)));
}
Thread CreateThread(string fileNameArg)
{
Thread thread = new Thread(new Worker(fileNameArg, WorkerStart, WorkerDone).ProcessFile);
thread.IsBackground = true;
return thread;
}
// called when a worker starts
public void WorkerStart(int threadIdArg, Worker workerArg)
{
lock (locker)
{
// update with worker instance
runningThreads[threadIdArg] = workerArg;
}
}
// called when a worker finishes
public void WorkerDone(int threadIdArg)
{
lock (locker)
{
runningThreads.Remove(threadIdArg);
}
Console.WriteLine(string.Format(" Thread {0} done", threadIdArg.ToString()));
LaunchWaitingThreads();
}
// launches workers until max is reached
public void LaunchWaitingThreads()
{
lock (locker)
{
while ((runningThreads.Count < maxThreads) && (waitingThreads.Count > 0))
{
Thread thread = waitingThreads.Dequeue();
runningThreads.Add(thread.ManagedThreadId, null); // place holder so count is accurate
thread.Start();
}
}
}
}
public class Worker
{
string fileName;
StartCallbackDelegate startCallback;
DoneCallbackDelegate doneCallback;
public Worker(string fileNameArg, StartCallbackDelegate startCallbackArg, DoneCallbackDelegate doneCallbackArg)
{
fileName = fileNameArg;
startCallback = startCallbackArg;
doneCallback = doneCallbackArg;
}
public void ProcessFile()
{
startCallback(Thread.CurrentThread.ManagedThreadId, this);
Console.WriteLine(string.Format("Reading file {0} on thread {1}", fileName, Thread.CurrentThread.ManagedThreadId.ToString()));
File.ReadAllBytes(fileName);
doneCallback(Thread.CurrentThread.ManagedThreadId);
}
}