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