一、 进程 简单来说,进程是对资源的抽象,是资源的容器,在传统操作系统中,进程是资源分配的基本单位,而且是执行的基本单位,进程支持并发执行,因为每个进程有独立的数据,独立的堆栈空间。一个程序想要并...
一、 进程
简单来说,进程是对资源的抽象,是资源的容器,在传统操作系统中,进程是资源分配的基本单位,而且是执行的基本单位,进程支持并发执行,因为每个进程有独立的数据,独立的堆栈空间。一个程序想要并发执行,开多个进程即可。
Q1:在单核下,进程之间如何同时执行?
首先要区分两个概念——并发和并行
- 并发:并发是指在一段微小的时间段中,有多个程序代码段被CPU执行,宏观上表现出来就是多个程序能”同时“执行。
- 并行:并行是指在一个时间点,有多个程序段代码被CPU执行,它才是真正的同时执行。
所以应该说进程之间是并发执行。对于CPU来讲,它不知道进程的存在,CPU主要与寄存器打交道。有一些常用的寄存器,如程序计数器寄存器,这个寄存器存储了将要执行的指令的地址,这个寄存器的地址指向哪,CPU就去哪。还有一些堆栈寄存器和通用寄存器等等等,总之,这些数据构成了一个程序的执行环境,这个执行环境就叫做”上下文(Context)“,进程之间切换本质就是保存这些数据到内存,术语叫做”保存现场“,然后恢复某个进程的执行环境,也即是”恢复现场“,整个过程术语叫做“上下文切换”,具体点就是进程上下文切换,这就是进程之间能并发执行的本质——频繁的切换进程上下文。这个功能是由操作系统提供的,是内核态的,对应用软件开发人员透明。
二、 线程
进程虽然支持并发,但是对并发不是很友好,不友好是指每开启一个进程,都要重新分配一部分资源,而线程相对进程来说,创建线程的代价比创建进程要小,所以引入线程能更好的提高并发性。在现代操作系统中,进程变成了资源分配的基本单位,而线程变成了执行的基本单位,每个线程都有独立的堆栈空间,同一个进程的所有线程共享代码段和地址空间等共享资源。相应的上下文切换从进程上下文切换变成了线程上下文切换。
三、 为什么要引入进程和线程
- 提高CPU利用率,在早期的单道批处理系统中,如果执行中的代码需要依赖与外部条件,将会导致CPU空闲,例如文件读取,等待键盘信号输入,这将浪费大量的CPU时间。引入多进程和线程可以解决CPU利用率低这个问题。
- 隔离程序之间的数据(每个进程都有单独的地址空间),保证系统运行的稳定性。
- 提高系统的响应性和交互能力。
四、 在C#中创建托管线程
1. Thread类
在.NET中,托管线程分为:
- 前台线程
- 后台线程
一个.Net程序中,至少要有一个前台线程,所有前台线程结束了,所有的后台线程将会被公共语言运行时(CLR)强制销毁,程序执行结束。
如下将在控制台程序中创建一个后台线程
1 static void Main(string[] args) 2 { 3 var t = new Thread(() => 4 { 5 Thread.Sleep(1000); 6 Console.WriteLine("执行完毕"); 7 }); 8 t.IsBackground = true; 9 t.Start(); 10 }View Code
主线程(默认是前台线程)执行完毕,程序直接退出。
但IsBackground 属性改为false时,控制台会打印“执行完毕”。
2. 有什么问题
直接使用Thread类来进行多线程编程浪费资源(服务器端更加明显)且不方便,举个子。
假如我写一个Web服务器程序,每个请求创建一个线程,那么每一次我都要new一个Thread对象,然后传入处理HttpRequest的委托,处理完之后,线程将会被销毁,这将会导致浪费大量CPU时间和内存,在早期CPU性能不行和内存资源珍贵的情况下这个缺点会被放大,在现在这个缺点不是很明显,原因是硬件上来了。
不方便体现在哪呢?
- 无法直接获取另一个线程内未被捕捉的异常
- 无法直接获取线程函数的返回值
1 public static void ThrowException() 2 { 3 throw new Exception("发生异常"); 4 } 5 static void Main(string[] args) 6 { 7 var t = new Thread(() => 8 { 9 Thread.Sleep(1000); 10 ThrowException(); 11 }); 12 t.IsBackground = false; 13 try 14 { 15 t.Start(); 16 } 17 catch(Exception e) 18 { 19 Console.WriteLine(e.Message); 20 } 21 }View Code
上述代码将会导致程序奔溃,如下图。
要想获取返回值和可以从主线程捕捉线程函数内未捕捉的异常,我们可以这么做。
新建一个MyTask.cs文件,内容如下
1 using System; 2 using System.Threading; 3 namespace ConsoleApp1 4 { 5 public class MyTask 6 { 7 private Thread _thread; 8 private Action _action; 9 private Exception _innerException; 10 public MyTask() 11 { 12 } 13 public MyTask(Action action) 14 { 15 _action = action; 16 } 17 protected virtual void Excute() 18 { 19 try 20 { 21 _action(); 22 } 23 catch(Exception e) 24 { 25 _innerException = e; 26 } 27 28 } 29 public void Start() 30 { 31 if (_thread != null) throw new InvalidOperationException("任务已经开始"); 32 _thread = new Thread(() => Excute()); 33 _thread.Start(); 34 } 35 public void Start(Action action) 36 { 37 _action = action; 38 if (_thread != null) throw new InvalidOperationException("任务已经开始"); 39 _thread = new Thread(() => Excute()); 40 _thread.Start(); 41 } 42 public void Wait() 43 { 44 _thread.Join(); 45 if (_innerException != null) throw _innerException; 46 } 47 } 48 public class MyTask<T> : MyTask 49 { 50 private Func<T> _func { get; } 51 private T _result; 52 public T Result { 53 54 private set => _result = value; 55 get 56 { 57 base.Wait(); 58 return _result; 59 } 60 } 61 public MyTask(Func<T> func) 62 { 63 _func = func; 64 } 65 public new void Start() 66 { 67 base.Start(() => 68 { 69 Result = _func(); 70 }); 71 } 72 } 73 }View Code
简单的包装了一下(不要在意细节),我们便可以实现我们想要的效果。
测试代码如下
1 public static void ThrowException() 2 { 3 throw new Exception("发生异常"); 4 } 5 public static void Test3() 6 { 7 MyTask<string> myTask = new MyTask<string>(() => 8 { 9 Thread.Sleep(1000); 10 return "执行完毕"; 11 }); 12 myTask.Start(); 13 try 14 { 15 Console.WriteLine(myTask.Result); 16 } 17 catch (Exception e) 18 { 19 Console.WriteLine(e.Message); 20 } 21 } 22 public static void Test2() 23 { 24 MyTask<string> myTask = new MyTask<string>(() => 25 { 26 Thread.Sleep(1000); 27 ThrowException(); 28 return "执行完毕"; 29 }); 30 myTask.Start(); 31 try 32 { 33 Console.WriteLine(myTask.Result); 34 } 35 catch(Exception e) 36 { 37 Console.WriteLine(e.Message); 38 } 39 } 40 public static void Test1() 41 { 42 MyTask myTask = new MyTask(() => 43 { 44 Thread.Sleep(1000); 45 ThrowException(); 46 }); 47 myTask.Start(); 48 try 49 { 50 myTask.Wait(); 51 } 52 catch (Exception e) 53 { 54 Console.WriteLine(e.Message); 55 } 56 } 57 static void Main(string[] args) 58 { 59 Test1(); 60 Test2(); 61 Test3(); 62 }
本文标题为:C#并行编程(一)——进程与线程
基础教程推荐
- C#8 的模式匹配实现 2023-02-03
- Unity3D移动端实现摇一摇功能 2023-02-02
- C# 实例化接口对象的方法 2022-11-02
- Unity中C#和Java的相互调用实例代码 2022-12-06
- C#使用OpenCv图像批处理并改变图片大小并且重命名 2023-02-08
- c# – NHibernate / MySQL字符串连接 2023-11-26
- C#多线程之线程同步 2023-05-26
- C#从数据库读取图片并保存的两种方法 2023-03-29
- C# 执行Javascript脚本的方法步骤 2023-05-12
- C#泛型集合类型实现添加和遍历 2023-07-04