在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内部实现的爬坡算法究竟是什么样的机制?–待研究