为什么我的多线程比单线程更慢?

31

我知道有几个人问了类似这样的问题,但我找不到任何回答可以让我理解为什么速度会变慢。

所以,我为了自己对Visual Studio 2013中线程对象的理解制作了一个小型控制台程序。我的CPU是一颗Intel Core i7,可以使用多线程。

我的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class Program
    {

        static TimeSpan MTTime;
        static TimeSpan STTime;

        static void Main(string[] args)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();


            Console.WriteLine(Environment.NewLine + "---------------Multi Process-------------" + Environment.NewLine);

            Thread th1 = new Thread(new ParameterizedThreadStart(Process));
            Thread th2 = new Thread(new ParameterizedThreadStart(Process));
            Thread th3 = new Thread(new ParameterizedThreadStart(Process));
            Thread th4 = new Thread(new ParameterizedThreadStart(Process));

            th1.Start("A");
            th2.Start("B");
            th3.Start("C");
            th4.Start("D");

            th1.Join();
            th2.Join();
            th3.Join();
            th4.Join();

            stopwatch.Stop();
            MTTime = stopwatch.Elapsed ;

            Console.WriteLine(Environment.NewLine + "---------------Single Process-------------" + Environment.NewLine);


            stopwatch.Reset();
            stopwatch.Start();

            Process("A");
            Process("B");
            Process("C");
            Process("D");

            stopwatch.Stop();
            STTime = stopwatch.Elapsed;

            Console.Write(Environment.NewLine + Environment.NewLine + "Multi  : "+ MTTime + Environment.NewLine + "Single : " + STTime);


            Console.ReadKey();
        }

        static void Process(object procName)
        {
            for (int i = 0; i < 100; i++)
            {
                Console.Write(procName);
            }
        }
    }
}

结果图像:

在此输入图片描述

我们可以清楚地看到,多线程过程是完全随机的,而单线程则只是一个接一个地执行所有操作,但我认为这对速度没有影响。

起初,我以为我的线程比程序运行所需的进程更大,但是在更换更大的进程后,单线程仍然远远是最快的。那么,我在多线程方面是否缺少了一些概念?或者这很正常,它比较慢吗?


32
向控制台写入内容是一种不好的测试方法。相反,尝试在Process循环中计算几千个加密哈希值(最后只需一次向控制台写入)你会发现速度更快。 - bstenzel
3
补充下面给出的非常好的答案:使用线程很少会成功 - 请使用TPL中更高级的API,如Tasks,或者更好的PLinq等(或者如果您真的知道自己在做什么,请继续 - 但无意冒犯,这种情况下您可能需要更深入地了解它)。 - Random Dev
运行Visual Studio Profiler,你就会看到应用程序的低效之处。(提示:控制台 - Anders
6个回答

71
请注意,Process会向控制台写入(基本上什么都不做),而且在这里,输出到控制台(它充当了一种共享资源)是缓慢的,并且需要与其他线程同步。
据我所知,您使用的并行化方式会产生巨大的开销,但不会获得速度提升,因为所有时间,线程似乎主要在等待其他进程完成向控制台的写入。

29
没错。对于楼主而言:如果您想要一个有效的单线程与多线程算法实现的比较,您必须确保这些实现在运行时不相互干扰。特别是,它们不能使用任何共享资源,因为这会导致各个线程互相等待。 - Peter Duniho

44

来自官方控制台文档的内容如下:

使用这些流进行的I/O操作是同步的,这意味着多个线程可以从流中读取或写入数据。这意味着通常是异步的方法(例如TextReader.ReadLineAsync)如果对象表示控制台流,则会同步执行。

这意味着控制台类处理线程同步,因此如果线程A和线程B尝试写入控制台,则控制台将处理它们并且只能一次写入一个线程。这背后的处理逻辑就是为什么它需要更长时间的原因。


更新
我建议您查看Parallel.ForEach


好的!如果我理解正确的话,这是因为控制台处理线程的方式导致的?所以,我想如果我在控制台中写入或读取其他东西,它就会起作用?对吗?让我试试这个,但我不确定什么是一个好的测试。非常感谢你促使我进步!:D - Kazimar
@Kazimar 当你需要处理大量数据时,建议使用多线程来执行某些算法或其他操作。我还建议你看一下Parallel.ForEach,我会更新我的答案。看看吧,如果你认为我的答案是你要找的,请标记为已回答! - Sid
另一种描述方式是,由于控制台写入需要线程安全,因此它是一个串行化资源,线程花费大量时间等待它们的轮到。如果这些是磁盘写入,您将从各个方面缓冲它们。 - mckenzm

4

好的!感谢Assa和Codor让我想到正确的方向!我最终做出了一个小控制台程序,可以非常清晰地显示所有内容。当使用重量级处理工具时,多线程最终会更快。只需阅读我的代码,你就可以轻松理解。

结果:

enter image description here

代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class Program
    {
        //Timer for speed guidance
        static Stopwatch stopwatch;
        //Data i use for generate time
        static List<int> timeData;
        static void Main(string[] args)
        {
            stopwatch = new Stopwatch();
            timeData = new List<int> { 1000, 800, 200, 700, 600, 300, 800, 100, 200, 300, 655, 856, 695, 425 };

            ////-------------------------- SINGLE THREAD ------------------------------/////
            Console.WriteLine("-------------------------------------------------");
            Console.WriteLine("             Single Threading Process            ");
            Console.WriteLine("-------------------------------------------------");
            Console.WriteLine("   Process Time        Thread ID                 ");
            Console.WriteLine("-------------------------------------------------");
            stopwatch.Reset();
            stopwatch.Start();
            //For each normal that use only 1 thread
            foreach(int i in timeData)
            {
                Process(i);
            }

            stopwatch.Stop();
            //Total time that the program take for making the process happen
            Console.WriteLine("*Total : " + stopwatch.Elapsed );

            ////-------------------------- Mulit Multiple ------------------------------/////

            Console.WriteLine("-------------------------------------------------");
            Console.WriteLine("-------------------------------------------------");
            Console.WriteLine("             Multi Threading Process            ");
            Console.WriteLine("-------------------------------------------------");
            Console.WriteLine("   Process Time        Thread ID                 ");
            Console.WriteLine("-------------------------------------------------");
            stopwatch.Reset();
            stopwatch.Start();
            //for each thats use Multiple thread fr the process (can be made with parallel.invoke or Task Library or Thread Library)
            Parallel.ForEach(timeData, (i) => Process(i));
            //Total time that the program take for making the process happen
            Console.WriteLine("*Total : " + stopwatch.Elapsed);
            Console.WriteLine("-------------------------------------------------");
            Console.ReadKey();
        }

        // Methode for sumulating long processing
        static void Process( int time)
        {
            stopwatch.Reset();
            stopwatch.Start();
            //sleep time simulate the IO portion of the process
            Thread.Sleep(time);
            // The loop simulate de algoritme type of precessing
            for (int i = 0; i < time*1000000; i++){}
            stopwatch.Stop();
            Console.WriteLine( stopwatch.Elapsed + "         " + Thread.CurrentThread.ManagedThreadId.ToString());          
        }


    }
}

