进程是系统进行资源分配和调度的一个独立单位。一个程序在一个数据集中的一次动态执
行过程,可以简单理解为“正在执行的程序” 。
进程一般由程序、数据集、进程控制块三部分组成。
程序用来描述进程要完成哪些功能以及如何完成;数据集则是程序在执行过程中所需要使用
的资源;进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它
来控制和管理进程,它是系统感知进程存在的唯一标志。
进程的局限是创建、撤销和切换的开销比较大。
是进程中的基本执行单元,是操作系统分配 CPU 时间的基本单位,一个进程可以包含若干个线程,在进程入口执行的第一个线程被视为这个进程的主线程。
线程是进程的一个实体,也叫轻量级进程,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
协程,又称微线程、纤程。英文名 Coroutine。
协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。
协程是一个线程执行,但执行有点像多线程。
协程执行过程中,在协程内部可中断,然后转而执行别的协程,在适当的时候再返回来接着执行。协程之间不是调用者与被调用者的关系,而是彼此对称、平等的,通过相互协作共同完成任务。
Python 对用在 generator 中的 yield 可以一定程度上实现协程。通过 yield 方式转移执行权。
进程,能够完成多任务,比如在一台电脑上能够同时运行多个 QQ;
线程,能够完成多任务,比如一个 QQ 中的多个聊天窗口。
一个程序至少有一个进程,一个进程至少有一一个线程.
线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性高。
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的
线程不能够独立执行,必须依存在进程中;
线程属于内核级别的,即由操作系统控制调度;
协程:应用程序级别(而非操作系统)控制切换,以此来提升效率。
线程执行开销小, 但不利于资源的管理和保护; 而进程正相反。
同步就是发起一个请求,直到请求返回结果之后,才进行下一步操作。
简单来说,同步就是必须一件一件事做,等前一件做完了才能做下一件事。
eg: B/S模式中的表单提交,具体过程是:客户端提交请求->等待服务器处理->处理完毕返回,在这个过程中客户端(浏览器)不能做其他事。
异步与同步相对,当一个异步操作发出后,调用者在没有得到结果之前,就可以继续执行后续操作。这就是异步。
当这个调用完成后,一般通过状态、通知和回调来通知调用者。对于异步调用,调用的返回并不受调用者控制。
对于通知调用者的三种方式,具体如下:
eg: B/S 模式中的 ajax 请求,具体过程是:客户端发出 ajax 请求 -> 服务端处理 -> 处理完毕执行客户端回调,在客户端(浏览器)发出请求后,仍然可以做其他的事。
总结来说,同步和异步的区别:请求发出后,是否需要等待结果,才能继续执行其他操作。
在调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会被唤醒执行后续的操作。
在结果没有返回之前,该调用不会阻塞住当前线程。
阻塞和非阻塞这两个概念与程序(线程)等待消息通知(无所谓同步或者异步)时的状态有关。也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的。
阻塞和非阻塞关注的是 程序在等待调用结果(消息,返回值)时的状态.
指应用能够交替执行不同的任务。
当有多个线程在一个 CPU 运行,操作系统只能把 CPU 运行时间划分成若干个时间段,再将
时间段分配给各个线程执行,不同时间段,快速切换不同的线程代码运行。
指应用能够同时执行不同的任务。
系统有多个 CPU,执行多个线程,可以一个 CPU 执行一个线程,线程之间互不抢占 CPU 资源,可以同时进行。
你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。
所以我认为它们最关键的点就是:是否是同时。
其实,异步是目的,而多线程是实现这个目的的方法。异步是说,A发起一个操作后(一般都是比较耗时的操作,如果不耗时的操作就没有必要异步了),可以继续自顾自的处理它自己的事儿,不用干等着这个耗时操作返回。
多线程和异步操作两者都可以达到避免调用线程阻塞的目的,从而提高软件的可响应性。甚至有些时候我们就认为多线程和异步操作是等同的概念。但是,多线程和异步操作还是有一些区别的。而这些区别造成了使用多线程和异步操作的时机的区别。
所有的程序最终都会由计算机硬件来执行,所以为了更好的理解异步操作的本质,我们有必要了解一下它的硬件基础。 熟悉电脑硬件的朋友肯定对DMA这个词不陌生,硬盘、光驱的技术规格中都有明确DMA的模式指标,其实网卡、声卡、显卡也是有DMA功能的。DMA就是直 接内存访问的意思,也就是说,拥有DMA功能的硬件在和内存进行数据交换的时候可以不消耗CPU资源。只要CPU在发起数据传输时发送一个指令,硬件就开 始自己和内存交换数据,在传输完成之后硬件会触发一个中断来通知操作完成。这些无须消耗CPU时间的I/O操作正是异步操作的硬件基础。所以即使在DOS 这样的单进程(而且无线程概念)系统中也同样可以发起异步的DMA操作。
线程不是一个计算机硬件的功能,而是操作系统提供的一种逻辑功能,线程本质上是进程中一段并发运行的代码,所以线程需要操作系统投入CPU资源来运行和调度。
因为异步操作无须额外的线程负担,并且使用回调的方式进行处理,在设计良好的情况下,处理函数可以不必使用共享变量(即使无法完全不用,最起码可以减少 共享变量的数量),减少了死锁的可能。当然异步操作也并非完美无暇。编写异步操作的复杂程度较高,程序主要使用回调方式进行处理,与普通人的思维方式有些 初入,而且难以调试。
异步与多线程,从辩证关系上来看,异步和多线程并不是一个同等关系。异步是目的,多线程只是我们实现异步的一个手段。
什么是异步:异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回。实现异步可以采用多线程技术或则交给另外的进程来处理。
非专业人员,就用非专业的语言解释下吧,比喻不够贴切,但大概是那么个意思,
先听我讲一个故事:
那还是10年前,还没有12306的年代,大家买票只能去火车站买。因为大家都要过年回家,都还不想等,火车站只有一个,窗口只有那么多,头疼啊。更头疼的是,排到窗口的那个人,各种挑剔,不要贵的,不要晚上的,不要站票…跟售票员各种墨迹,后面的人更加着急,一个个义愤填膺,骂爹骂娘。
现在假设整个城市就只有1个火车,1个售票员,每个乘客咨询售票员后需要思考1分钟再决定买哪趟车的票。
异步:在买票的人咨询后,需要思考1分钟,马上靠边站,但不用重新排队,什么时候想清楚可以立马去跟售票员去买票。在该人站在旁边思考的时候,后面的人赶紧上去接着买。这时候队伍是很快的挪动的,没有阻塞,售票员的最大化的效率。
多线程:火车站开n个窗口(但还是只有一个人售票),外面同时排n个队,售票员回答咨询者问题后,立马马上去下个窗口,然后继续轮换到下个窗口…哪个窗口的人决定好了,售票员立马过去买给他。这个时候乘客比较简单,但万一那个队伍有人思考半天纠结,后面的人就悲剧了。
并行:复制n个火车站,同时卖票,买票能力大大增强。大家也可以哪个火车站人少,就去那个买票。
可见:在只有一个火车站,且只有一个售票员的情况下,卖完一个再卖一个就会导致资源浪费,效率低下,队伍卡死,很难往前挪动。1,2优化的办法都解决了队伍不动,售票率低下的问题。但增加火车站,增加窗口,增加售票员才是好办法。
结论:
异步和多线程其实效率差不多,但是开的窗口不多例如3个,同时有很多人都是去花5分钟,而不是1分钟去纠结的时候,多线程效率实际是低于异步的,因为售票员还是常遇到3个队伍同时卡在那纠结不能买票的时候。
这2个概念拿来对比也有点不合适,因为他们不是一个概念,多线程的目的还是为了实现异步,多线程应该是一种实现异步的手段。异步应该去跟同步比较才对。
多线程比较简单,但需要增设窗口,增加成本,且售票员比较累这类似apache下php,和node.js下javascript的关系,一个是多线程,但是是阻塞的,另外一个是单线程异步非阻塞的。php的方案比较符合常规思维,但比较费内存,node.js非阻塞,用较少的资源就能完成同样的任务,但编程比较费神。
并行,类似同时利用多核cpu的各个核去计算。并发可分为伪并发、真并发。前者例如单核处理器的并发,后者发是指多核处理器的并发。
终极办法是并行计算,并且每个cpu下进行异步计算,这样你的每个核都充分利用。只不过对编程要求太高了太高了,如果不是密集型计算,例如大型有限元计算(多采用并发),或者服务器同时处理上千的访问(多采用异步或者多线程),还是老老实实的用传统的办法吧,毕竟常规程序的计算量对现在的硬件来说,问题都不大。
同步/异步关注的是消息通知的机制,而阻塞/非阻塞关注的是程序(线程)等待消息通知时的状态。
以小明下载文件打个比方,从这两个关注点来再次说明这两组概念,希望能够更好的促进大家的理解。
同步阻塞:小明一直盯着下载进度条,到 100% 的时候就完成。
同步体现在:等待下载完成通知;
阻塞体现在:等待下载完成通知过程中,不能做其他任务处理;
同步非阻塞:小明提交下载任务后就去干别的,每过一段时间就去瞄一眼进度条,看到 100% 就完成。
同步体现在:等待下载完成通知,但是要在;
非阻塞体现在:等待下载完成通知过程中,去干别的任务了,只是时不时会瞄一眼进度条;【小明必须要在两个任务间切换,关注下载进度】
异步阻塞:小明换了个有下载完成通知功能的软件,下载完成就“叮”一声。不过小明仍然一直等待“叮”的声音(看起来很傻,不是吗)。
异步体现在:下载完成“叮”一声通知;
阻塞体现在:等待下载完成“叮”一声通知过程中,不能做其他任务处理;
异步非阻塞:仍然是那个会“叮”一声的下载软件,小明提交下载任务后就去干别的,听到“叮”的一声就知道完成了。
异步体现在:下载完成“叮”一声通知;
非阻塞体现在:等待下载完成“叮”一声通知过程中,去干别的任务了,只需要接收“叮”声通知即可;【软件处理下载任务,小明处理其他任务,不需关注进度,只需接收软件“叮”声通知,即可】
也就是说,同步/异步是“下载完成消息”通知的方式(机制),而阻塞/非阻塞则是在等待“下载完成消息”通知过程中的状态(能不能干其他任务),在不同的场景下,同步/异步、阻塞/非阻塞的四种组合都有应用。
所以,综上所述,同步和异步仅仅是关注的消息如何通知的机制,而阻塞与非阻塞关注的是等待消息通知时的状态。也就是说,同步的情况下,是由处理消息者自己去等待消息是否被触发,而异步的情况下是由触发机制来通知处理消息者,所以在异步机制中,处理消息者和触发机制之间就需要一个连接的桥梁:
在小明的例子中,这个桥梁就是软件“叮”的声音。
同步阻塞形式
效率是最低的,拿上面的例子来说,就是你专心等待下载完成,什么别的事都不做。实际程序中:就是未对 fd 设置 O_NONBLOCK 标志位的 read/write 操作;
异步阻塞形式
异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息通知时被阻塞。比如 select 函数,假如传入的最后一个 timeout 参数为 NULL,那么如果所关注的事件没有一个被触发,程序就会一直阻塞在这个 select 调用处。
同步非阻塞形式
实际上是效率低下的,想象一下你一边干别的事情一边还需要抬头看下载完成没有,如果把干别的事情和观察下载完成情况的位置看成是程序的两个操作的话,这个程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的。很多人会写阻塞的 read/write 操作,但是别忘了可以对 fd 设置 O_NONBLOCK 标志位,这样就可以将同步操作变成非阻塞的了。
异步非阻塞形式
效率更高,因为等待下载完成是你(等待者)的事情,而通知你则是电脑(消息触发机制)的事情,程序没有在两种不同的操作中来回切换。