进程是正在进行的一个过程或者一个任务。而负责执行任务的则是cpu。python
程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。同一个程序执行两次是两个进程。linux
在多道编程中,咱们容许多个程序同时加载到内存中,在操做系统的调度下,能够实现并发地执行。这是这样的设计,大大提升了CPU的利用率。进程的出现让每一个用户感受到本身独享CPU,所以,进程就是为了在CPU上实现多道编程而提出的。程序员
不管是并行仍是并发,在用户看来都是'同时'运行的,而一个cpu同一时刻只能执行一个任务。web
1)并发:是伪并行,即某个时段看起来是同时运行。单个cpu+多道技术就能够实现并发,(并行也属于并发)数据库
2)并行:同时运行,只有具有多个cpu才能实现并行。编程
单核下,能够利用多道技术,多核中的每一个核也均可以利用多道技术(多道技术是针对单核而言的)缓存
有四个核,六个任务,这样同一时间有四个任务被执行,假设分别被分配给了cpu1,cpu2,cpu3,cpu4,tomcat
一旦任务1遇到I/O就被迫中断执行,此时任务5就拿到cpu1的时间片去执行,这就是单核下的多道技术安全
而一旦任务1的I/O结束了,操做系统会从新调用它(需知进程的调度、分配给哪一个cpu运行,由操做系统说了算),可能被分配给四个cpu中的任意一个去执行。服务器
同步:A调用B,B处理直到得到结果,才返回给A。
须要调用者一直等待和确认调用结果是否返回, 而后继续往下执行。
异步:A调用B,B直接返回。无需等待结果,B经过状态,通知等来通知A或回调函数来处理。
调用结果返回时, 会以消息或回调的方式通知调用者。
阻塞:A调用B,A被挂起直到B返回结果给A,A继续执行。
调用结果返回前,当前进程挂起不可以处理其余任务,一直等待调用结果返回。
非阻塞:A调用B,A不会被挂起,A能够执行其余操做。
调用结果返回前,当前进程不挂起, 能够去处理其余任务。
因此,同步异步说的是被调用者结果返回时通知进程的一种通知机制,阻塞非阻塞说的是调用结果返回前进程的状态,是挂起仍是继续处理其余任务。
线程是操做系统可以进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运做单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中能够并发多个线程,每条线程并行执行不一样的任务。
1)线程共享建立它的进程的内存空间,进程的内存是独立的
2)线程能够直接访问其进程的数据段,进程拥有父进程的数据段副本
3)线程能够直接与其进程的其余线程通讯,进程间通讯须要经过一个中间代理来实现。
4)新线程很容易建立,建立新进程须要对父进程实现一次复制。
5)线程能够控制和操做同一进程里的其余线程,进程只能控制子进程。
6)对主线程的更改(取消,优先级更改等)可能会影响到进程中其余线程的行为; 对父进程的更改不会影响子进程。
多线程指的是,在一个进程中开启多个线程,简单的讲:若是多个任务共用一块地址空间,那么必须在一个进程内开启多个线程。
详细的讲分为4点:
1. 多线程共享一个进程的地址空间。
2. 线程比进程更轻量级,线程比进程更容易建立可撤销,在许多操做系统中,建立一个线程比建立一个进程要快10-100倍,在有大量线程须要动态和快速修改时,这一特性颇有用
3. 若多个线程都是cpu密集型的,那么并不能得到性能上的加强,可是若是存在大量的计算和大量的I/O处理,拥有多个线程容许这些活动彼此重叠运行,从而会加快程序执行的速度。
4. 在多cpu系统中,为了最大限度的利用多核,能够开启多个线程,比开进程开销要小的多。(这一条并不适用于python)
每一个线程在执行的过程当中都须要先获取GIL,保证同一时刻只有一个线程在运行,目的是解决多线程同时竞争程序中的全局变量而出现的线程安全问题。它并非python语言的特性,仅仅是因为历史的缘由在CPython解释器中难以移除,由于python语言运行环境大部分默认在CPython解释器中。
因为之前的电脑基本都是单核CPU,多线程和单线程几乎看不出差异,但是因为计算机的迅速发展,如今的电脑几乎都是多核CPU了,最少也是两个核心数的,这时差异就出来了:经过以前的案例咱们已经知道,即便在多核CPU中,多线程同一时刻也只有一个线程在运行,这样不只不能利用多核CPU的优点,反而因为每一个线程在多个CPU上是交替执行的,致使在不一样CPU上切换时形成资源的浪费,反而会更慢。即缘由是一个进程只存在一把gil锁,当在执行多个线程时,内部会争抢gil锁,这会形成当某一个线程没有抢到锁的时候会让cpu等待,进而不能合理利用多核cpu资源。
协程是单线程下的并发,一种用户态的轻量级线程,即协程是由用户程序本身控制调度的。
1) python的线程属于内核级别的,即由操做系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其余线程运行)
2) 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操做系统)控制切换,以此来提高效率(!!!非io操做的切换与效率无关)
3)操做系统控制线程的切换,用户在单线程内控制协程的切换
优势:
协程的切换开销更小,属于程序级别的切换,操做系统彻底感知不到,于是更加轻量级,单线程内就能够实现并发的效果,最大限度地利用cpu。
缺点:
协程的本质是单线程下,没法利用多核,(能够是一个程序开启多个进程,每一个进程内开启多个线程,每一个线程内开启协程)
协程指的是单个线程,于是一旦协程出现阻塞,将会阻塞整个线程
1)必须在只有一个单线程里实现并发
2)修改共享数据不需加锁
3)用户程序里本身保存多个控制流的上下文栈
4)一个协程遇到IO操做自动切换到其它协程(如何实现检测IO,yield、greenlet都没法实现,就用到了gevent模块(select机制)
IO发生时涉及的对象和步骤。对于一个network IO (这里咱们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另外一个就是系统内核(kernel)。当一个read操做发生时,该操做会经历两个阶段:
1)等待数据准备
2)将数据从内核拷贝到进程中
不一样IO模型的区别就是在两个阶段上各有不一样的状况
补充
#一、输入操做:read、readv、recv、recvfrom、recvmsg共5个函数,若是会阻塞状态,则会经历wait data和copy data两个阶段,若是设置为非阻塞则在wait 不到data时抛出异常 #二、输出操做:write、writev、send、sendto、sendmsg共5个函数,在发送缓冲区满了会阻塞在原地,若是设置为非阻塞,则会抛出异常 #三、接收外来连接:accept,与输入操做相似 #四、发起外出连接:connect,与输出操做相似
默认状况下全部的socket都是阻塞的
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来讲,不少时候数据在一开始尚未到达(好比,尚未收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。
而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,而后kernel返回结果,用户进程才解除block的状态,从新运行起来。因此,blocking IO的特色就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了
除非特别指定,几乎全部的IO接口 ( 包括socket接口 ) 都是阻塞型的。这给网络编程带来了一个很大的问题,如在调用recv(1024)的同时,线程将被阻塞,在此期间,线程将没法执行任何运算或响应任何的网络请求。
一个简单的解决方案:
#在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每一个链接都拥有独立的线程(或进程),这样任何一个链接的阻塞都不会影响其余的链接。
该方案的问题是:
#开启多进程或都线程的方式,在遇到要同时响应成百上千路的链接请求,则不管多线程仍是多进程都会严重占据系统资源,下降系统对外界响应效率,并且线程与进程自己也更容易进入假死状态。
改进方案:
#不少程序员可能会考虑使用“线程池”或“链接池”。“线程池”旨在减小建立和销毁线程的频率,其维持必定合理数量的线程,并让空闲的线程从新承担新的执行任务。
“链接池”维持链接的缓存池,尽可能重用已有的链接、减小建立和关闭链接的频率。
这两种技术均可以很好的下降系统开销,都被普遍应用不少大型系统,如websphere、tomcat和各类数据库等。
改进后方案其实也存在着问题:
#“线程池”和“链接池”技术也只是在必定程度上缓解了频繁调用IO接口带来的资源占用。
并且,所谓“池”始终有其上限,当请求大大超过上限时,“池”构成的系统对外界的响应并不比没有池的时候效果好多少。
因此使用“池”必须考虑其面临的响应规模,并根据响应规模调整“池”的大小。
对应上例中的所面临的可能同时出现的上千甚至上万次的客户端请求,“线程池”或“链接池”或许能够缓解部分压力,可是不能解决全部问题。总之,
多线程模型能够方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈,能够用非阻塞接口来尝试解决这个问题。
能够经过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操做时,流程是这个样子:
当用户进程发出read操做时,若是kernel中的数据尚未准备好,那么它并不会block用户进程,而是马上返回一个error。从用户进程角度讲 ,它发起一个read操做后,并不须要等待,而是立刻就获得了一个结果。用户进程判断结果是一个error时,它就知道数据尚未准备好,因而用户就能够在本次到下次再发起read询问的时间间隔内作其余事情,或者直接再次发送read操做。一旦kernel中的数据准备好了,而且又再次收到了用户进程的system call,那么它立刻就将数据拷贝到了用户内存(这一阶段仍然是阻塞的),而后返回。
也就是说非阻塞的recvform系统调用调用以后,进程并无被阻塞,内核立刻返回给进程,若是数据还没准备好,此时会返回一个error。进程在返回以后,能够干点别的事情,而后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程一般被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。须要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。
能够看到用户进程须要一直主动询问kernel数据准备好了没有,带来的问题就是
循环调用recv()将大幅度推高CPU占用率,在低配主机下极容易出现卡机状况。
任务完成的响应延迟增大了,由于每过一段时间才去轮询一次read操做,而任务可能在两次轮询之间的任意时间完成。这会致使总体数据吞吐量的下降。
此外,在这个方案中recv()更多的是起到检测“操做是否完成”的做用,实际操做系统提供了更为高效的检测“操做是否完成“做用的接口,例如select()多路复用模式,能够一次检测多个链接是否活跃。
有些地方也称这种IO方式为事件驱动IO。咱们都知道,select/epoll的好处就在于单个process就能够同时处理多个网络链接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的全部socket,当某个socket有数据到达了,就通知用户进程。它的流程如图:
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”全部select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操做,将数据从kernel拷贝到用户进程。
这个图和blocking IO的图其实并无太大的不一样,事实上还更差一些。由于这里须要使用两个系统调用(select和recvfrom),而blocking IO只调用了一个系统调用(recvfrom)。可是,用select的优点在于它能够同时处理多个connection。
强调:
1)若是处理的链接数不是很高的话,使用select/epoll的web server不必定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优点并非对于单个链接能处理得更快,而是在于能处理更多的链接。
2)在多路复用模型中,对于每个socket,通常都设置成为non-blocking,可是,如上图所示,整个用户的process实际上是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。
结论: select的优点在于能够处理多个链接,不适用于单个链接
select监听fd变化的过程分析:
#用户进程建立socket对象,拷贝监听的fd到内核空间,每个fd会对应一张系统文件表,内核空间的fd响应到数据后,就会发送信号给用户进程数据已到; #用户进程再发送系统调用,好比(accept)将内核空间的数据copy到用户空间,同时做为接受数据端内核空间的数据清除,这样从新监听时fd再有新的数据又能够响应到了(发送端由于基于TCP协议因此须要收到应答后才会清除)。
该模型的优势:
#相比其余模型,使用select() 的事件驱动模型只用单线程(进程)执行,占用资源少,不消耗太多 CPU,同时可以为多客户端提供服务。若是试图创建一个简单的事件驱动的服务器程序,这个模型有必定的参考价值。
该模型的缺点:
#首先select()接口并非实现“事件驱动”的最好选择。由于当须要探测的句柄值较大时,select()接口自己须要消耗大量时间去轮询各个句柄。不少操做系统提供了更为高效的接口,如linux提供了epoll,BSD提供了kqueue,Solaris提供了/dev/poll,…。若是须要实现更高效的服务器程序,相似epoll这样的接口更被推荐。遗憾的是不一样的操做系统特供的epoll接口有很大差别,因此使用相似于epoll的接口实现具备较好跨平台能力的服务器会比较困难。 #其次,该模型将事件探测和事件响应夹杂在一块儿,一旦事件响应的执行体庞大,则对整个模型是灾难性的。
用户进程发起read操做以后,马上就能够开始去作其它的事。而另外一方面,从kernel的角度,当它受到一个asynchronous read以后,首先它会马上返回,因此不会对用户进程产生任何block。而后,kernel会等待数据准备完成,而后将数据拷贝到用户内存,当这一切都完成以后,kernel会给用户进程发送一个signal,告诉它read操做完成了。
而后因为不稳定等缘由异步IO用的并很少。