在这个例子中,为什么线程会增加时间(降低性能)?

6 c# performance multithreading

这段代码:

  object obj = new object { };
  Stopwatch watch = new Stopwatch();
  watch.Start();
  for (int i = 0; i < 90000; i++)
  {
      new Thread(() =>
      {
          lock (obj)
          {
              string file = new JavaScriptSerializer().Serialize(saeed);
              File.AppendAllText(string.Format(@"c:\Temp\{0}.txt", i), file);
          }
      }).Start();
  }
  watch.Stop();
Run Code Online (Sandbox Code Playgroud)

在15分钟内运行,而这段代码:

  Stopwatch watch = new Stopwatch();
  watch.Start();
  for (int i = 0; i < 90000; i++)
  {
      {
          string file = new JavaScriptSerializer().Serialize(saeed);
          File.AppendAllText(string.Format(@"c:\Temp\{0}.txt", i), file);
      }
  }
  watch.Stop();
Run Code Online (Sandbox Code Playgroud)

跑了45秒.为什么第一个应用程序在线程化时速度要慢得多?使用线程是一种提高应用程序性能的技术吗?

更新:即使使用闭包概念并引用中间变量而不是i在我的线程中而不是使用锁定,这使得线程真正异步,仍然创建这些文件需要超过5分钟.

  Stopwatch watch = new Stopwatch();
  watch.Start();
  for (int i = 0; i < 90000; i++)
  {
      var x = i;
      new Thread(() =>
      {
          string file = new JavaScriptSerializer().Serialize(saeed);
          File.AppendAllText(string.Format(@"c:\Temp\{0}.txt", i), file);
      }).Start();
  }
  watch.Stop();
Run Code Online (Sandbox Code Playgroud)

ken*_*n2k 34

1)您当前正在创建90000个线程,这根本没有效率.不要每次创建一个线程,而是使用线程池,因此您可以重用已创建的线程.记住创建一个线程需要一些时间和内存.

2)您正在锁定整个代码块lock,这意味着每个线程都被阻塞,直到另一个线程完成其工作.所以你基本上在这里击败了多线程的整个目的.

3)出于复杂的硬件相关原因(缓冲区......等),磁盘I/O无法与多线程配合使用.通常,多线程这部分代码并不是一个好主意.


关于磁盘I/O和多线程的评论:实际上这很复杂.

对于磁盘,磁盘臂必须移动,以便在好扇区/柱面/磁道上读/写字节.如果你同时写两个不同的文件(两个线程的情况,每个写一个不同的文件),根据磁盘上的物理文件位置,你可能会要求你的磁盘臂很快从一个物理位置切换到另一个物理位置,摧毁表演.在一个物理位置为第一个文件写入多个磁盘扇区,然后将磁盘臂移动到另一个位置,然后为第二个文件写入一些磁盘扇区将更加高效.比较复制两个文件的时间与复制一个文件,然后复制另一个文件时,可以看到此效果.

因此,对于这个非常基本的示例,性能增益/损失取决于:

  • 硬件本身.没有带SSD的磁盘臂,因此文件访问速度更快
  • 物理文件位置
  • 文件碎片
  • bufferization.该磁盘缓冲系统可以帮助阅读连续的块,这可能不是你要的手臂移动到另一个位置的情况下,有任何帮助.

我谦虚的建议:如果演出是你的主要目标,尽量避免在多个线程中进行多次读/写.


Han*_*ant 19

线程可以通过为您提供更多的执行引擎来加速您的代码.但是您在第一个片段中探索了截然不同的资源限制.

第一个是机器提交90 千兆字节内存的能力.线程堆栈所需的空间.这需要一段时间,你的硬盘可能会疯狂地为这么多内存创建备份存储..NET有点不寻常,因为它为一个线程提交了堆栈空间,它提供了一个执行保证.你可以关闭btw的东西,<disableCommitThreadStack>app.exe.config文件中的元素应该有一个非常明显的效果.

您正在探索的第二个资源限制是文件系统同时修改多个文件的能力.它会受到第一个限制的极大阻碍,你正在从文件系统缓存中窃取大量RAM.当它用完空间时,你会看到这些线程都试图占用磁盘写头的效果.强制它在文件集群之间来回压缩.磁盘搜索速度非常慢,是磁盘上最慢的操作.这是一种机械操作,驱动头臂需要物理移动,这需要花费很多毫秒.您的代码很可能生成的硬页面错误也会使情况变得更糟.

线程代码中的锁定将减少这种颠簸,但不会消除它.由于内存需求量很大,您的程序可能会产生大量的页面错误.更糟糕的情况是每个线程上下文切换.当磁盘执行seek + read以满足页面调入请求时,将阻止该线程.

好吧,通过让你这样做而不是摔倒来赞美Windows.但显然这是一个坏主意.最多使用几个线程.或者只有一个如果写入将使文件系统缓存饱和,那么你将避免寻求惩罚.

  • 我总是设法从你的帖子Hans中学到一些东西.谢谢. (6认同)

NPS*_*000 7

我会注意到大多数答案都没有读过代码的例子.这不是关于产生一堆线程并写入磁盘,这是关于产生一堆线程,做一些工作新的JavaScriptSerializer().Serialize(saeed); 写入磁盘!

需要注意的是,这一点非常重要,因为工作时间越长,通过确保磁盘在计算发生时不会空闲,提供的优势就越多.


它的长短是因为你写了一些简单的代码,正如其他人解释的那样:

  1. 您正在创建90,000个线程 - 这是昂贵且不必要的!
  2. 你正在锁定所有的工作,使这个单线程!
    1. 是的,如果没有锁定,你会得到一个例外......这并不能从一个表现理念中神奇地使锁定成为一个好主意 - 它只是意味着你有错误的代码.

进入线程的一种快速简便的方法 - 虽然你仍然可以填充它,但是使用任务并行库是一种稍微不那么危险的方法.例如:

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

namespace ConsoleApplication15
{
    class Program
    {
        const int FILE_COUNT = 9000;
        const int DATA_LENGTH = 100;
        static void Main(string[] args)
        {
            if (Directory.Exists(@"c:\Temp\")) Directory.Delete(@"c:\Temp\", true);
            Directory.CreateDirectory(@"c:\Temp\");

            var watch = Stopwatch.StartNew();
            for (int i = 0; i < FILE_COUNT; i++)
            {
                string data = new string(i.ToString()[0], DATA_LENGTH);
                File.AppendAllText(string.Format(@"c:\Temp\{0}.txt", i), data);
            }
            watch.Stop();
            Console.WriteLine("Wrote 90,000 files single-threaded in {0}ms", watch.ElapsedMilliseconds);

            Directory.Delete(@"c:\Temp\", true);
            Directory.CreateDirectory(@"c:\Temp\");

            watch = Stopwatch.StartNew();
            Parallel.For(0, FILE_COUNT, i =>
            {
                string data = new string(i.ToString()[0], DATA_LENGTH);
                File.AppendAllText(string.Format(@"c:\Temp\{0}.txt", i), data);
            });
            watch.Stop();
            Console.WriteLine("Wrote 90,000 files multi-threaded in {0}ms", watch.ElapsedMilliseconds);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

单线程版本在大约8.1秒内运行,多线程版本在大约3.8秒内运行.请注意,我的测试值与您的不同.

虽然TPL的默认设置并不总是针对您正在处理的场景进行优化,但它们提供了比运行90,000个线程更好的基础!你还会注意到,在这种情况下,我不需要做任何锁定,也不必处理闭包 - 因为提供的API已经为我处理了.