一、 进程

简单来说,进程是对资源的抽象,是资源的容器,在传统操作系统中,进程是资源分配的基本单位,而且是执行的基本单位,进程支持并发执行,因为每个进程有独立的数据,独立的堆栈空间。一个程序想要并发执行,开多个进程即可。

Q1:在单核下,进程之间如何同时执行?

首先要区分两个概念——并发和并行

  • 并发:并发是指在一段微小的时间段中,有多个程序代码段被CPU执行,宏观上表现出来就是多个程序能”同时“执行。
  • 并行:并行是指在一个时间点,有多个程序段代码被CPU执行,它才是真正的同时执行。

所以应该说进程之间是并发执行。对于CPU来讲,它不知道进程的存在,CPU主要与寄存器打交道。有一些常用的寄存器,如程序计数器寄存器,这个寄存器存储了将要执行的指令的地址,这个寄存器的地址指向哪,CPU就去哪。还有一些堆栈寄存器和通用寄存器等等等,总之,这些数据构成了一个程序的执行环境,这个执行环境就叫做”上下文(Context)“,进程之间切换本质就是保存这些数据到内存,术语叫做”保存现场“,然后恢复某个进程的执行环境,也即是”恢复现场“,整个过程术语叫做“上下文切换”,具体点就是进程上下文切换,这就是进程之间能并发执行的本质——频繁的切换进程上下文。这个功能是由操作系统提供的,是内核态的,对应用软件开发人员透明。

二、 线程

进程虽然支持并发,但是对并发不是很友好,不友好是指每开启一个进程,都要重新分配一部分资源,而线程相对进程来说,创建线程的代价比创建进程要小,所以引入线程能更好的提高并发性。在现代操作系统中,进程变成了资源分配的基本单位,而线程变成了执行的基本单位,每个线程都有独立的堆栈空间,同一个进程的所有线程共享代码段和地址空间等共享资源。相应的上下文切换从进程上下文切换变成了线程上下文切换。

三、 为什么要引入进程和线程

  1. 提高CPU利用率,在早期的单道批处理系统中,如果执行中的代码需要依赖与外部条件,将会导致CPU空闲,例如文件读取,等待键盘信号输入,这将浪费大量的CPU时间。引入多进程和线程可以解决CPU利用率低这个问题。
  2. 隔离程序之间的数据(每个进程都有单独的地址空间),保证系统运行的稳定性。
  3. 提高系统的响应性和交互能力。

四、 在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 }

主线程(默认是前台线程)执行完毕,程序直接退出。

但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 }

上述代码将会导致程序奔溃,如下图。

要想直接获取返回值和可以直接从主线程捕捉线程函数内未捕捉的异常,我们可以这么做。

新建一个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 }

简单的包装了一下(不要在意细节),我们便可以实现我们想要的效果。

测试代码如下

 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 }

可以看到,我们可以通过简单包装Thread对象,便可实现如下效果

  • 直接读取线程函数返回值
  • 直接捕捉线程函数未捕捉的异常(前提是调用了Wait()函数或者Result属性)

这是理解和运用Task的基础,Task功能非常完善,但是运用好Task需要掌握许多概念,下面再说。

