前言
准备再好好总结一下线程。
1.概念
并行(parallel):同一时间,多个线程/进程同时执行。多线程的目的就是为了并行,充分利用cpu多个核心,提高程序性能。
线程(threading):线程是操作系统能够进行运算调度的最小单位,是进程的实际运作单位。
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并行多个线程,每条线程并行执行不同的任务。
进程(process):进程是操作系统进行资源分配的基本单位。多个进程并行的在计算机上执行,多个线程并行的在进程中执行,
进程之间是隔离的,线程之间共享堆,私有栈空间。
CLR 为每个线程分配各自独立的 栈(stack) 空间,因此局部变量是线程独立的。
临界区(critical section):在同一时刻只有一个线程能进入,不允许并发。当有线程进入临界区段时,其他试图进入的线程或是进程必须 等待或阻塞(blocking)。
线程阻塞(blocking):指一个线程在执行过程中暂停,以等待某个条件的触发来解除暂停。阻塞状态的线程不会消耗CPU资源。
挂起(Suspend):和阻塞非常相似,在虚拟内存管理的操作系统中,通常会把阻塞状态的进程的物理内存空间换出到硬盘,等需要再次运行的时候,再从硬盘换入到物理内存。描述进程没有占用实际的物理内存空间的情况,这个状态就是挂起状态。
运行时:
线程在内部由一个 线程调度器(thread scheduler) 管理,一般 CLR 会把这个任务交给操作系统完成。线程调度器确保所有活动的线程能够分配到适当的执行时间,并且保证那些处于等待或阻塞状态(例如,等待排它锁或者用户输入)的线程不消耗CPU时间。
在单核计算机上,线程调度器会进行 时间切片(time-slicing) ,快速的在活动线程中切换执行。在 Windows 操作系统上,一个时间片通常在十几毫秒(译者注:默认 15.625ms),远大于 CPU 在线程间进行上下文切换的开销(通常在几微秒区间)。
在多核计算机上,多线程的实现是混合了时间切片和 真实的并发(genuine concurrency) ,不同的线程同时运行在不同的 CPU 核心上。仍然会使用到时间切片,因为操作系统除了要调度其它的应用,还需要调度自身的线程。
线程的执行由于外部因素(比如时间切片)被中断称为 被抢占(preempted)。在大多数情况下,线程无法控制其在什么时间,什么代码块被抢占。
2.Thread
maxStackSize:设置线程的栈空间。不建议使用。
https://stackoverflow.com/questions/5507574/maximum-thread-stack-size-net
带参数的线程委托
public delegate void ParameterizedThreadStart(object? obj);
不带 参数的线程委托
public delegate void ThreadStart();
2.1创建和启动
new Thread(DoWork).Start(); //线程执行DoWork();
DoWork(); //主线程执行DoWork();
void DoWork() => Console.WriteLine("DoWork");
2.2Name属性,便于调试
var thread = new Thread(DoWork); //线程执行DoWork();
thread.Name = "peng";
thread.Start();
DoWork(); //主线程执行DoWork();
void DoWork() => Console.WriteLine($"{Thread.CurrentThread.Name}:DoWork");
2.3传递参数
public delegate void ParameterizedThreadStart(object? obj);
var thread = new Thread(msg => DoWorkByMsg("Thread:Hello!")); //线程执行DoWorkByMsg();
thread.Start(); //线程执行DoWorkByMsg()
DoWorkByMsg("Main Thread:Hello!"); //主线程执行DoWorkByMsg();
void DoWorkByMsg(string msg)
{
Console.WriteLine($"{msg}");
}
2.4前台/后台线程
默认情况下,显式创建的线程都是前台线程(foreground threads)。只要有一个前台线程在运行,程序就可以保持存活不结束。
当一个程序中所有前台线程停止运行时,仍在运行的所有后台线程会被强制终止。
非默认情况,指的是将Thread的IsBackground属性设置为true。
当进程以强制终止这种方式结束时,后台线程执行栈中所有finally块就会被避开。如果程序依赖finally(或是using)块来执行清理工作,例如释放数据库/网络连接或是删除临时文件,就可能会产生问题。
为了避免这种问题,在退出程序时可以显式的等待这些后台线程结束。有两种方法可以实现:
- 如果是显式创建的线程,在线程上调用Join阻塞。
- 如果是使用线程池线程,使用信号构造,如事件等待句柄。
线程的 前台/后台状态 与它的 优先级/执行时间的分配无关。
2.5异常处理
2.5.1try...catch...执行方法
net8框架不在方法内加try...catch...也被抛出异常,记录一下,后面查查资料。
2.5.2AppDomain.CurrentDomain.UnhandledException
AppDomain.CurrentDomain.UnhandledException 会对所有未处理的异常触发,因此它可以用于集中记录线程发生的异常,但是它不能阻止程序退出。
并非所有线程上的异常都需要处理,以下情况,.NET Framework 会为你处理:
- 异步委托(APM)
- BackgroundWorker(EAP)
- 任务并行库(TPL)
2.6中断与中止
所有阻塞方法Wait(), Sleep() or Join(),在阻塞条件永远无法被满足且没有指定超时时间的情况下,线程会陷入永久阻塞。
有两个方式可以实现强行结束:中断、中止
2.6.1中断(Interrupt)
在一个阻塞线程上调用Thread.Interrupt
会强制释放它,并抛出ThreadInterruptedException
异常,与上文的一样,这个异常同样不会抛出。
var t = new Thread(new ThreadStart(() =>
{
try
{
Thread.Sleep(Timeout.Infinite); // 无期限休眠
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}));
t.Start();
Thread.Sleep(3000); // 睡3s后中断线程t
t.Interrupt();
2.6.2中止(Abort)
Net5以上不支持Abort,不做演示,仅做了解。
https://learn.microsoft.com/zh-cn/dotnet/core/compatibility/core-libraries/5.0/thread-abort-obsolete
Interrupt和Abort最大的不同是:调用Interrupt线程会继续工作直到下次被阻塞时抛出异常,而调用Abort会立即在线程正在执行的地方抛出异常(非托管代码除外)。
这将导致一个新的问题:.NET Framework 中的代码可能会被中止,而且不是安全的中止。如果中止发生在FileStream被构造期间,很可能造成一个非托管文件句柄会一直保持打开直到应用程序域结束。
2.7协作取消模式
正如上面所说Interrupt和Abort总是危险的
,替代方案就是实现一个协作模式(cooperative )
:工作线程定期检查一个用于指示是否应该结束的标识
,发起者只需要设置这个标识,等待工作线程响应,即可取消线程执行。
Framework 4.0 提供了两个类CancellationTokenSource和CancellationToken来完成这个模式:
CancellationTokenSource
定义了Cancel
方法。CancellationToken
定义了IsCancellationRequested
属性和ThrowIfCancellationRequested
方法。
cancelSource.CancelAfter():在此System.Threading.CancellationTokenSource上调度一个取消操作
在指定毫秒数之后。
cancelToken.ThrowIfCancellationRequested():抛出一个系统。OperationCanceledException,如果此令牌已被取消请求。
var cancelSource = new CancellationTokenSource();
cancelSource.CancelAfter(5000); //5秒后取消
var t = new Thread(() => DoWorkCancel(cancelSource.Token));
t.Start();
t.Join();
void DoWorkCancel(CancellationToken cancelToken) {
int i = 0;
while (true)
{
cancelToken.ThrowIfCancellationRequested();
Thread.Sleep(1000);
i++;
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:{i}");
}
}
标签:01Thread,Thread,thread,DoWork,线程,进程,执行
From: https://www.cnblogs.com/pengboke/p/18117013