线程池是按顺序运行的吗?

3

我正在编写一个函数,用于获取目录中的所有文件,但是通过将每个子目录添加到线程池中实现并行操作。我认为这意味着每个目录都将被并行遍历,由于有许多子目录,因此与顺序执行相比,速度会更快。我的代码如下:

    private object addlock = new object();
    private void addFiles(string[] newFiles)
    {
        lock (addlock) {
            files.AddRange( newFiles );
            Console.WriteLine( "Added {0} files", newFiles.Length );
        }
    }

    private void getFilesParallel(string dir)
    {
        if (!Directory.Exists( dir )) {
            return;
        }

        string[] dirs = Directory.GetDirectories( dir, "*", SearchOption.TopDirectoryOnly );
        ManualResetEvent mre = new ManualResetEvent( false );

        ThreadPool.QueueUserWorkItem( (object obj) =>
        {
            addFiles( Directory.GetFiles( dir, "*", SearchOption.TopDirectoryOnly ) );
            mre.Set();
        } );

        Process currentProcess = Process.GetCurrentProcess();
        long memorySize = currentProcess.PrivateMemorySize64;

        Console.WriteLine( "Used {0}", memorySize );

        foreach (string str in dirs) {
            getFilesParallel( str );
        }

        mre.WaitOne();
    }

问题在于我得到了这样的输出:
Added 34510 files
Used 301420544
Added 41051 files
Used 313937920
Added 39093 files
Used 322764800
Added 44426 files
Used 342536192
Added 30772 files
Used 350728192
Added 36262 files
Used 360329216
Added 31686 files
Used 368685056
Added 33194 files
Used 374894592
Added 34486 files
Used 384057344
Added 37298 files
Used 393998336

这表明我的代码是按顺序运行的,正如我所期望的那样,每个语句都会在不同的线程上运行并集中出现。我已经使用不同的文件夹运行了多次,并且结果始终如一。为什么它是按顺序运行的?


尝试使用一些虚拟磁盘驱动器软件的多个虚拟磁盘。我想6Gb/s是极限。 - huseyin tugrul buyukisik
2个回答

8
你只有一个物理磁盘驱动器。磁头一次只能在一个位置。你同时要求它获取两个信息,并不能使其同时处于两个位置。
你的程序中有一些占用CPU资源较少的工作实际上可以并行处理,但这不是主要瓶颈。
如果你有多个物理磁盘驱动器,每个驱动器上都有数据,那么你可以并行访问每个驱动器上的数据,从而实现所需工作的并行处理。

0

由于只要有足够的内存,第一次运行会缓存数据,随后对同一文件夹的枚举可能根本不需要访问磁盘,因此准确地进行基准测试有些困难。

另外需要考虑的是,如果你有一块固态硬盘,它将从并行操作中获得更多好处,因为它支持更多的IOPS,而不必等待任何移动部件。

这段代码表明,当针对SSD运行或数据已经被缓存时,使用并行处理比单线程处理快2-3倍,演示了如何使用Parallel.ForEach来方便任务并行处理。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;

namespace FilesReader
{
    class Program
    {
        static void Main(string[] args)
        {
            string path = args[0];
            RunTrial(path, false);
            RunTrial(path, true);
        }

        private static void RunTrial(string path, bool useParallel)
        {
            Console.WriteLine("Parallel: {0}", useParallel);

            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            FileListing listing = new FileListing(path, useParallel);
            stopwatch.Stop();

            Console.WriteLine("Added {0} files in {1} ms ({2} file/second)", 
                listing.Files.Count, stopwatch.ElapsedMilliseconds, 
                (listing.Files.Count * 1000 / stopwatch.ElapsedMilliseconds));
        }
    }

    class FileListing
    {
        private ConcurrentList<string> _files;
        private bool _parallelExecution;

        public FileListing(string path, bool parallelExecution)
        {
            _parallelExecution = parallelExecution;
            _files = new ConcurrentList<string>();
            BuildListing(path);
        }

        public ConcurrentList<string> Files
        {
            get { return _files; }
        }

        private void BuildListing(string path)
        {
            string[] dirs = null;
            string[] files = null;
            bool success = false;

            try
            {
                dirs = Directory.GetDirectories(path, "*", SearchOption.TopDirectoryOnly);
                files = Directory.GetFiles(path);
                success = true;
            }
            catch (SystemException) { /* Suppress security exceptions etc*/ }

            if (success)
            {
                Files.AddRange(files);

                if (_parallelExecution)
                {
                    Parallel.ForEach(dirs, d => BuildListing(d));
                }
                else
                {
                    foreach (string dir in dirs)
                    {
                        BuildListing(dir);
                    }
                }
            }
        }
    }

    class ConcurrentList<T>
    {
        object lockObject = new object();
        List<T> list;

        public ConcurrentList()
        {
            list = new List<T>();
        }

        public void Add(T item)
        {
            lock (lockObject) list.Add(item);

        }
        public void AddRange(IEnumerable<T> collection)
        {
            lock (lockObject) list.AddRange(collection);
        }

        public long Count
        {
            get { lock (lockObject) return list.Count; }
        }
    }
}

我曾考虑使用并发集合来代替编写线程安全的列表实现,但它们被证明要慢大约5%。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接