C#多线程之线程基础篇( 二 )


??在单核计算机上,线程调度器会进行 时间切片(time-slicing) ,快速的在活动线程中切换执行 。在 Windows 操作系统上,一个时间片通常在十几毫秒(译者注:默认 15.625ms),远大于 CPU 在线程间进行上下文切换的开销(通常在几微秒区间) 。
??在多核计算机上,多线程的实现是混合了时间切片和 真实的并发(genuine concurrency),不同的线程同时运行在不同的 CPU 核心上 。仍然会使用到时间切片,因为操作系统除了要调度其它的应用,还需要调度自身的线程 。
??线程的执行由于外部因素(比如时间切片)被中断称为 被抢占(preempted) 。在大多数情况下 , 线程无法控制其在什么时间 , 什么代码块被抢占 。

??多线程同样也会带来缺点,最大的问题在于它提高了程序的复杂度 。使用多个线程本身并不复杂,复杂的是线程间的交互(共享数据)如何保证安全 。无论线程间的交互是否有意为之,都会带来较长的开发周期,以及带来间歇的、难以重现的 bug 。因此,最好保证线程间的交互尽可能少,并坚持简单和已被证明的多线程交互设计 。
??当频繁地调度和切换线程时(且活动线程数量大于 CPU 核心数),多线程会增加系统资源和 CPU 的开销,线程的创建和销毁也会增加开销 。多线程并不总是能提升程序的运行速度,如果使用不当 , 反而可能降低速度 。
三、基础创建与启动使用Thread类的构造方法来创建线程,支持以下两种委托
public delegate void ThreadStart();public delegate void ParameterizedThreadStart (object? obj);关于Thread构造重载方法参数 maxStackSize,不建议使用
https://stackoverflow.com/questions/5507574/maximum-thread-stack-size-net
public void 创建一个线程(){var t = new Thread(Go);// 开一个线程tt.Start();// 启动t线程,执行Go方法Go();// 主线程执行Go方法}void Go(){_testOutputHelper.WriteLine("hello world!");}每一个线程都有一个 Name 属性,我们可以设置它以便于调试 。线程的名字只能设置一次,再次修改会抛出异常 。
public void 线程命名(){var t = new Thread(Go);// 开一个线程tt.Name = "worker";t.Start();// 启动t线程,执行Go方法Go();// 主线程执行Go方法}void Go(){// Thread.CurrentThread属性会返回当前执行的线程_testOutputHelper.WriteLine(Thread.CurrentThread.Name + " say: hello!");}传递参数Thread类的Start方法重载支持向thread实例传参
public void Start(object? parameter)参数被lambda表达式捕获,传递给Go方法
public void 创建一个线程(){var t = new Thread(msg => Go(msg));// 开一个线程tt.Start("hello world!");// 启动t线程,执行Go方法Go("main thread say:hello world!");// 主线程执行Go方法}void Go(object? msg){_testOutputHelper.WriteLine(msg?.ToString());}请务必注意,不要在启动线程之后误修改被捕获变量(captured variables)
public void 闭包问题(){for (int i = 0; i < 10; i++){new Thread (() => Go(i)).Start();}}前台/后台线程默认情况下,显式创建的线程都是前台线程(foreground threads) 。只要有一个前台线程在运行,程序就可以保持存活不结束 。当一个程序中所有前台线程停止运行时,仍在运行的所有后台线程会被强制终止 。
这里说的 显示创建 , 指的是通过new Thread()创建的线程
非默认情况,指的是将Thread的IsBackground属性设置为true
static void Main (string[] args){ Thread worker = new Thread ( () => Console.ReadLine() ); if (args.Length > 0) worker.IsBackground = true; worker.Start();}
当进程以强制终止这种方式结束时,后台线程执行栈中所有finally块就会被避开 。如果程序依赖finally(或是using)块来执行清理工作,例如释放数据库/网络连接或是删除临时文件,就可能会产生问题 。为了避免这种问题,在退出程序时可以显式的等待这些后台线程结束 。有两种方法可以实现:
  • 如果是显式创建的线程,在线程上调用Join阻塞 。
  • 如果是使用线程池线程,使用信号构造 , 如事件等待句柄 。
在任何一种情况下,都应指定一个超时时间,从而可以放弃由于某种原因而无法正常结束的线程 。这是后备的退出策略:我们希望程序最后可以关闭,而不是让用户去开任务管理器(╯-_-)╯╧══╧
线程的 前台/后台状态 与它的 优先级/执行时间的分配无关 。
异常处理当线程开始运行后,其内部发生的异常不会抛到外面,更不会被外面的try-catch-finally块捕获到 。

推荐阅读