【问题标题】:Why is threading increasing execution time c# [duplicate]为什么线程会增加执行时间c# [重复]
【发布时间】:2016-02-23 09:40:49
【问题描述】:

使用两个线程打印增加时间的数字,然后使用单循环打印我知道同步会增加时间,但在我的代码中如何增加时间并停止线程打印重复数字?有吗?

class Program
{
    public static int count=0;
    public static List<string> numbers = new List<string>();
    public static int semaphore=0;

    static void Main(string[] args)
    {
        for (int i = 0; i < 10; i++)
        {
            numbers.Add(i.ToString());
        }

       Console.WriteLine("Before start thread");

       Thread tid1 = new Thread(new ThreadStart(MyThread.Thread1));
       Thread tid2 = new Thread(new ThreadStart(MyThread.Thread1));

       tid1.Start();
       tid2.Start();
    }
}
  public class MyThread
{
    public static object locker = new object();

    public static void Thread1()
    {
        for (; Program.count < Program.numbers.Count;)
        {
            lock (locker)
            {
                Console.WriteLine(Program.numbers[Program.count]);
                Program.count++;
            }
        }
    }
}

// 这比线程快,为什么?

foreach (var item in numbers)
{
    Console.WriteLine(item);
}

线程的平均时间是 1.5 毫秒,循环是 0.6 毫秒

【问题讨论】:

  • 我知道由于等待资源锁定,时间正在增加。但就我而言,我必须使用同步。有没有增加执行时间?我的代码中没有同步?
  • 嗯,由于上下文切换,线程数多于处理器内核会增加执行时间,但不会增加那么多。如果你想避免同步导致的延迟,而不仅仅是不使用需要同步的线程代码 - 设计你的应用程序,让所有可并行化的东西都可以在不同步的情况下执行。
  • 在我的问题中你能想出一种并行的方法吗?
  • 线程不一定会提高性能。您必须计算的实际资源是并行处理单元。如果您的并行处理单元都很忙,那么增加线程将降低性能。第二个因素是上下文切换。您拥有的线程越多,您进行上下文切换的频率就越高。如果与上下文切换时间相比,您的线程计算时间相对较低,那么多线程的性能会更差。另一个当然是共享资源。如果你的共享资源经常被两个线程访问
  • 那么他们相互等待的可能性就越大。

标签: c# multithreading


【解决方案1】:

您有 2 个线程正在相互等待,因为您锁定了线程之间的同步:

        lock (locker)
        {
            Console.WriteLine(Program.numbers[Program.count]);
            Program.count++;
        }

线程切换等待导致执行时间变长。

【讨论】:

  • 我知道@Peter 但如何避免同步?
  • 你应该在你的 MyThread 函数中只使用局部变量而不是你现在使用的静态变量。
【解决方案2】:

多线程并不能保证性能的提高。

首先,在多处理中真正重要的实际资源是并行处理单元的数量以及它们是否 - 不是 线程数。如果您的并行处理单元都很忙,或者如果

the number of thread > number of parallel processing unit

然后创建更多线程会降低性能,不会提高性能。

其次上下文切换因素。您拥有的线程越多,您进行上下文切换的频率可能就越高。因此,如果您的Thread 计算时间与上下文切换时间相比相对较低,那么您的多线程性能更差

并且第三,它还受shared-resources(或同步)因素的影响:您的共享资源是否经常被多个线程 - 它们需要彼此等待,从而导致较慢执行。

你的情况中,这似乎是第三种情况,正如@Peter 所提到的。这是因为你有全局的count 变量(不是本地的),它们是共享的,每个线程都必须访问才能完成你的任务。也就是说,您的任务本质上是顺序。这使得多线程的执行时间比单线程的执行时间更糟。

对于这种情况,如果您的任务本质上不是顺序的(即可以在收集结果之前拆分并独立完成),您可以期望使用多线程获得更好的结果,然后您可以尝试寻找@ 987654324@ 执行您的任务。

例如:每个线程都有本地 count,并在进程结束时对它们求和。

