线程池和Thread
1、线程池
创建线程需要时间。如果有不同的短任务要完成,就可以事先创建许多线程,在应完成这些任务时发出请求。这个线程数最好在需要更多线程时增加,在需要释放资源时减少。不需要自己创建这样一个列表。该列表有ThreadPool类托管。这个类会在需要时增减池中线程的线程数,直到最大的线程数。池中的最大线程数是可配置的。在四核CPU中,默认设置为1023个工作线程和1000个I/O线程。也可以指定在创建线程池时应立即启动的最小线程数,以及线程池中可用的最大线程数。如果有更多的作业要处理,线程池中线程的个数也到了极限,最新的作业就要排队,且必须等待线程完成其任务。
下面的示例应用程序首先要读取工作线程和I/O线程的最大线程数,把这些信息写入控制台中,接着在for循环中,调用ThreadPool.QueueUserWorkItem()方法,传递一个WaitCallback类型的委托,来调用该方法。如果线程池还没有运行,就会创建一个线程池,并启动第一个线程。如果线程池已经在运行,且有一个空闲线程来完成该任务,就把该任务传递给这个线程。
int nWorkerThreads;
int nCompletionPortThreads;
ThreadPool.GetMaxThreads(out nWorkerThreads,out nCompletionPortThreads);
Console.WriteLine("Max worker threads:{0} ,I/O completion threads:{1}",nWorkerThreads,nCompletionPortThreads);
for (int i = 0; i < 5; i++)
{
ThreadPool.QueueUserWorkItem(JobForAThread);
}
static void JobForAThread(object state)
{
for (int i = 0; i < 3; i++)
{
Console.WriteLine("loop {0}, running inside pooled thread {1}",i,Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(50);
}
}
运行应用程序时,可以看到1023个工作线程的当前设置。5个任务只由4个线程池中的线程处理(这是一个四核系统)。
线程池的一些限制:
a、线程池中的线程都是后台线程。如果进程的所有前台线程都结束了,所有的后台线程就会停止。不能把入池的线程改为前台线程。
b、不能给入池的线程设置优先级或名称。
c、对于COM对象,入池的所有线程都是多线程单元(MTA)线程许多COM对象都需要单线程单元(STA)线程。
d、入池的线程只能用于时间较短的任务。如果线程要一直运行就应使用Thread类创建一个线程(或者在创建Task时使用LongRunning选项)。
2、Thread类
使用Thread类可以创建和控制线程。下面的结合Lambda表达式示例,结果不能保证哪一个先输出。
var t1 = new Thread(() => Console.WriteLine("running in a thread,id:{0}",Thread.CurrentThread.ManagedThreadId));
t1.Start();
Console.WriteLine("This is the main ,id:{0}",Thread.CurrentThread.ManagedThreadId);
2.1、给线程传递参数
给线程传递一些数据可以采用两种方式。一种方式是使用带ParameterizedThreadStart委托参数的Thread构造函数,另一种是创建一个自定义类,把线程的方法定义为实例方法,这样就可以初始化实例的数据,之后启动线程。
第一种:
var d = new Data { message="this is a thread"};
var t1 = new Thread(ThreadMainWithParameters);
t1.Start();
static void ThreadMainWithParameters(object o)
{
Data d = (Data)o;
Console.WriteLine("Running in a thread,receiver {0}");
}
public class Data
{
public string message;
}
第二种:
public class MyThread
{
private string data;
public MyThread(string data)
{
this.data = data;
}
public void ThreadMain()
{
Console.WriteLine("Running in a thread,data:{0}",data);
}
}
var obj = new MyThread("info");
var t3 = new Thread(obj.ThreadMain);
t3.Start();
2.2、后台线程
只要有一个前台线程在运行,应用程序的进程就在运行。如果多个前台线程在运行,而Main()方法结束了,应用程序的进程就仍然是激活的,知道所有前台线程完成其任务为止。在默认情况下,用Tread类创建的线程是前台线程。线程池中的线程总是后台线程。在用Thread类创建线程时,可以设置IsBackground属性,以确定该线程是前台线程还是后台线程。
2.3、线程的优先级
前面提到,线程由操作系统调度。给线程指定优先级,就可以影响调度顺序。在改变优先级之前,必须理解线程调度器。操作系统根据优先级来调度线程。调度优先级最高的线程以在CPU上运行。线程如果在等待资源,它就会停止运行,并释放CPU。
线程必须等待时有几个原因,例如,响应睡眠指令、等待磁盘I/O的完成,等待网络包的到达等。如果线程不是主动释放CPU,线程调度器就会抢占该线程。线程有一个时间量,这意味着它可以持续使用CPU,知道这个时间到达(这是指没有更高优先级的线程时)。如果优先级相同的多个线程等待使用CPU,线程调度器就会使用一个循环调度规则,将CPU逐个交给线程使用。如果线程被其他线程抢占,它就会排在队列的最后。
只有优先级相同的多个线程在运行,才用的上时间量和循环规则。优先级是动态的。如果线程是CPU密集型的(一直需要CPU,且不等待资源),其优先级就降低为该线程定义的基本优先级。如果线程在等待资源,它的优先级会提高。由于优先级的提高,线程很有可能在下次等待结束时获得CPU。
在Thread类中,可以设置Priority属性,以影响线程的基本优先级。Priority属性需要ThreadPriority枚举定义的一个值。定义的级别有Highest、AboveNormal、Normal、BelowNormal和Lowest。(在给线程指定较高级的优先级的时候要小心,因为这可能降低其他线程的运行概率,根据需要、可以短暂地改变优先级)
2.4、控制线程
调用Thread对象的Start()方法,可以创建线程。但是,在调用Start()方法后,新线程仍不是出于Running状态,而是处于Unstarted状态。只要操作系统的线程调度器选择了要运行的线程,线程就会改变Running状态,读取Thread.ThreadState属性,就可以获得线程的当前状态。
使用Thread.Sleep()方法,会使线程处于WaitSleepJoin状态,在等待Sleep()方法定义的时间段后,线程就会再次被唤起。
要停止另一个线程,可以调用Thread.Abort()方法调用这个方法时,会在接到终止命令的线程中抛出一个ThreadAbortException类型的异常。用一个处理程序捕获这个异常,线程可以在结束前就完成一些清理工作。如果调用了Thread.ResetAbort,线程还有机会接收到ThreadAbortException异常后继续运行,如果线程,没有重置终止,接收到终止请求的线程的状态就从AbortRequest改为Aborted。
如果需要等待线程的结束,就可以调用Thread.join()方法。Thread.join()方法会停止当前线程,并把它设置为WaitSleepJoin状态,直到加入的线程完成为止。
3、线程问题
使用多个线程编程并不容易。在启动访问相同数据的多个线程时,会间歇性地遇到难以发现的问题。如果使用任务,并行LINQ或Parallel类,也会遇到这些问题。为了避免这些问题,必须特别注意同步问题和多个线程可能发生的其他问题。
3.1、争用条件
如果两个或多个线程访问相同的对象,并且对共享状态的访问没有同步,就会出现争用条件。为了说明争用条件下面定义一个StateObject类,它包含一个int字段和一个ChangeState()方法。在ChangeState()方法的实现代码中,验证状态是否包含5,如果包含,就递增其值。下一条语句是Trace.Assert,它立刻验证State现在是否包含6.。
public class StateObject
{
private int state = 5;
public void ChangeState(int loop)
{
if(state==5)
{
state++;
Trace.Assert(state==6,"Race condition occurred after"+loop+"loops");
}
state = 5;
}
}
下面通过给任务顶一个方法来验证这一点
public class SampleTask
{
public void RaceCondition(object o)
{
Trace.Assert(o is StateObject,"o must be of type StateObject");
StateObject state = o as StateObject;
int i = 0;
while(true)
{
state.ChangeState(i++);
}
}
}
在Main()方法中新建StateObject对象,它由所有任务共享。通过使用传递给Task的Run方法的lambda表达式调用RaceCondition方法来创建Task对象。然后,主线程等待用户输入。但是,可能出现争用,所以程序很有可能在读取用户输入前就挂了。
var state = new StateObject();
for (int i = 0; i < 2; i++)
{
Task.Run(()=>new SampleTask().RaceCondition(state));
}
要避免该问题,可以锁定共享的对象。这可以在线程中完成,用下面的Lock语句锁定在线程中共享的state变量。只有一个线程能在锁定的块中处理共享的state对象。由于这个对象在所有的线程之间共享,因此如果一个线程锁定了state。另一个线程就必须等待该锁定的解除。一旦接受锁定,线程就拥有该锁定,直到该锁定块的末尾才能解除锁定,如果改变state变量引用的对象的每个线程都使用一个锁定,就不会出现争用条件。
public class SampleTask
{
public void RaceCondition(object o)
{
Trace.Assert(o is StateObject,"o must be of type StateObject");
StateObject state = o as StateObject;
int i = 0;
while(true)
{
lock(state)//no race condition with this lock
{
state.ChangeState(i++);
}
}
}
}
在使用共享对象时,除了进行锁定之外,还可以将共享对象设置为线程安全的对象。在下面的代码中,ChangeState方法中包含一条lock语句,由于不能锁定state本身(只有引用类型才能用于锁定)
public class StateObject
{
private int state = 5;
private object _lock = new object();
public void ChangeState(int loop)
{
lock(_lock)
{
if (state == 5)
{
state++;
Trace.Assert(state == 6, "Race condition occurred after" + loop + "loops");
}
state = 5;
}
}
}
3.2、死锁
过多的锁定也会有麻烦。在死锁中,至少有两个线程被挂起,并等待对方解除锁定。由于两个线程都在等待对方,就出现了死锁,线程将无限等待下去。为了说明死锁,下面实例化StateObject类型的两个对象,并把它们传递给SampleTask类的构造函数,创建两个任务,其中一个任务运行Deadlock1()方法,另一个任务运行Deadlock2()方法。
var state1 = new StateObject();
var state2 = new StateObject();
new Task(new Sample(state1,state2).DeadLock1).Start();
new Task(new Sample(state1, state2).DeadLock2).Start();
public class Sample
{
public Sample(StateObject s1, StateObject s2)
{
this.s1 = s1;
this.s2 = s2;
}
private StateObject s1;
private StateObject s2;
public void DeadLock1()
{
int i = 0;
while(true)
{
lock(s1)
{
lock(s2)
{
s1.ChangeState(i);
s2.ChangeState(i++);
Console.WriteLine("still running , {0}",i);
}
}
}
}
public void DeadLock2()
{
int i = 0;
while (true)
{
lock (s2)
{
lock (s1)
{
s1.ChangeState(i);
s2.ChangeState(i++);
Console.WriteLine("still running , {0}", i);
}
}
}
}
}
在本例中只需要改变锁定顺序,这两个线程就会以相同的顺序进行锁定。但是,锁定可能隐藏在方法的深处。为了避免这个问题,可以在应用程序的体系架构中,从一开始就设计好锁定顺序,也可以为锁定定义超时时间。
线程池和Thread的更多相关文章
- Thread(线程)和ThreadPool(线程池) Thread回调与返回值
Thread(线程) Thread开启线程:接收一个参数 TestClass tc = new TestClass(); //没有返回值,有一个object类型的参数的委托:两种写法. Paramet ...
- C#之线程和线程池(Thread和ThreadPool类)
注:要使用此方法都需要引入应用:using System.Threading;//引入应用 参数意义:将要执行的方法排入队列以便执行,WaitCallback,即表示将要执行的方法:Object,包含 ...
- 解释一下什么是线程池(thread pool)?
在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源.在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收.所以提高服务程序效率的 ...
- 什么是线程池(thread pool)?
在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内 存资源或者其它更多资源.在 Java 中更是如此,虚拟机将试图跟踪每一个对象, 以便能够在对象销毁后进行垃圾回收.所以提高服务程 ...
- Java多线程之Thread、Runnable、Callable及线程池
一.多线程 线程是指进程中的一个执行流程,一个进程中可以有多个线程.如java.exe进程中可以运行很多线程.进程是运行中的程序,是内存等资源的集合,线程是属于某个进程的,进程中的多个线程共享进程中的 ...
- java多线程-线程池
线程池(Thread Pool)对于限制应用程序中同一时刻运行的线程数很有用.因为每启动一个新线程都会有相应的性能开销,每个线程都需要给栈分配一些内存等等. 我们可以把并发执行的任务传递给一个线程池, ...
- Java 线程池
系统启动一个线程的成本是比较高的,因为它涉及到与操作系统的交互,使用线程池的好处是提高性能,当系统中包含大量并发的线程时,会导致系统性能剧烈下降,甚至导致JVM崩溃,而线程池的最大线程数参数可以控制系 ...
- IIS线程池与ASP.NET线程池
原文地址:http://www.cnblogs.com/dudu/p/3762672.html 1. IIS线程池 W3 Thread Pool(W3TP) 当处于内核模式的http.sys接收到来自 ...
- Python 线程池的原理和实现及subprocess模块
最近由于项目需要一个与linux shell交互的多线程程序,需要用python实现,之前从没接触过python,这次匆匆忙忙的使用python,发现python确实语法非常简单,功能非常强大,因为自 ...
随机推荐
- Symbol Table(符号表)
一.定义 符号表是一种存储键值对的数据结构并且支持两种操作:将新的键值对插入符号表中(insert):根据给定的键值查找对应的值(search). 二.API 1.无序符号表 几个设计决策: A.泛型 ...
- 仿联想商城laravel实战---2、后端页面搭建(验证码如何在页面中使用)
仿联想商城laravel实战---2.后端页面搭建(验证码如何在页面中使用) 一.总结 一句话总结: 放在img里面,img的src就是生产验证码的控制器路径: img src="/admi ...
- git忽略文件和目录
******************************************************** http://jingxuan.io/progit/2-Git%E5%9F%BA%E7 ...
- HYSBZ - 1588 splay
题意:每天给你一个数,要求统计最小波动值,强制在线的就是每次从已经出现过的数值中找与当前值间隔最小的加起来 题解:splay维护,同时求解当前值的前驱和后继,找距离小的那个就好了 splay是一种二叉 ...
- hadoop_学习_00_资源帖
一.精品 1.虚无境的博客 随笔分类 - hadoop 二.参考资料 1.大数据学习之路(持续更新中...) 2.Hadoop安装教程_单机/伪分布式配置_CentOS6.4/Hadoop2.6.0 ...
- JavaUtil_09_通用工具类-01_Hutool
一.重要的官方资料 1. Hutool 官网 2. Hutool 参考文档 3. Hutool API文档
- Linux-NoSQL之MongoDB
1.mongodb介绍 什么是MongoDB ? MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统. 在高负载的情况下,添加更多的节点,可以保证服务器性能. MongoD ...
- 关于c++中的全局变量(不赋值的全局变量算定义)
定义有三种: 1.不赋值的定义:int a; 2.赋值的定义:int a=5; 或者 int a;a=5; 3.加extern的定义:extern int a=5;//其实和不加是一样的. 声明只有一 ...
- hdu-5640 King's Cake (水题)
题目链接 King's Cake Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others ...
- codeforces 632C C. The Smallest String Concatenation(sort)
C. The Smallest String Concatenation time limit per test 3 seconds memory limit per test 256 megabyt ...