C#多线程编程(一)进程与线程的更多相关文章

  1. python 多线程编程之进程和线程基础概念

    多线程编程 在多线程(multithreaded,MT)出现之前,计算机程序的执行都是由单个步骤序列组成的,该序列组合在主机的CPU中按照同步顺序执行.无论是任务本身需要按照步骤顺序执行,还是整个过程 ...

  2. Java并发编程:进程和线程之由来

    Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够融会贯通 ...

  3. Java多线程基础:进程和线程之由来

    转载: Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够 ...

  4. Java并发编程:进程和线程之由来__进程让操作系统的并发性成为可能,而线程让进程的内部并发成为可能

    转载自海子:http://www.cnblogs.com/dolphin0520/p/3910667.html Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨 ...

  5. python并发编程之进程、线程、协程的调度原理(六)

    进程.线程和协程的调度和运行原理总结. 系列文章 python并发编程之threading线程(一) python并发编程之multiprocessing进程(二) python并发编程之asynci ...

  6. 数据结构(逻辑结构,物理结构,特点) C#多线程编程的同步也线程安全 C#多线程编程笔记 String 与 StringBuilder (StringBuffer) 数据结构与算法-初体验(极客专栏)

    数据结构(逻辑结构,物理结构,特点) 一.数据的逻辑结构:指反映数据元素之间的逻辑关系的数据结构,其中的逻辑关系是指数据元素之间的前后件关系,而与他们在计算机中的存储位置无关.逻辑结构包括: 集合 数 ...

  7. 1、Java多线程基础:进程和线程之由来

    Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够融会贯通 ...

  8. java并发编程:进程和线程

    java并发编程涉及到很多内容,当然也包括多线程,再次补充点相关概念 原文地址:http://www.cnblogs.com/dolphin0520/p/3910667.html 一.操作系统中为什么 ...

  9. C++多线程编程(三)线程间通信

    多线程编程之三——线程间通讯 作者:韩耀旭 原文地址:http://www.vckbase.com/document/viewdoc/?id=1707 七.线程间通讯 一般而言,应用程序中的一个次要线 ...

  10. Linux多线程编程,为什么要使用线程,使用线程的理由和优点等

    线程?为什么有了进程还需要线程呢,他们有什么区别?使用线程有什么优势呢?还有多线程编程的一些细节问题,(http://www.0830120.com)如线程之间怎样同步.互斥,这些东西将在本文中介绍. ...

随机推荐

  1. [Python基础]004.语法(3)

    语法(3) 方法 定义 调用 参数 返回 模块 引入模块 写模块 模块名称 dir() 方法 定义 语法 def 方法名(参数): 返回值 return 没有指定返回值的方法,默认返回空值 None ...

  2. Java中的集合类型体系(一)

    Java中的集合类型体系(一) 提问:为什么需要集合? 通常情况下,程序需要根据运行时才知道创建了多少对象.若非程序运行时,而在开发阶段,我们并不知道创建了多少对象,甚至不知道对象的准确类型,为了满足 ...

  3. spring boot 整合 poi 导出excel

    一. 第一种方式 1.首先从中央仓库中导入架包Poi3.14以及Poi-ooxml3.14. <dependency> <groupId>org.apache.poi</ ...

  4. Angular 从入坑到挖坑 - 路由守卫连连看

    一.Overview Angular 入坑记录的笔记第六篇,介绍 Angular 路由模块中关于路由守卫的相关知识点,了解常用到的路由守卫接口,知道如何通过实现路由守卫接口来实现特定的功能需求,以及实 ...

  5. cmd启动mysql,服务名无效

    通过cmd无法启动mysql 解决办法: 在计算机管理(或者win+R,输入services.msc)中打开服务,查看mysql服务的名称是否正确. 键入正确的名称启动mysql.

  6. Java实现 蓝桥杯VIP 算法训练 -2进制(暴力)

    试题 算法训练 -2进制 问题描述 给出1个十进制整数N,计算出它的-2进制表示. 输入格式 第一行:一个整数N,表示要转换的十进制数. 输出格式 第一行:N的-2进制表示. 样例输入 -13 样例输 ...

  7. Java实现 LeetCode 面试题62. 圆圈中最后剩下的数字(约瑟夫环)

    面试题62. 圆圈中最后剩下的数字 0,1,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字.求出这个圆圈里剩下的最后一个数字. 例如,0.1.2.3.4这5个数字组成一个圆 ...

  8. Java实现 蓝桥杯VIP 算法训练 P1101

    有一份提货单,其数据项目有:商品名(MC).单价(DJ).数量(SL).定义一个结构体prut,其成员是上面的三项数据.在主函数中定义一个prut类型的结构体数组,输入每个元素的值,计算并输出提货单的 ...

  9. Java实现中值问题

    中值问题是求一个n个数列表中某一数组下标k,它要求该下标元素比列表中的一半元素大,又比另一半元素小,这个中间的值被称为中值. 使用Lomuto划分算法思想,此处引用<算法设计与分析基础>第 ...

  10. 6.keras-基于CNN网络的Mnist数据集分类

    keras-基于CNN网络的Mnist数据集分类 1.数据的载入和预处理 import numpy as np from keras.datasets import mnist from keras. ...