【讨论】:

    【解决方案3】:

    正如其他答案所暗示的,上下文切换和锁定需要时间来降低性能。因此,如果您希望获得更快的处理速度,您需要移除锁和共享资源。
    除此之外,您的测试不适合探索多线程,因为除了显式锁定之外,Console.WriteLine 内部还有隐式锁定(控制台也是共享资源)。

    要提高性能,您需要移除锁。

    因此,例如,如果您运行两个线程,其中第一个线程仅处理数字数组的一半(例如奇数),而第二个线程正在处理后半数(例如偶数)而不是 console.WriteLine你做了一些不使用共享资源的事情,那么你会看到性能的提升。

    考虑以下示例(我更改了您的代码):

    class Program
    {
        public static int count = 0;
        public static List<string> numbers = new List<string>();
        public static System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
    
        static void Main(string[] args)
        {
            for (int i = 0; i < 10; i++)
            {
                numbers.Add(i.ToString());
            }
    
            // First test - process nummbers in current thread
            sw.Start();
            foreach (var item in numbers)
            {
                DoSomethingWithTheNumber(item);
            }
            sw.Stop();
            Console.WriteLine("foreach in main thread took, ticks: "+sw.ElapsedTicks);
    
            // Second test - process nummbers in 2 threads with lock
            Thread tid1 = new Thread(new ThreadStart(MyThread.Thread1));
            Thread tid2 = new Thread(new ThreadStart(MyThread.Thread1));
            sw.Reset();
            sw.Start();
            tid1.Start();
            tid2.Start();
            tid1.Join();
            tid2.Join();
            sw.Stop();
            Console.WriteLine("for in 2 threads with lock took, ticks: " + sw.ElapsedTicks);
    
            // Third test - process nummbers in 2 threads without lock
            // first thread processes odd numbers, second processes odd numbers
            Thread tid1A = new Thread(new ThreadStart(MyThreadWithoutLock.ThreadOddNumbers));
            Thread tid2A = new Thread(new ThreadStart(MyThreadWithoutLock.ThreadEvenNumbers));
            sw.Reset();
            sw.Start();
            tid1A.Start();
            tid2A.Start();
            tid1A.Join();
            tid2A.Join();
            sw.Stop();
            Console.WriteLine("for in 2 threads without lock took, ticks: " + sw.ElapsedTicks);
    
            Console.ReadKey();
        }
    
        public static void DoSomethingWithTheNumber(string number)
        {
            //Console.WriteLine(number);
            Thread.Sleep(100);
        }
    
        public class MyThread
        {
            public static object locker = new object();
    
            public static void Thread1()
            {
                for (; Program.count < Program.numbers.Count; )
                {
                    lock (locker)
                    {
                        if(Program.count < Program.numbers.Count)
                            DoSomethingWithTheNumber(Program.numbers[Program.count]);
                        Program.count++;
                    }
                }
            }
        }
    
        public class MyThreadWithoutLock
        {
            public static void ThreadOddNumbers()
            {
                for (int i=1; i < Program.numbers.Count; i=i+2)
                {
                    DoSomethingWithTheNumber(Program.numbers[i]);
                }
            }
            public static void ThreadEvenNumbers()
            {
                for (int i = 0; i < Program.numbers.Count; i = i + 2)
                {
                    DoSomethingWithTheNumber(Program.numbers[i]);
                }
            }
        }
    }
    

    输出是:
    主线程中的 foreach 已占用,刻度:2337320
    在 2 个线程中使用了锁,滴答声:2351632
    对于在 2 个线程中没有锁定的情况,记号:1176403

    您可以看到,最后一个带有两个线程且不加锁的选项确实使您的处理速度提高了 2 倍。

    【讨论】:

      【解决方案4】:

      以下代码虽然仍然是线程安全的,但速度更快,因为它是无锁的。我用Interlocked.Increment替换了lock关键字

      using System;
      using System.Collections.Generic;
      using System.Threading;
      
      namespace ConsoleApplication1
      {
          class Program
          {
              public static int count = 0;
              public static List<string> numbers = new List<string>();
              public static int semaphore = 0;
      
              static void Main(string[] args)
              {
                  for (int i = 0; i < 10; i++)
                  {
                      numbers.Add(i.ToString());
                  }
                  Console.WriteLine("Before start thread");
                  Thread tid1 = new Thread(new ThreadStart(MyThread.Thread1));
                  Thread tid2 = new Thread(new ThreadStart(MyThread.Thread1));
      
                  tid1.Start();
                  tid2.Start();
      
                  tid1.Join();
                  tid2.Join();
              }
          }
          public class MyThread
          {
      
              public static void Thread1()
              {
                  int nextIndex;
                  while ((nextIndex = Interlocked.Increment(ref Program.count)) <= Program.numbers.Count)
                  { 
                     Console.WriteLine(Program.numbers[nextIndex - 1]);
                  }
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-06-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多