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內部實現的爬坡算法究竟是什麼樣的機制?–待研究

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章