线程池和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确实语法非常简单,功能非常强大,因为自 ...
随机推荐
- HTML URL 编码:请参阅:http://www.w3school.com.cn/tags/html_ref_urlencode.html
http://www.w3school.com.cn/tags/html_ref_urlencode.html
- HDU 4417 Super Mario(2012杭州网络赛 H 离线线段树)
突然想到的节约时间的方法,感觉6翻了 给你n个数字,接着m个询问.每次问你一段区间内不大于某个数字(不一定是给你的数字)的个数 直接线段树没法做,因为每次给你的数字不一样,父节点无法统计.但是离线一 ...
- Codeforces 453B Little Pony and Harmony Chest:状压dp【记录转移路径】
题目链接:http://codeforces.com/problemset/problem/453/B 题意: 给你一个长度为n的数列a,让你构造一个长度为n的数列b. 在保证b中任意两数gcd都为1 ...
- iis常见问题解决
iis7以上版本部署4.0框架项目常见问题解决 配置错误: 不能在此路径中使用此配置节.如果在父级别上锁定了该节,便会出现这种情况.锁定是默认设置的 (overrideModeDefault=&quo ...
- css 网站素装 追忆过去
素装代码,以表哀悼等.以下为全站CSS代码. html { filter: grayscale(100%); -webkit-filter: grayscale(100%); -moz-filter: ...
- 一款实现滑动切换效果的插件---swiper
Swiper是纯javascript打造的滑动特效插件,面向手机.平板电脑等移动终端. Swiper能实现触屏焦点图.触屏Tab切换.触屏多图切换等常用效果. Swiper开源.免费.稳定.使用简单. ...
- 一次websocket的抓包体验
一个简单的demo 我们知道websocket一种服务端推送技术,首先Websocket是基于HTTP协议的,或者说借用了HTTP的协议来完成一部分握手.后续数据传递是基于TCP的. 客户端代码 &l ...
- android 应用程序Activity之间数据传递与共享的几种途径
一.基于消息的通信机制 Intent ---boudle ,extraAndroid为了屏蔽进程的概念,利用不同的组件[Activity.Service]来表示进程之间的通信!组件间通信的核心机制是I ...
- 【leetcode】Construct Binary Tree from Inorder and Postorder Traversal
Given inorder and postorder traversal of a tree, construct the binary tree. Note:You may assume that ...
- windows cmd 看服务cpu、内存
开始菜单-运行-cmd-输入systeminfo-回车 不用命令从以下两个地方都可以看出CPU个数 使用命令看CPU 利用win+r键打开运行,输入cmd回车即会出现 查看cpu信息 通过上图可以看出 ...