这是一个更好的多线程“演示”,但请注意,您的所有线程都使用相同的计时器对象。如果您希望每个线程测量其自身的执行时间,则应在Process(int)中声明一个新的StopWatch变量。 - Mathieu Pagé
你的秒表只显示最后一次启动和第一次停止之间的差异。这就是为什么你有很多相同的数字。使用线程,每个单独的进程不会更快。而是多个进程的组合会更快。 - the_lotus
嗯,我不确定你的意思是什么?能否请您详细说明一下 :) 你是说我的启动/停止表在循环中的位置不对吗?因为如果您看到组合,我的多线程仍然更快。还是你只是在说线程的事实,而我的编码没问题? - Kazimar
请不要发布澄清原问题的答案,请将编辑内容发布到原问题中。回答应该只是问题的答案。 - Eric Lippert
好的,明白了!抱歉这是我的第一篇帖子 :) - Kazimar
1
在这种特定情况下,请忽略Eric的建议。编辑您的问题以应用其他答案中提出的建议会使未来的访问者无法理解现有的答案。但是,我不建议发布以下形式的答案:“我尝试了另一个答案,它起作用了”,除非现有的答案未能完全回答您的问题(尽管通常在这种情况下,评论就足够了)。结束您的StackOverflow体验的理想方式是接受最有帮助的答案(单击该答案旁边的复选框)。 - Brian

4

0

如果处理器是瓶颈,应用程序将通过多线程更快地运行。有时您的磁盘或 RAM 是瓶颈。最常见的情况是数据库或外部服务。在这些情况下添加多个线程通常不会使代码运行更快,相反,由于需要额外的同步,它经常会减慢执行速度。

如果想使代码运行更快,必须首先确定是什么导致了运行缓慢。对代码进行分析,找到瓶颈所在

记住:过早优化是万恶之源

在您的情况下,瓶颈是控制台输出流,即单个实例相对缓慢的工作,它是最常访问的资源,会阻塞所有操作。即使只有一个核心,也无法以最快的速度打印在上面并利用100%的性能。


是的,我终于通过我的所有测试注意到了这一点!如果我运行一些小的进程,多线程实际上会变得更慢,只是因为它生成用于创建多线程对象和其他东西的进程。在大多数情况下,这是无用的...除非你正在做真正需要大量多线程的事情,比如网络等...或者大型算法处理。 - Kazimar
嗯,这并不完全是对问题的回答,但我正在寻找我的应用程序运行缓慢的原因,这让我思考了一下,所以点赞。 - śmiglidigli

0

如果您删除下面的行,您的多线程将更快。

Console.Write(procName);

这是因为操作系统将控制台视为共享资源,每次只能有一个线程使用共享资源。因此,在这种情况下,多线程的速度提升就像单个线程一样,但是多线程会增加额外的线程管理开销,而没有性能上的收益。


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