C# 不同方式启动多线程执行同一个耗时方法多次,耗时情况比较(不考虑内存占用)

在C#中我们经常使用多线程,据我自己所了解到的启动多线程有以下几种方式:
1,Thread,
2,ThreadPool,
3,Task,
4,Parallel.For,
5,Parallel.Foreach
后面两种,可能是多线程,可能不是,看注释就可以看的到:
Parallel.For的注释:
在这里插入图片描述
Parallel.Foreach的注释:在这里插入图片描述
暂且也当作是多线程的一种。
据我直观理解:
1.Thread直接开启一个线程来执行,基本上是启动就执行了,所以耗时最短,但是占用内存也最大
2.ThreadPool充分发挥线程的重复利用,不会占用过多内存,但是线程池的线程资源有限,所以可能有的需要排队等待其他任务释放空闲的线程
3.Task应该和ThreadPool差不多
4.Parallel.For和Parallel.Foreach是适当的时候可能会并行,所以耗时也会比Thread长,至于什么情况下会并行,待以后翻看源码再补充

现在开始写测试,来测试这几种方式启动线程是否和我的直观理解一致:
1.定义测试的抽象方法:

 public abstract class BaseTest
	{
	      private int sleep = 1000;
	      /// <summary>
	      /// 测试次数
	      /// </summary>
	      protected int TestCount = 100;
	      /// <summary>
	      /// 抽象测试方法,待每个子类不同实现
	      /// </summary>
	      public abstract void Test();
	      /// <summary>
	      /// 测试方法
	      /// </summary>
	      protected void TestMethod()
	      {
	          Thread.Sleep(sleep);
	      }
	     
	  }

2.实现Thread的测试:

class ThreadTest : BaseTest
    {
        public override void Test()
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            Thread[] threads = new Thread[TestCount];
            for (int i = 0; i < TestCount; i++)
            {
                threads[i] = new Thread(TestMethod);
                threads[i].Start();
            }
            for (int i = 0; i < 10; i++)
            {
                
                if (threads[i].ThreadState != System.Threading.ThreadState.Stopped)
                {
                    threads[i].Join();
                }
            }
            Console.WriteLine($"ThreadTest,{TestCount}个循环耗时:{stopwatch.Elapsed.TotalSeconds}s");

        }
    }

3.实现ThreadPool的测试:

class ThreadPoolTest : BaseTest
    {
        public override void Test()
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            Thread[] threads = new Thread[TestCount];
            for (int i = 0; i < TestCount; i++)
            {
                ThreadPool.QueueUserWorkItem(x => 
                {
                    int index = (int)x;
                    threads[index] = Thread.CurrentThread;
                },i);
            }
            for (int i = 0; i < TestCount; i++)
            {
                while (threads[i] == null)
                {
                    Thread.Sleep(1);
                }
                if (threads[i].ThreadState != System.Threading.ThreadState.Stopped)
                {
                    threads[i].Join();
                }
            }
            Console.WriteLine($"ThreadPoolTest,{TestCount}个循环耗时:{stopwatch.Elapsed.TotalSeconds}s");
        }
    
    }

需要注意的是,为啥加了如下这一句

while (threads[i] == null)
{
     Thread.Sleep(1);
}

因为在调用ThreadPool.QueueUserWorkItem这个方法时,线程并不一定开始执行,可能等1s后开始执行,也可能等10s后开始执行,当没有开始执行时,threads[i]就是null
4.实现Task的测试:

 class TaskTest : BaseTest
    {
        public override void Test()
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            Task[] tasks = new Task[TestCount];
            for (int i = 0; i < TestCount; i++)
            {
                tasks[i] = Task.Factory.StartNew(TestMethod);
            }
            Task.WaitAll(tasks);
            Console.WriteLine($"TaskTest,{TestCount}个循环耗时:{stopwatch.Elapsed.TotalSeconds}s");
        }
    }

5.实现Parallel.For测试:

class Parallel_ForTest : BaseTest
    {

        public override void Test()
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            Parallel.For(0, TestCount, i => TestMethod());
            Console.WriteLine($"Parallel_ForTest,{TestCount}个循环耗时:{stopwatch.Elapsed.TotalSeconds}s");
        }
    }

6.实现Parallel.Foreach测试:

class Parallel_ForeachTest : BaseTest
    {
        public override void Test()
        {
            List<int> list = new List<int>();
            for (int i = 0; i < TestCount; i++)
            {
                list.Add(i);
            }
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            Parallel.ForEach(list,i=>TestMethod());
            Console.WriteLine($"Parallel_ForeachTest,{TestCount}个循环耗时:{stopwatch.Elapsed.TotalSeconds}s");
        }
    }

写了如上5中实现方式,现在来调用,每个测试调用三次,看时间会不会有太大变化:

 class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 3; i++)
            {
                Console.WriteLine($"第{i+1}次测试...");
                BaseTest test = new ThreadTest();
                test.Test();

                test = new ThreadPoolTest();
                test.Test();

                test = new Parallel_ForeachTest();
                test.Test();

                test = new Parallel_ForTest();
                test.Test();

                test = new TaskTest();
                test.Test();

                Console.WriteLine($"第{i+1}次完成");
            }
            Console.WriteLine("测试完成!");
            Console.Read();
        }
    }

测试结果如下:
在这里插入图片描述
将sleep设置为5s,TestCount设置为10次,测试的结果如下:
在这里插入图片描述
哈哈,符合预期。
疑问:
1.ThreadTest相当于把一个耗时1s的方法并行执行100次,为什么总耗时接近2s而不是1s?
答:可能是100个线程实例化需要时间,Start需要时间,CPU切换需要时间
2.Task不是调用线程池吗?为什么Task比ThreadPool耗时少这么多?–待研究
3.Parallel.For/Parallel.Foreach内部实现的爬坡算法究竟是什么样的机制?–待研究

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值