C#线程与线程池
进程和线程是分不开的,进程在C#学习笔记——进程篇有讲述
1. 简单介绍:
线程:
- 程序实现:
将一个进程划分为若干个独立的执行流,每一个执行流均称为一个线程。
- 硬件实现:
线程是CPU调度和分配的基本单位
- 一个进程中既可以只包含一个线程,也可以同时包含多个线程。
- 线程共享进程的资源
2. 线程管理(Thread类)
1、主线程和辅助线程:
- 无论何种类型的应用程序,当将这些程序作为进程来运行时,系统都会为该进程创建一个默认的线程,该线程称为主线程。
- 主线程用于执行Main方法中的代码,当Main方法返回时,主线程也自动终止。
- 在一个进程中,除了主线程之外的其他线程都称为辅助线程。
- 一个线程要么是前台线程要么是后台线程。
区别:后台线程不会影响进程的终止,而前台线程则会影响进程的终止。
- 利用Thread对象的IsBackground属性,可以设置或判断一个线程是后台线程还是前台线程。
- 将某个线程的IsBackground属性设置为true,使其变为后台线程。
- 默认情况下,属于托管线程池的线程都是后台线程(即其IsThreadPoolThread属性为true),通过创建并启动新的Thread对象而生成的线程都是前台线程。
3. 进程演示:
创建和启动线程:
- 通过Thread创建一个单独的线程,常用形式为:
Thread t=new Thread(方法名);//无参数Thread t2 = new Thread(方法名(Object obj));//有参数
创建带参数线程(必须传递一个Object类型的参数)。
- 线程通过委托来实现的,委托由定义的方法是否带参数而定。
不带参数方法使用ThreadStart委托
带参数方法使用ParameterizedThreadStart委托
- Thread创建的线程默认为前台线程。
- 线程启动调用该实例的Start方法
- 启动有参数线程的方法:
要求:必须传递一个Object类型的参数
a.单个参数
b.多个参数:将参数封装到一个类或结构中,然后传递该类或结构的实例。
t.Start();//调用不带参数的方法t.Start(\"abc\");//调用带参数的方法
调用Start方法启动线程,并将参数传过去。
举例
6. 无参数方法
//定义无参数的方法static void MethodA(){for (int i = 0; i < 10; i++){ Console.WriteLine(\"i的当前值是:\" + i); }}//创建线程并启动线程Thread t1 = new Thread(new ThreadStart(MethodA));t1.Start();Thread t2 = new Thread(MethodA);t2.Start();
- 无参数方法
//1.定义参数类型为Object的方法static void MethodC(object obj){Console.WriteLine(obj.ToString());}//2.创建线程Thread t3 = new Thread(new ParameterizedThreadStart(MethodC));Thread t4 = new Thread(MethodC);//3.传递参数并启动线程t3.Start(\"Thread T3\");t4.Start(\"Thread T4\");
线程终止和取消:
终止或取消线程的执行
第1种方法是先设置一个修饰符为volatile的布尔型的字段表示是否需要正常结束该线程,称为终止线程。
第2种方法是在其他线程中调用Thread实例的Abort方法终止当前线程,该方法的最终效果是强行终止该线程的执行,属于非正常终止的情况,称为取消线程的执行。
Volatile关键字
- volatile修饰符表示所声明的字段可以被多个并发执行的线程修改。如果某个字段声明包含volatile关键字,则该字段将不再被编译器优化。这样可以确保该字段在任何时间呈现的都是最新的值。
- 对于由多个线程访问的字段,而且该字段没有用lock语句对访问进行序列化,声明字段时应该使用volatile修饰符。
- volatile修饰符只能包含在类或结构的字段声明中,不能将局部变量声明为volatile。
- 在布尔型字段的声明中,添加volatile修饰符的方法如下:
public volatile bool shouldStop;
- volatile修饰符常用于以下类型:
(1)引用类型。
(2)整型,如sbyte、byte、short、ushort、int、uint、char、float和bool。
(3)具有整数基类型的枚举类型。
(4)已知为引用类型的泛型类型参数。
休眠线程:
- 调用Thread类提供的静态Sleep方法,可使当前线程暂停一段时间。
举例
Thread.Sleep(1000);//这条语句的功能是让当前线程暂停1000毫秒
注意: Sleep方法是暂停的是该语句所在的线程,而不是其他线程;并且无法从一个线程中暂停其他的线程。
获取或设置线程的优先级:
- 每个线程都具有分配给它的优先级。
- 当线程之间争夺CPU时间片时,CPU是按照线程的优先级进行调度的。
- 创建线程时,默认优先级为Normal。
- 使用下面的方法可为线程赋予较高的优先级:
Thread t1 = new Thread(MethodName);t1.priority = ThreadPriority.AboveNormal;
注意:使用最高优先级时要特别小心。
4. 线程池(ThreadPool类)
- 线程池的基本特征
(1)托管线程池中的线程都是后台线程。
(2)添加到线程池中的任务不一定会立即执行。
(3)线程池可自动重用已创建过的线程。一旦池中的某个线程完成任务,它将返回到等待线程队列中,等待被再次使用,而不是直接销毁它。
(4)开发人员可设置线程池的最大线程数。
(5)从.NET框架4.0开始,线程池的默认大小由多个因素决定;线程池中的线程都是利用多核处理技术来实现的。 - 向线程池中添加工作项
(1)在传统的编程模型中,开发人员一般是直接用ThreadPool.QueueUserWorkItem方法向线程池中添加工作项。
ThreadPool.QueueUserWorkItem(new WaitCallback(Method1));ThreadPool.QueueUserWorkItem(new WaitCallback(Method2));
ThreadPool只提供了一些静态方法,不能通过创建该类的实例来使用线程池。
5. 线程池多线程编程中的资源同步
- 同步执行和异步执行
(1)同步执行: 执行某语句时,在该语句完成之前不会执行其后面的代码,这种执行方式称为同步执行。
(2)异步执行:执行某语句时,不管该语句是否完成,都会继续执行其后面的语句,这种执行方式1.称为异步执行。
- 多线程执行过程中的资源同步问题
(1)在某个线程中启动另一个或多个线程后,这些线程会同时执行,称为并行(准确说是并发)。
(2)并行执行的多个线程同时访问某些资源时,必须考虑如何让多个线程保持同步。
- 死锁和争用情况
(1)死锁的典型例子是两个线程都停止响应,并且都在等待对方完成,从而导致任何一个线程都不能继续执行。
(2)争用就是程序结果取决于两个或多个线程中的哪一个先到达某一特定代码块是出现的一种错误。
(3)为了解决死锁问题,C#和.NET框架都提供了多种协调线程同步的方案。
- 实现资源同步的常用方式
(1)用volatile修饰符锁定公共或私有字段。
利用该修饰符可直接访问内存中的字段,而不是将字段缓存在某个处理器的寄存器中。这样做的好处是所有处理器都可以访问该字段最新的值。
(2)用Interlocked类提供的静态方法锁定局部变量。
System.Threading.Interlocked类通过加锁和解锁提供了原子级别的静态操作方法,对并行执行过程中的某个局部变量进行操作时,可采用这种办法实现同步。
(3)用lock语句锁定代码块
直接用C#提供的lock语句将包含局部变量的代码块锁定,退出被锁定的代码块后会自动解锁。
- C#提供了一个lock语句,该语句能确保当一个线程完成执行代码块之前,不会被其他线程中断。被锁定的代码块称为临界区。
实现原理: 进入临界区之前先锁定某个私有对象(声明为private的对象),然后再执行临界区中的代码,当代码块中的语句执行完毕后,再自动解除该锁。
Private List<int> list = new List<int>() ;…Lock(list){ }
如果锁定的代码段中包含多个需要同步的字段或者多个局部变量,可先定义一个私有字段lockedObj,通过一次性锁定该私有字段实现多个变量的同步操作。
- 使用lock语句的注意实现:
(1)临界区中的代码一般不宜太多。
在锁定和解锁期间处理的代码过多,会导致某个线程执行临界区中的代码时,其他等待运行临界区中代码的线程都会处于阻塞状态,从而降低系统的性能;
(2)lock语句锁定的类型可以是任意类型的实例,但不允许锁定类型本身。
(3)lock语句不能锁定public的对象,必须是私有对象。
锁定public对象会导致对象无法控制,从而引发一系列的问题。
- 点赞
- 收藏
- 分享
- 文章举报
Geng_Sir发布了5 篇原创文章 · 获赞 0 · 访问量 68私信关注