Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/335.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/25.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用.NET进行多线程文件处理_C#_.net_Multithreading_Architecture - Fatal编程技术网

C# 使用.NET进行多线程文件处理

C# 使用.NET进行多线程文件处理,c#,.net,multithreading,architecture,C#,.net,Multithreading,Architecture,有一个包含1000个小文本文件的文件夹。我的目标是在文件夹中填充更多文件的同时解析和处理所有这些文件。我的意图是对这个操作进行多线程处理,因为单线程原型需要六分钟来处理1000个文件 我喜欢读写线程如下。当读线程读取文件时,我希望有写线程来处理它们。一旦读卡器开始读取文件,我想将其标记为正在处理,例如重命名它。读取后,将其重命名为completed 如何处理这样的多线程应用程序 使用分布式哈希表还是队列更好 我应该使用哪种数据结构来避免锁 有更好的方法实现此方案吗?您可以有一个中央队列,在将内存

有一个包含1000个小文本文件的文件夹。我的目标是在文件夹中填充更多文件的同时解析和处理所有这些文件。我的意图是对这个操作进行多线程处理,因为单线程原型需要六分钟来处理1000个文件

我喜欢读写线程如下。当读线程读取文件时,我希望有写线程来处理它们。一旦读卡器开始读取文件,我想将其标记为正在处理,例如重命名它。读取后,将其重命名为completed

如何处理这样的多线程应用程序

使用分布式哈希表还是队列更好

我应该使用哪种数据结构来避免锁


有更好的方法实现此方案吗?

您可以有一个中央队列,在将内存中的内容推送到队列的过程中,读卡器线程将需要写访问权限。处理线程需要对此中央队列进行读取访问,才能弹出下一个要处理的内存流。通过这种方式,您可以最大限度地减少花在锁上的时间,并且不必处理无锁代码的复杂性

编辑:理想情况下,您应该优雅地处理所有异常/错误条件(如果有),这样就不会出现故障点


作为替代方案,您可以有多个线程,每个线程通过在处理之前重命名文件来“声明”一个文件,因此文件系统成为锁定访问的实现。不知道这是否比我原来的答案更有效,只有测试才能证明。

一般来说,1000个小文件(多小,顺便说一下?)不需要6分钟的处理时间。作为一个快速测试,在包含文件的目录中执行一个
查找“foobar”*
(引号中的第一个参数不重要;它可以是任何参数),然后查看处理每个文件所需的时间。如果超过一秒钟,我会失望的

假设这个测试证实了我的怀疑,那么这个进程是受CPU限制的,将读取分离到自己的线程中不会有任何改进。你应该:

  • 找出为什么处理一个小的输入平均需要350毫秒以上,并希望改进算法
  • 如果没有办法加快算法的速度,而您有一台多核机器(现在几乎每个人都有),那么使用线程池为每个任务分配1000个任务,每个任务读取一个文件

  • 你可以考虑一个文件队列来处理。通过在启动时扫描目录来填充队列一次,并使用更新来更新队列,以高效地向队列中添加新文件,而无需不断地重新扫描目录

    如果可能,可以读取和写入不同的物理磁盘。这将为您提供最大的IO性能

    如果有许多文件的初始突发需要处理,然后添加新文件的不均匀速度,并且所有这些文件都发生在同一磁盘上(读/写),则可以考虑将处理过的文件缓冲到内存,直到两种条件之一应用:

    • (暂时)没有新文件
    • 您缓冲了太多文件,导致 您不想使用更多的内存来存储 缓冲(理想情况下是可配置的 阈值)
    <> P>如果文件的实际处理是CPU密集型的,可以考虑每个CPU核有一个处理线程。然而,对于“正常”处理,CPU时间与IO时间相比微不足道,其复杂性不值得任何微小的提高。

    Design 对于这种情况,生产者/消费者模式可能是最有用的。您应该创建足够的线程来最大化吞吐量

    以下是一些关于生产者/消费者模式的问题,让您了解其工作原理:

    您应该使用阻塞队列,生产者应该向队列中添加文件,而使用者则处理队列中的文件。阻塞队列不需要锁定,因此它是解决问题的最有效方法

    如果您使用的是.NET 4.0,那么有几种可以直接使用:

    • ConcurrentQueue:
    • 阻止收集:
    穿线 单个生产者线程可能是从磁盘加载文件并将其推送到队列中的最有效方式;随后,多个消费者将从队列中弹出项目,并对其进行处理。我建议您尝试每个核心2-4个消费线程,并进行一些性能测量,以确定哪一个是最佳的(即为您提供最大吞吐量的线程数)。对于这个特定的示例,我不建议使用线程池


    另外,我不明白单点故障和分布式哈希表的使用有什么关系?我知道使用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);
        }
    }