多线程技术
- 程序中的多任务
在使用文字处理软件时,用户在输入文字的同时,软件能同步进行拼写检查而不需要用户的等待 - 多线程工作方式
单处理器计算机上的并发性
抢先式多任务处理
时间片轮转 - 进程的概念
进程是应用程序的一次动态执行,包括程序代码在内存中的映像以及进程所需的系统资源
System.Diagnostics下的Process类可以访问和管理当前系统中的进程 - 线程的概念
线程包括堆栈、CPU寄存器状态以及系统任务计划列表中的项
进程中的所有线程共享进程的虚拟地址空间和系统资源,如程序代码的内存和堆 - 使用多线程的优缺点
优点
可以及时对用户操作作出响应
如果一个或多个线程所处理的工作不占用CPU时间时,就可以节省时间。
缺点
切换线程需要开销
必须额外考虑线程的并发、同步等线程安全问题,从而使得程序更加复杂而难以维护
线程的建立与启动
假定需要编写一个文件压缩软件,用户点击压缩按钮后开始压缩指定的文件。因为整个压缩过程需要一定的时间才能完成,而用户此时还可能需要移动或缩放程序的窗口,甚至暂停或终止当前文件的压缩。因此需要一个单独线程来处理压缩过程
public Thread ( ThreadStart start )
Thread compressThread=new Thread(entryPoint);
在C#应用程序中,Main()方法所在线程是.NET运行库开始执行的第一个线程,称为主线程
在一个应用程序中创建的用于执行一些工作任务的线程称为工作线程
Thread的构造方法需要一个参数,用于指定线程的入口—即线程开始执行的方法
public delegate void ParameterizedThreadStart ( Object obj )
public delegate void ThreadStart( );
static void DoCompress( )
{
//压缩代码
}
ThreadStart entryPoint=new ThreadStart( DoCompress );
线程创建后,并未获得系统资源。启动线程,即给线程分配除处理器之外的系统资源并执行各种安全性检查。
compressThread.Start( );
public void start()
在调用该方法后,新线程并不是处于Running状态,而是处于Unstarted状态
线程的挂起、恢复与终止
public void Suspend ()
compressThread.Suspend ( );
使线程处于阻塞状态
public void Resume ()
compressThread.Resume ( );
恢复被挂起的线程,使其进入就绪状态
Suspend方法不一定会立即起作用。.NET允许要挂起的
线程再执行几个指令,达到可以安全挂起的状态,确保
垃圾收集器执行正确的操作
public void Abort ()
compressThread.Abort ( );
中止线程,Windows永久删除该线程的所有数据,该线程不能重新启动
Abort方法会在受影响的线程中产生一个ThreadAbortException异常,在finally块中进行资源清理并确保线程正在处理的数据处于有效状态
在调用Abort方法前一定要判断线程是否被激活
if ( myThread.IsAlive )
{
myThread.Abort( );
}
public void Join ()
thread1.Join ( );
当前线程进入阻塞状态,等待调用该方法的线程执行完毕
public static void Sleep ( int millisecondsTimeout )
使当前线程睡眠指定的时间,休眠完后线程变为就绪状态
Thread类的常用属性
CurrentThread
获取当前正在运行的线程
Thread myOwnThread=Thread.CurrentThread;
IsAlive
指示当前线程的执行状态,如果此线程已启动并且尚未正常终止,则为 true;否则为 false。
IsBackground
指示该线程是否为后台线程。后台运行的线程在所有前台线程都结束后会被自动终止,以防止出现程序无法退出的情况
Name
线程的名称
Priority
指示线程的调度优先级
- ThreadPriority.Highest
将线程安排在具有任何其他优先级的线程之前 - AboveNormal
将线程安排在具有Highest优先级的线程之后,在具有Normal优先级的线程之前。 - Normal
将线程安排在具有AboveNormal优先级的线程之后,在具有BelowNormal优先级的线程之前。为线程的默认优先级。
同优先级的线程按照时间片轮流运行
- BelowNormal
将线程安排在具有Normal优先级的线程之后,在具有Lowest优先级的线程之前 - Lowest
将线程安排在具有任何其他优先级的线程之后
ThreadState
指示当前线程的状态
- ThreadState.Aborted
线程处于 Stopped 状态中。 - AbortRequested
已对线程调用了 Thread.Abort 方法,但线程尚未收到试图终止它的挂起的 System.Threading.ThreadAbortException - Background
线程正作为后台线程执行(相对于前台线程而言)。 - Running
线程正在运行 - Stopped
线程已停止 - StopRequested
正在请求线程停止 - Suspended
线程已挂起 - SuspendRequested
正在请求线程挂起 - Unstarted
尚未启动线程 - WaitSleepJoin
由于调用 Wait、Sleep 或 Join,线程处于阻塞状态
给线程传递数据
使用带ParameterizedThreadStart委托参数的Thread构造函数
创建一个定制类,把线程的方法定义为实例方法,这样就可以初始化实例的数据,然后启动线程
后台线程
前台与后台线程
- 只要有一个前台线程在运行,应用程序的进程就在运行
- 在默认情况下,用Thread类创建的线程是前台线程,线程池中的线程总是后台线程
- 通过设置线程的属性IsBackground可以确定该线程是前台线程还是后台线程
- 后台线程非常适合完成后台任务。例如,如果关闭word应用程序,拼写检查器继续运行其进程就没有意义了
线程池
ThreadPool线程池是可以用来在后台执行多个任务的线程集合,使主线程可以自由地异步执行其它任务
- 该类会在需要时增减池中线程的个数,直到最大的线程数。
- 池中的最大线程数是可配置的
- 如果有更多的工作要处理,而线程池中线程的使用也到了极限,最新的工作就要排队,必须等待线程完成其任务
- 线程池中的每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中
- 如果线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间之后创建另一个辅助线程
- 线程池适合于执行一些需要多个线程的任务。使用线程池可以优化任务的执行过程,提高吞吐量;如果应用程序需要对线程进行特定的控制,则不适合使用线程池,需要创建并管理自己的线程
- 每个进程都有且仅有一个线程池。当进程启动时,线程池不会自动创建。只有第一次将回调方法排入队列时才会创建线程池。
- 线程池通常用于服务器应用程序,每个传入请求都将分配给线程池中的一个线程,因此可以异步处理请求,而不会占用主线程,也不会延迟后续请求的处理
ThreadPool常用方法
void GetMaxThreads(out int workerThreads, out int completionPortThreads)
检索可以同时处于活动状态的线程池请求的数目,所有大于此数目的请求将保持排队状态,直到线程池线程变为可用
bool QueueUserWorkItem ( WaitCallback callback )
public delegate void WaitCallback(Object state)
将方法排入队列以便执行,此方法在有线程池线程变得可用时执行 。线程池收到请求后会从池中选择一个线程来调用该方法。如果线程池还没有运行,就会创建一个线程池,并启动第一个线程。如果线程池已经在运行,且有一个空闲线程来完成该任务就把该作业传递给这个线程
使用线程池的限制
- 线程池中所有线程都是后台线程。如果进程中的所有前台线程都结束了,所有的后台线程就会停止。不能把线程池中的线程改为前台线程。
- 不能给线程池中的线程设置优先级或名称
- 线程池中的线程只能用于时间较短的任务,如果线程要一直运行(如word的拼写检查器线程),就应使用Thread类创建一个线程
static int interval;
static void DisplayNumbers()
{
//获取当前运行线程的Thread对象实例
Thread thisThread = Thread.CurrentThread;
Console.WriteLine("线程:" + thisThread.Name + " 已开始运行。");
//循环计数直到结束,在指定间隔输出当前计数值
for (int i = 1; i <= 8 * interval; i++)
{
if (i % interval == 0)
{
Console.WriteLine(thisThread.Name + ":当前计数为" + i);
// Thread.Sleep(10);
}
}
Console.WriteLine("线程 " + thisThread.Name + " 完成。");
Console.ReadKey();
}
static void Main(string[] args)
{
//获取用户输入的数字
Console.Write("请输入一个数字:");
interval = int.Parse(Console.ReadLine());
//定义当前主线程对象的名字
Thread thisThread = Thread.CurrentThread;
thisThread.Name = "Main Thread";
//建立新线程对象
ThreadStart workerStart=new ThreadStart(DisplayNumbers);
Thread workerThread=new Thread(workerStart);
workerThread.Name="Worker Thread";
workerThread.IsBackground=true;
// workerThread.Priority = ThreadPriority.AboveNormal;
//启动新线程
workerThread.Start();
//主线程同步进行计数
DisplayNumbers();
}
和顺序执行不同,多线程程序的执行常常具有不确定性,这是因为线程执行的时间点和时间长短都受到CPU运算速度、系统资源等多种因素的影响。
在不同的条件下,同一个程序的执行结果可能存在较大差异
线程的使用场合
多线程的优缺点
- 多线程并发执行可以提高程序运行速度
- 提高了程序的复杂度
- 占用更多的CPU和内存资源
- CPU需要花费很多时间来进行线程调度
在单CPU计算机上,多线程的使用场合有
- 长时间等待
- 后台处理
- 优先任务