;
thread.Start();
经过这两个扩展技术点,我们是不是就更牢固了我们的知识点。
线程池ThreadPool
1.线程池概述
线程池是C# 2.0以后才有的。确切的说是Net2.0以后,什么是线程池呢??先看图,然后在慢慢的聊。
.NET Framework的ThreadPool类提供一个线程池,该线程池可用于执行任务、发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器。那么什么是线程池?线程池其实就是一个存放线程对象的“池子(pool)”,他提供了一些基本方法,如:设置pool中最小/最大线程数量、把要执行的方法排入队列等等。ThreadPool是一个静态类,因此可以直接使用,不用创建对象。
微软官网说法如下:许多应用程序创建大量处于睡眠状态,等待事件发生的线程。还有许多线程可能会进入休眠状态,这些线程只是为了定期唤醒以轮询更改或更新的状态信息。 线程池,使您可以通过由系统管理的工作线程池来更有效地使用线程。
说得简单一点,每新建一个线程都需要占用内存空间和其他资源,而新建了那么多线程,有很多在休眠,或者在等待资源释放;又有许多线程只是周期性的做一些小工作,如刷新数据等等,太浪费了,划不来,实际编程中大量线程突发,然后在短时间内结束的情况很少见。于是,就提出了线程池的概念。线程池中的线程执行完指定的方法后并不会自动消除,而是以挂起状态返回线程池,如果应用程序再次向线程池发出请求,那么处以挂起状态的线程就会被激活并执行任务,而不会创建新线程,这就节约了很多开销。只有当线程数达到最大线程数量,系统才会自动销毁线程。因此,使用线程池可以避免大量的创建和销毁的开支,具有更好的性能和稳定性,其次,开发人员把线程交给系统管理,可以集中精力处理其他任务。
2.线程池初始化
线程池位于 ThreadPool中,该类也是在 System.Threading 命名空间下,线程默认初始化大小是有CPU和操作系统决定的。 其中线程数最小不能小于CPU的核数。在我们使用ThreadPool类的时候。
int workerThreads = 0;
int ioThreads = 0;
ThreadPool.GetMaxThreads(out workerThreads, out ioThreads);
Console.WriteLine(String.Format("可创建最大线程数: {0}; 最大 I/O 线程: {1}", workerThreads, ioThreads));
ThreadPool.GetMinThreads(out workerThreads, out ioThreads);
Console.WriteLine(String.Format("最小线程数: {0}; 最小 I/O 线程: {1}", workerThreads, ioThreads));
ThreadPool.GetAvailableThreads(out workerThreads, out ioThreads);
Console.WriteLine(String.Format("可以使用的工作线程: {0}; 可用 I/O 线程: {1}", workerThreads, ioThreads));
执行上面的代码可以得带结果如下:
可创建最大线程数: 2047; 最大 I/O 线程: 1000
最小线程数: 4; 最小 I/O 线程: 4
可以使用的工作线程: 2047; 可用 I/O 线程: 1000
3.设置线程池初始化大小
我们为什么要设置线程池的大小呢?其实我们计算机并不是只运行你一个软件,你的给其他软件预留线程池,一般我们开发的时候使用的线程数是使用 8条、16条、32条和64条。不建议大于64条,毕竟老式的计算机支持有限,如果你计算机线程有2000多,你完全可以分配256以下的线程数。下面我们来看下如何设置。
#region 设置初始化线程
ThreadPool.SetMaxThreads(8, 8);//最小也是CPU核数
ThreadPool.SetMinThreads(4, 4);
#endregion
#region 获取线程池当前设置 ,默认设置取决于操作系统和CPU
int workerThreads = 0;
int ioThreads = 0;
ThreadPool.GetMaxThreads(out workerThreads, out ioThreads);
Console.WriteLine(String.Format("可创建最大线程数: {0}; 最大 I/O 线程: {1}", workerThreads, ioThreads));
ThreadPool.GetMinThreads(out workerThreads, out ioThreads);
Console.WriteLine(String.Format("最小线程数: {0}; 最小 I/O 线程: {1}", workerThreads, ioThreads));
ThreadPool.GetAvailableThreads(out workerThreads, out ioThreads);
Console.WriteLine(String.Format("可以使用的工作线程: {0}; 可用 I/O 线程: {1}", workerThreads, ioThreads));
#endregion
执行结果如下:
可创建最大线程数: 8; 最大 I/O 线程: 8
最小线程数: 4; 最小 I/O 线程: 4
可以使用的工作线程: 8; 可用 I/O 线程: 8
4.线程池的使用
线程池可以理解为,我们预先创建好指定数量的线程,给程序使用,当前正在使用的子线程一旦使用完成以后,要立即归还,下一个任务使用的时候,我在分配给这个任务。我们使用ManualResetEvent类来控制线程的使用与归还。具体看代码。
#region 多线程的使用
Console.WriteLine("当前主线程id:{0}", Thread.CurrentThread.ManagedThreadId);
//首先创建5个任务线程
ManualResetEvent[] mre = new ManualResetEvent[]
new ManualResetEvent(false),
new ManualResetEvent(false),
new ManualResetEvent(false),
new ManualResetEvent(false),
new ManualResetEvent(false)
for (int i = 0; i < 5; i++)
///false 默认是关闭的,TRUE 默认为打开的
Thread.Sleep(300);
ThreadPool.QueueUserWorkItem(t =>
//lambda任务
Console.WriteLine("参数的内容是"+t);
Console.WriteLine("获取参数值((dynamic)t).num:" + ((dynamic)t).num);
int num = ((dynamic)t).num;
for (int j = 0; j < num; j++)
Thread.Sleep(2);//一件很耗时的事情
Console.WriteLine("当前子线程id:{0} 的状态:{1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.ThreadState);
//这里是释放共享锁,让其他线程进入
mre[i].Set();//可以理解为打开一个线程
//这里是打开共享锁,让其他线程进入
// mre[i].Reset();//可以理解为关闭一个线程
Console.WriteLine();
}, new { num = (i + 1) * 10 });
mre[i].WaitOne(3000); //单独阻塞线程,因为我们使用的是池,索引这里不使用这个,用法同委托异步
//注意这里,设定WaitAll是为了阻塞调用线程(主线程),让其余线程先执行完毕,
//其中每个任务完成后调用其set()方法(收到信号),当所有
//的任务都收到信号后,执行完毕,将控制权再次交回调用线程(这里的主线程)
// ManualResetEvent.WaitAll(mre);不建议这么使用
Console.ReadKey();
#endregion
执行结果:
当前主线程id:1
参数的内容是{ num = 10 }
获取参数值((dynamic)t).num:10
当前子线程id:3 的状态:Background
参数的内容是{ num = 20 }
获取参数值((dynamic)t).num:20
当前子线程id:3 的状态:Background
参数的内容是{ num = 30 }
获取参数值((dynamic)t).num:30
当前子线程id:4 的状态:Background
参数的内容是{ num = 40 }
获取参数值((dynamic)t).num:40
当前子线程id:3 的状态:Background
参数的内容是{ num = 50 }
获取参数值((dynamic)t).num:50
当前子线程id:3 的状态:Background
通过执行结果可以看出,第一次任务执行完成以后,把线程归还了,第二次任务分配的还是当前线程,但是第三次任务执行不过来了,我们从小分配了 一次线程。请根据结果分析。关于回调,线程等待使用方法同异步,救赎前一篇有介绍。