线程Thread
- 线程是一个可执行路径,它可以独立于其他线程执行,每个线程都在操作系统的进程内执行,而操作系统提供了程序运行的独立环境
 - 在C#中线程的创建方式是
var thread = new Thread(WriteY);,他必须传入一个执行任务,这个执行任务可以是一个函数(委托),然后可以设置它的名字,调用Start()来执行线程中的任务,在这里会看到X和Y交替输出,这是thread线程和主线程并发执行的效果。 
1  | namespace ThreadDemo  | 
- 线程的一些属性
- IsAlive:线程一旦开始执行,IsAlive就是true,线程结束就为false,结束的条件是传入的委托结束了执行
 - Name:线程的名称,只能设置一次,一般用于调试。
 - CurrentThread:返回当前执行的线程
 - 注意:线程一旦结束,就无法再重启
 
 
Join和Sleep
- Join:调用Join方法,就可以等待另一个线程结束,即thread2调用了Join方法后,其他的线程就会等待thread2执行结束后才能执行
 
1  | class Program  | 
- Sleep:会暂停当前的线程,但线程不会被抢占
 - 添加超时:使用TimeSpan对象来设置Join的超时时间或设置Sleep的超时时间
 
1  | public class JoinTimeSpan  | 
- 注意:
Thread.Sleep(0)这样调用会导致线程立即放弃当前的时间片,自动将cpu移交给其他线程。这个功能类似于Thread.Yield(),但是它只会把执行交给同一处理器上的其他线程。- 当等待Sleep或Join的时候,线程处于阻塞的状态。
 Sleep(0)或Yield()在高级性能调试中是一个很好的诊断工具,有助于发现线程安全问题,但在生产代码中不能随意地使用Yield()函数。
 
阻塞
- 如果线程的执行由于某种原因导致暂停,则就认为该线程被阻塞了。被阻塞的线程会立即将其处理器的时间片生成给其他线程,从此就不再消耗处理器的时间,直到满足其阻塞条件为止。
 - 可以使用
ThreadState属性来判断线程是否处于被阻塞状态,ThreadState是一个枚举,可以用来获取线程的状态。它可以通过按位的形式合并状态的选项。 

1  | class Program  | 
解除阻塞Unblocking
- 当遇到下列四种情况的时候,就会解除阻塞:
 
- 阻塞条件被满足
 - 操作超时(需要设置超时条件)
 - 通过
Thread.Interrupt()进行打断 - 通过
Thread.Abort()进行中止 
上下文切换
- 当线程阻塞时或接触阻塞时,操作系统将执行上下文切换,这回产生少量开销。
 
阻塞vs忙等待
- 阻塞和忙等待也被称为
IO-bound和Cpu-bound - IO-bound操作的工作方式有两种:
- 在当前线程上同步等待
 - 异步的操作
 
 
1  | //例如  | 
- 而Cpu-bound就类似于死循环
 
1  | //例如  | 
线程安全
本地状态和共享状态
- Local本地独立:CLR为每个线程分配自己的内存栈,以便使本地变量保持独立。
 - Shared共享:
- 如果多个线程都引用到同一个对象实例,那么它们就共享了数据。
 - 被lambda表达式或匿名委托所捕获的本地变量,会被编译器转化为字段,所以也会被共享。
 - 静态字段也会在线程间共享。
 
 - 下面的_done字段就是共享变量
 
1  | class Program  | 
- 字段共享就会引出线程安全问题,因为上述例子的输出有可能不是固定的,如果在
_done = true;前加上Thread.Sleep()结果就会不一样 
1  | class Program  | 
lock
- 在现实中应该尽量避免这种共享变量的使用,这种线程安全也可以用互斥锁lock来解决。
 
1  | class Program  | 
- 在这里使用
lock将共享代码块包括,这个lock代码块就称为临界区,每次只允许一个线程进入,在多线程上下文中,以这种方式避免不确定性的代码就叫做线程安全 - 但是使用lock来解决线程安全也存在很大的问题,第一很容易忘记对字段加锁,第二会引起死锁。
 
线程传参
- 可以直接使用lambda表达式做为Thread的参数,比如:
 
1  | class Program  | 
- 可以使用Thread的Start方法来传递任务的参数,因为传参委托的类型是
ThreadStart,所以参数必须是object类型。 
1  | class Program  | 
- 使用lambda表达式可以很简单地给Thread传递参数。但是线程开始后,可能会不小心修改了被捕获地变量,比如下面的例子,每一次运行都有不同的输出,而且输出有可能会有相同的值。
 
1  | for (int i = 0; i < 10; i++)  | 
- 可以使用局部变量来解决这个问题
 
1  | for (int i = 0; i < 10; i++)  | 
线程的优先级
- 线程的优先级(Priority属性)决定了相对于操作系统中其他活跃线程所占的执行时间。
 - 如果想让某线程的优先级比其他进程中的线程高,那就必须提升进程的优先级,这里可以使用Process类
 
1  | using (Process p = Process.GetCurrentProcess())  | 
- 但是,提高线程或进程的优先级可能会导致其他线程或线程处于饥饿状态,不能随便设置。
 
信号
- 有时,你需要让某个线程一直处于等待状态,直到接收到其他线程发来的通知,才解除等待状态,这就叫做signaling。最简单的信号结构是
MaunalResetEvent类对象。 - 调用
MaunalResetEvent类对象的WaitOne方法就会阻塞当前的线程,直到另一个线程通过调用Set方法来打开信号(发送信号)。 
1  | internal class Program  | 
- 信号打开后可以通过
Reset方法来重新关闭信号。 
同步上下文
- Thread Marshaling:Marshaling的意思是假如要将一个平台上的数据发送给另一个平台,但是两个平台使用的数据格式不一致,这时候就需要把数据转化为可发送的数据格式,这就类似于json的序列化,而接收端就是Unmarshaling,也就类似于反序列化。而Thread Marshaling就是把一些数据的所有权从一个线程交给另外一个线程。
 - 在C#中同步上下文是用
System.ComponentModel下的SynchronizationContext抽象类来实现的,可以通过实例化SynchronizationContext的子类,调用它的Post方法来实现同步上下文,这一般在富客户端应用比较常用(WPF、WinForm),它可以让主线程的一些事件的操作交给UI线程,这样UI线程可以做一下事件的调用,主线程也不会进入假死。 
学习资料:B站杨旭