讲这些概念之前,想首先说一下“电脑配置”这个问题。
(1)硬件方面:CPU(中央处理器)、单核处理器、多核处理器、缓存(Cache)、内存
一台计算机中一般配置一个CPU,早期的计算机中一个CPU只包含一个内核(称为单核处理器),而目前的计算机中一个CPU一般都包含多个内核(称为多核处理器)。
缓存是指可以进行高速数据交换的存储器,它先于内存与CPU交换数据,因此速率很快。缓存的工作原理是当CPU要读取一个数据时,首先从CPU缓存中查找,找到就立即读取并送给CPU处理;没有找到,就从速率相对较慢的内存中读取并送给CPU处理,同时把这个数据所在的数据块调入缓存中,可以使得以后对整块数据的读取都从缓存中进行,不必再调用内存。
【概念:物理CPU、物理核和逻辑核】
① 物理CPU
实际Server中插槽上的CPU个数。
物理cpu数量,可以数不重复的 physical id 有几个。
② 逻辑CPU(逻辑核)
Linux用户对 /proc/cpuinfo 这个文件肯定不陌生. 它是用来存储cpu硬件信息的。
信息内容分别列出了processor 0 – n 的规格。这里需要注意,如果你认为n就是真实的cpu数的话, 就大错特错了。
一般情况,我们认为一颗cpu可以有多核,加上intel的超线程技术(Hyper Threading), 可以在逻辑上再分一倍数量的cpu core出来。
逻辑CPU数量=物理cpu数量 x cpu cores 这个规格值 x 2(如果支持并开启ht)。
③ CPU核数(物理核)
一块CPU上面能处理数据的芯片组的数量、比如现在的i5 760,是双核心四线程的CPU、而 i5 2250 是四核心四线程的CPU。
一般来说,物理CPU个数×每颗核数就应该等于逻辑CPU的个数,如果不相等的话,则表示服务器的CPU支持超线程技术。
总结一下:逻辑CPU的个数 = 物理CPU个数×每颗核数×超线程数
(2)软件方面:操作系统
**
一、进程与线程
进程与线程都是操作系统资源管理的方式。
1、进程(Process)
概念:
进程是操作系统级别的一个基本概念,可以简单的理解为“正在运行的程序”。进程是表示资源分配的的基本概念,又是调度运行的基本单位,是操作系统中的并发执行的基本单位。
特性:
进程之间是相互独立的,在操作系统级别中,一个进程所执行的程序无法直接访问另外一个进程所执行的内存区域(即实现进程之间通信比较困难),一个进程运行失败也不会影响其它进程的运行。Windows操作系统就是利用进程在内存中把工作划分为多个独立的运行区域。
进程管理:
.NET中使用Processs类,进行进程的启动、停止、获取或设置进程优先级、确定进程是否响应、是否已经退出、以及获取系统正在与运行的所有进程列表和各进程的资源占用情况等。
2、线程(Thread)
概念:
从程序实现的角度来说,将一个进程划分为若干个独立的执行流(任务),每个独立的执行流(任务)都称为一个线程。
从硬件实现的角度来说,对于早期的单核处理器,可以将线程看作是操作系统分配处理器时间片的基本执行单位;对于目前的多核处理器,可以将线程看作是内核上独立执行的代码段。(注意:一个是软件手段,另外一个是硬件方法)
一个进程中既可以只包含一个线程,也可以同时包含多个进程。
特性:
线程之间共用内存空间,要求同时进行又要共享某些变量的并发操作,只能用多线程,不能用进程。多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。
线程管理:
线程是靠CPU中某个逻辑内核(也叫硬件线程)来执行的。新的多线程实现技术应该是让不同的线程在不同的逻辑内核上真正的并行执行,这和传统的单核处理器通过轮询时间片来实现“宏观并行”的执行方式完全不同。
在.NET C#中利用Thread和ThreadPool可在进程中实现多线程并行执行,这是传统的实现多线程编程的技术,如果是新的开发不建议使用这种技术,因为其实现细节控制比较复杂。
3、区别
根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位。
在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)。
内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。
包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
二、同步与异步
关注点是消息通信机制,表示一种协作方式。
1、同步(Sync)
概念:
所谓同步,就是发出一个功能调用时,在没有得到结果之前,该调用就不返回或继续执行后续操作。简单来说,同步就是必须一件一件事做,等前一件做完了才能做下一件事。
例如:B/S模式中的表单提交,具体过程是:客户端提交请求->等待服务器处理->处理完毕返回,在这个过程中客户端(浏览器)不能做其他事。
2、异步(Async)
概念:
异步与同步相对,当一个异步过程调用发出后,调用者在没有得到结果之前,就可以继续执行后续操作。当这个调用完成后,一般通过状态、通知和回调来通知调用者。对于异步调用,调用的返回并不受调用者控制。
对于通知调用者的三种方式,具体如下:
状态
即监听被调用者的状态(轮询),调用者需要每隔一定时间检查一次,效率会很低。
通知
当被调用者执行完成后,发出通知告知调用者,无需消耗太多性能。
回调
与通知类似,当被调用者执行完成后,会调用调用者提供的回调函数。
例如:B/S模式中的ajax请求,具体过程是:客户端发出ajax请求->服务端处理->处理完毕执行客户端回调,在客户端(浏览器)发出请求后,仍然可以做其他的事。
3、区别
重点是请求发出后,是否需要等待结果,才能继续执行其他操作。
三、阻塞与非阻塞
关注点是程序在等待调用结果(消息、返回值)时的状态。
1、阻塞
概念:
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
2、非阻塞
概念:
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
【概念:五种IO模型与同步、异步、阻塞、非阻塞】
五种IO模型分别是:阻塞IO模型、非阻塞IO模型、多路复用IO模型、信号驱动IO模型以及异步IO模型。
说到IO模型,都会牵扯到同步、异步、阻塞、非阻塞这几个词。在处理IO时,阻塞和非阻塞都是同步IO,只有使用了特殊的API才是异步IO。如下图:
Tips:
- IO有内存IO、网络IO和磁盘IO三种,通常我们说的IO指的是后两者。
- 阻塞和非阻塞,是函数/方法的实现方式,即在数据就绪之前是立刻返回还是等待,即发起IO请求是否会被阻塞。
- 以文件IO为例,一个IO读过程是文件数据从磁盘→内核缓冲区→用户内存的过程。同步与异步的区别主要在于数据从内核缓冲区→用户内存这个过程需不需要用户进程等待,即实际的IO读写是否阻塞请求进程。(网络IO把磁盘换做网卡即可)
四、并发与并行
在单CPU系统中,系统调度在某一时刻只能让一个线程运行,虽然这种调试机制有多种形式(大多数是时间片轮巡为主),但无论如何,要通过不断切换需要运行的线程让其运行的方式就叫并发(concurrent)。而在多CPU系统中,可以让两个以上的线程同时运行,这种可以同时让两个以上线程同时运行的方式叫做并行(parallel)。
1、并发
概念:
在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。.这种方式我们称之为并发(Concurrent)。
特性:
通常用于提高运行在单处理器上的程序的性能。使用多个线程可以帮助我们在单个处理系统中实现更高的吞吐量,如果一个程序是单线程的,这个处理器在等待一个同步I/O操作完成的时候,他仍然是空闲的。在多线程系统中,当一个线程等待I/O的同时,其他的线程也可以执行。
"并发"在微观上不是同时执行的,只是把时间分成若干段,使多个线程快速交替的执行,从宏观外来看,好像是这些线程都在执行。
2、并行
概念:
当系统有一个以上CPU时,则线程的操作有可能非并发。比如有一组程序(线程),当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,无论从微观还是宏观,都可以按独立异步的速度同时进行,这种方式我们称之为并行(Parallel)。
3、区别
你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。所以我认为它们最关键的点就是:是否是『同时』。
【概念:并行、并发与多线程】
并行需要两个或两个以上的线程跑在不同的处理器上,并发可以跑在一个处理器上通过时间片进行切换。
【概念:异步与多线程】
1)基本概念
1. 并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。其中两种并发关系分别是同步和互斥。其中并发又有伪并发和真并发,伪并发是指单核处理器的并发,真并发是指多核处理器的并发。
2. 互斥:线程间相互排斥的使用临界资源的现象,就叫互斥。
3. 同步:线程之间的关系不是相互排斥临界资源的关系,而是相互依赖的关系。进一步的说明:就是前一个线程的输出作为后一个线程的输入,当第一个线程没有输出时第二个线程必须等待。具有同步关系的一组并发线程相互发送的信息称为消息或事件。
4. 并行:在单处理器中多道程序设计系统中,线程被交替执行,表现出一种并发的外部特种;在多处理器系统中,线程不仅可以交替执行,而且可以重叠执行。在多处理器上的程序才可实现并行处理。从而可知,并行是针对多处理器而言的。并行是同时发生的多个并发事件,具有并发的含义,但并发不一定并行,也亦是说并发事件之间不一定要同一时刻发生。
5. 多线程:多线程是程序设计的逻辑层概念,它是线程中并发运行的一段代码。多线程可以实现线程间的切换执行。
6. 异步:异步和同步是相对的,同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。线程就是实现异步的一个方式。异步是让调用方法的主线程不需要同步等待另一线程的完成,从而可以让主线程干其它的事情。
2)深层次理解
目的与手段
异步和多线程并不是一个同等关系,异步是最终目的,多线程只是实现异步的一种手段。
多线程和异步操作的异同
多线程和异步操作两者都可以达到避免调用线程阻塞的目的,从而提高软件的可响应性。甚至有些时候我们就认为多线程和异步操作是等同的概念。但是,多线程和异步操作还是有一些区别的。而这些区别造成了使用多线程和异步操作的时机的区别。
异步操作的本质
所有的程序最终都会由计算机硬件来执行,所以为了更好的理解异步操作的本质,我们有必要了解一下它的硬件基础。 熟悉电脑硬件的朋友肯定对DMA这个词不陌生,硬盘、光驱的技术规格中都有明确DMA的模式指标,其实网卡、声卡、显卡也是有DMA功能的。DMA就是直接内存访问的意思,也就是说,拥有DMA功能的硬件在和内存进行数据交换的时候可以不消耗CPU资源。只要CPU在发起数据传输时发送一个指令,硬件就开始自己和内存交换数据,在传输完成之后硬件会触发一个中断来通知操作完成。这些无须消耗CPU时间的I/O操作正是异步操作的硬件基础。所以即使在DOS这样的单进程(而且无线程概念)系统中也同样可以发起异步的DMA操作。
线程的本质
线程不是一个计算机硬件的功能,而是操作系统提供的一种逻辑功能,线程本质上是进程中一段并发运行的代码,所以线程需要操作系统投入CPU资源来运行和调度。
异步操作的优缺点
因为异步操作无须额外的线程负担,并且使用回调的方式进行处理,在设计良好的情况下,处理函数可以不必使用共享变量(即使无法完全不用,最起码可以减少共享变量的数量),减少了死锁的可能。当然异步操作也并非完美无暇。编写异步操作的复杂程度较高,程序主要使用回调方式进行处理,与普通人的思维方式有些初入,而且难以调试。
多线程的优缺点
多线程的优点很明显,线程中的处理程序依然是顺序执行,符合普通人的思维习惯,所以编程简单。但是多线程的缺点也同样明显,线程的使用(滥用)会给系统带来上下文切换的额外负担。并且线程间的共享变量可能造成死锁的出现。
适用范围
在了解了线程与异步操作各自的优缺点之后,我们可以来探讨一下线程和异步的合理用途。我认为:当需要执行I/O操作时,使用异步操作比使用线程+同步I/O操作更合适。I/O操作不仅包括了直接的文件、网络的读写,还包括数据库操作、Web Service、HttpRequest以及.Net Remoting等跨进程的调用。
而线程的适用范围则是那种需要长时间CPU运算的场合,例如耗时较长的图形处理和算法执行。但是往往由于使用线程编程的简单和符合习惯,所以很多朋友往往会使用线程来执行耗时较长的I/O操作。这样在只有少数几个并发操作的时候还无伤大雅,如果需要处理大量的并发操作时就不合适了。
【故事:异步、多线程与并行】
非专业人员,就用非专业的语言解释下吧,比喻不够贴切,但大概是那么个意思,
先听我讲一个故事:
那还是10年前,还没有12306的年代,大家买票只能去火车站买。因为大家都要过年回家,都还不想等,火车站只有一个,窗口只有那么多,头疼啊。更头疼的是,排到窗口的那个人,各种挑剔,不要贵的,不要晚上的,不要站票......跟售票员各种墨迹,后面的人更加着急,一个个义愤填膺,骂爹骂娘。
现在假设整个城市就只有1个火车,1个售票员,每个乘客咨询售票员后需要思考1分钟再决定买哪趟车的票。
1.异步:在买票的人咨询后,需要思考1分钟,马上靠边站,但不用重新排队,什么时候想清楚可以立马去跟售票员去买票。在该人站在旁边思考的时候,后面的人赶紧上去接着买。这时候队伍是很快的挪动的,没有阻塞,售票员的最大化的效率。
2.多线程:火车站开n个窗口(但还是只有一个人售票),外面同时排n个队,售票员回答咨询者问题后,立马马上去下个窗口,然后继续轮换到下个窗口.....哪个窗口的人决定好了,售票员立马过去买给他。这个时候乘客比较简单,但万一那个队伍有人思考半天纠结,后面的人就悲剧了。
3.并行:复制n个火车站,同时卖票,买票能力大大增强。大家也可以哪个火车站人少,就去那个买票。
可见:在只有一个火车站,且只有一个售票员的情况下,卖完一个再卖一个就会导致资源浪费,效率低下,队伍卡死,很难往前挪动。1,2优化的办法都解决了队伍不动,售票率低下的问题。但增加火车站,增加窗口,增加售票员才是好办法。
结论:
1.异步和多线程其实效率差不多,但是开的窗口不多例如3个,同时有很多人都是去花5分钟,而不是1分钟去纠结的时候,多线程效率实际是低于异步的,因为售票员还是常遇到3个队伍同时卡在那纠结不能买票的时候。
2.这2个概念拿来对比也有点不合适,因为他们不是一个概念,多线程的目的还是为了实现异步,多线程应该是一种实现异步的手段。异步应该去跟同步比较才对。
3.多线程比较简单,但需要增设窗口,增加成本,且售票员比较累这类似apache下php,和node.js下javascript的关系,一个是多线程,但是是阻塞的,另外一个是单线程异步非阻塞的。php的方案比较符合常规思维,但比较费内存,node.js非阻塞,用较少的资源就能完成同样的任务,但编程比较费神。
4.并行,类似同时利用多核cpu的各个核去计算。并发可分为伪并发、真并发。前者例如单核处理器的并发,后者发是指多核处理器的并发。
5.终极办法是并行计算,并且每个cpu下进行异步计算,这样你的每个核都充分利用。只不过对编程要求太高了太高了,如果不是密集型计算,例如大型有限元计算(多采用并发),或者服务器同时处理上千的访问(多采用异步或者多线程),还是老老实实的用传统的办法吧,毕竟常规程序的计算量对现在的硬件来说,问题都不大。
补充:
(1)任务(Task)
.NET C#中,Task 是一个类, 它表示一个操作不返回一个值,通常以异步方式执行。
(2)协程
.NET C#中,是协同程序,在主程序运行的同时,开启另外一段逻辑处理,来协同当前程序的执行。
(3)应用程序域(AppDomain)
.NET C#为了保证代码的键壮性CLR希望不同服务功能的代码之间相互隔离,这种隔离可以通过创建多个进程来实现,但操作系统中创建进程是即耗时又耗费资源的一件事,所以在CLR中引入了AppDomain的概念,AppDomain主要是用来实现同一进程中的各AppDomain之间的隔离
(4)上下文管理
上下文指的是进程间占有的资源空间。当一个进程时间片到了或者资缺的时候就会让出cpu 当另一个进程开始始用CPU之前,系统要保存即将退出进程的执行状态,以便轮到时间片或有资源的时候现场恢复。这就所谓的上下文切换。安全上下文,调用上下文,同步上下文其实本质都一样——进程切换。
应用程序域是进程中承载程序集的逻辑分区,在应用程序域当中,存在更细粒度的用于承载.NET对象的实体,那就.NET上下文Context。——引用
(5)锁
.NET C#中,lock 关键字可确保当一个线程位于代码的临界区时,另一个线程不会进入该临界区。 如果其他线程尝试进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
(6)串行
串行是指多个任务时,各个任务按顺序执行,完成一个之后才能进行下一个。
(7)分布式与集群
集群是个物理形态,分布式是个工作方式。