Python 多进程 多线程 协程 I/O多路复用

引言

在学习Python多进程、多线程以前,先脑补一下以下场景;html

说有这么一道题:小红烧水须要10分钟,拖地须要5分钟,洗菜须要5分钟,第一种方式:若是同样同样去干,就是简单的加法,所有作完,须要20分钟;第二种方式:若是在烧水的同时去拖地、洗菜,所有作完,只须要10分钟!也可类比,工做中咱们处理平常事务;python

能够将上述示例中,作事的主体:人,理解成计算机的CPU,而第二种作事方式,能够简单的理解成多任务!linux

这里须要了解一下,计算机是由硬件和软件组成的。git

硬件中:CPU是计算机的核心,它承担计算机的全部任务。算法

软件中:操做系统是运行在硬件之上的软件,是计算机的管理者,它负责资源的管理和分配、任务的调度。编程

程序是运行在系统上的具备某种功能的软件,好比说浏览器,音乐播放器等。 每当执行程序的时候,都会完成必定的功能,好比说浏览器帮咱们打开网页等!为了保证每一个程序的独立性,就须要一个专门的管理和控制执行程序的数据结构——进程控制块。浏览器

Come on!一大波概念来袭!!!缓存

进程  VS  线程

什么是进程(Process)?服务器

狭义定义:进程就是一段程序的执行过程。网络

广义定义:进程是具备必定独立功能的程序,关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位。

进程的概念主要有两点:

  • 第一,进程是一个实体。每个进程都有它本身的地址空间,通常状况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程当中调用的指令和本地变量。
  • 第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,咱们称其为进程。

进程的状态: 进程有三个状态,就绪,运行和阻塞。

  • 就绪状态其实就是获取了除cpu外的全部资源,只要处理器分配资源立刻就能够运行。
  • 运行态就是获取了处理器分配的资源,程序开始执行。
  • 阻塞态,当程序条件不够时,须要等待条件知足时候才能执行,如等待I/O操做的时候,此刻的状态就叫阻塞态。

进程的特征:

  • 动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
  • 并发性:任何进程均可以同其余进程一块儿并发执行;
  • 独立性:进程是系统进行资源分配和调度的一个独立单位;
  • 结构性:进程由程序、数据和进程控制块三部分组成。

 

什么是线程(thread)?

定义:线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。

线程起源:

  • 在早期的操做系统中并无线程的概念,进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每一个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。
  • 后来,随着计算机的发展,对CPU的要求愈来愈高,进程之间的切换开销较大,已经没法知足愈来愈复杂的程序的要求了,因而就发明了线程。

线程特征:

  • 线程本身基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),一个线程能够建立和撤销另外一个线程;
  • 一般在一个进程中能够包含若干个线程,固然一个进程中至少有一个线程,否则没有存在的意义。线程能够利用进程所拥有的资源。在引入线程的操做系统中,一般都是把进程做为分配资源的基本单位,而把线程做为独立运行和独立调度的基本单位,因为线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提升系统多个程序间并发执行的程度。

 

进程 和 线程的区别

一、一个程序至少有一个进程,一个进程至少有一个线程;

     进程是资源分配的最小单位,线程是CPU调度的最小单位。

二、资源(内存、寄存器等)分配给进程,进程在执行过程拥有独立的内存空间,而同一进程下的全部线程共享全部资源,从而提升程序的运行效率;

三、处理机分配给线程,即处理机真正运行的是线程;

四、线程在执行过程当中,须要协做同步。不一样线程间的要利用通讯协议来实现同步。

协程  

协程,使用一个线程去完成多个任务,能够理解成微线程。

协程的调度彻底由用户控制,相对独立,有本身的上下文。一个线程能够有多个协程,用户建立了几个线程,而后每一个线程都是循环按照指定的任务清单顺序完成不一样的任务,当任务被阻塞的时候执行下一个任务,当恢复的时候再回来执行这个任务,任务之间的切换只须要保存每一个任务的上下文内容,就像直接操做栈同样的,这样就彻底没有内核切换的开销,能够不加锁的访问全局变量,因此上下文的切换很是快;另外协程还须要保证是非阻塞的且没有相互依赖,协程基本上不能同步通信,多采用异步的消息通信,效率比较高。

协程的适用场景:当程序中存在大量不须要CPU的操做时(IO),适用于协程;

协程(Coroutine)并非真正的多线程

 

多进程  VS  多线程

多进程和多线程 是两个不一样的概念!!!

什么是多进程?

进程是操做系统进行资源分配的最小单位。

在同一个时间里,同一个计算机系统中若是容许两个或两个以上的进程处于运行状态,这即是多任务。现代的操做系统几乎都是多任务操做系统,可以同时管理多个进程的运行。 多任务带来的好处是明显的,好比你能够边听mp3边上网,与此同时甚至能够将下载的文档打印出来,而这些任务之间丝绝不会相互干扰。

那么这里就涉及到并行的问题,俗话说,一心不能二用,这对计算机也同样,原则上一个CPU只能分配给一个进程,以便运行这个进程。咱们一般使用的计算机中只有一个CPU,也就是说只有一颗心,要让它一心多用,同时运行多个进程,就必须使用并发技术。实现并发技术至关复杂,最容易理解的是“时间片轮转进程调度算法”,它的思想简单介绍以下:在操做系统的管理下,全部正在运行的进程轮流使用CPU,每一个进程容许占用CPU的时间很是短(好比10毫秒),这样用户根本感受不出来 CPU是在轮流为多个进程服务,就好象全部的进程都在不间断地运行同样。但实际上在任何一个时间内有且仅有一个进程占有CPU。

假如一台计算机有多个CPU,若是进程数小于CPU数,则不一样的进程能够分配给不一样的CPU来运行,这样,多个进程就是真正同时运行的,这即是并行;但若是进程数大于CPU数,则仍然须要使用并发技术。

什么是多线程?

在计算机编程中,一个基本的概念就是同时对多个任务加以控制。许多程序设计问题都要求程序可以停下手头的工做,改成处理其余一些问题,再返回主进程。

最开始,线程只是用于分配单个处理器的处理时间的一种工具。但假如操做系统自己支持多个处理器,那么每一个线程均可分配给一个不一样的处理器,真正进入“并行运算”状态。

线程处理很是简单,但必须注意一个问题:共享资源!若是有多个线程同时运行,并且它们试图访问相同的资源,就可能会遇到问题。对那些可共享的资源来讲(好比打印机),它们在使用期间必须进入锁定状态。因此一个线程可将资源锁定,在完成了它的任务后,再解开(释放)这个锁,使其余线程能够接着使用一样的资源。

一个采用了多线程技术的应用程序能够更好地利用系统资源。其主要优点在于充分利用了CPU的空闲时间片,能够用尽量少的时间来对用户的要求作出响应,使得进程的总体运行效率获得较大提升,同时加强了应用程序的灵活性。更为重要的是,因为同一进程的全部线程是共享同一内存,因此不须要特殊的数据传送机制,不须要创建共享存储区或共享文件,从而使得不一样任务之间的协调操做与运行、数据的交互、资源的分配等问题更加易于解决。

多进程 多线程 的优缺点

多进程和多线程,是实现多任务最经常使用的两种方式。如今,咱们来讨论一下这两种方式的优缺点。

        要实现多任务,一般咱们会设计Master-Worker模式,Master负责分配任务,Worker负责执行任务,所以,多任务环境下,一般是一个Master,多个Worker。      

  •         若是用多进程实现Master-Worker,主进程就是Master,其余进程就是Worker。
  •         若是用多线程实现Master-Worker,主线程就是Master,其余线程就是Worker。

       多进程 优势 是稳定性高,由于一个子进程崩溃了,不会影响主进程和其余子进程。(固然主进程挂了全部进程就全挂了,可是Master进程只负责分配任务,挂掉的几率低)著名的Apache最先就是采用多进程模式。

        多进程 缺点 是建立进程的代价大,在Unix/Linux系统下,用fork调用还行,在Windows下建立进程开销巨大。另外,操做系统能同时运行的进程数也是有限的,在内存和CPU的限制下,若是有几千个进程同时运行,操做系统连调度都会成问题。还有一个不足之处,多进程下程序与各进程之间的通讯和数据共享不方便;

        多线程 优势 一般比多进程快一点,可是也快不到哪去,开销小!

        多线程主要是为了节约CPU时间,为了同步完成多项任务,不是为了提升运行效率,而是为了提升资源使用效率来提升系统的效率!

        多线程 缺点 任何一个线程挂掉均可能直接形成整个进程崩溃,由于全部线程共享进程的内存。在Windows上,若是一个线程执行的代码出了问题,你常常能够看到这样的提示:“该程序执行了非法操做,即将关闭”,其实每每是某个线程出了问题,可是操做系统会强制结束整个进程。

为何在python里推荐使用多进程而不是多线程?

额外补充

实际开发中 何时用多线程? 何时用多进程?

在TCP/IP协议族中,使用频率最高的上层协议无非是TCP,UDP,HTTP这3者了。

其中HTTP用在B/S架构通讯中,前两种协议用在C/S架构中。

TCP是面向链接的,UDP面向非链接。同时,多进程间是资源隔离的,多线程间是资源共享的。

基于以上缘由,从程序设计难易程度上说:

采用TCP协议的软件用多进程更简单,采用UDP协议的软件两者均可。

这并非惟一的,只是基于各人的经验,以及技术特性致使的复杂程度不一样做为参考标准。

实质上,无论采用什么协议,采用任何多任务技术均可以在理论上实现。然而,软件追求的是高效和稳定,因此在实际软件设计中,便会参考软件的实现难度和维护性等指标。

下面根据经验简单阐述协议与多任务方式的组合特色:

1. TCP+多进程,因为TCP是面向链接的,而一个服务器的单进程空间内若是多个子任务都能看到链接的话,保存链接的资源会增多,同时各资源维护难度也随之加大。故而采用多进程的话(多线程间资源是共享的),子进程各自独享资源,每一个链接对应一个子进程,这样进程之间的资源是天然隔离的,这就省去了管理的代价。

2. UDP+多线程,在UDP程序中,服务端采用多进程仍是多线程均可。由于UDP面向非链接,就省去了管理每一个链接的成本,这样的话采用多线程便可。但实际状况须要根据特性而定。

 

并发  VS  并行

假如一台计算机有多个CPU,若是进程数小于CPU数,则不一样的进程能够分配给不一样的CPU来运行,这样,多个进程就是真正同时运行的,这即是并行。但若是进程数大于CPU数,则仍然须要使用并发技术。
在Windows中,进行CPU分配是以线程为单位的,一个进程可能由多个线程组成,这时状况更加复杂,但简单地说,有以下关系:
总线程数<= CPU数量:并行运行
总线程数> CPU数量:并发运行

多任务操做系统(如Windows)的基本原理是:操做系统将CPU的时间片分配给多个线程,每一个线程在操做系统指定的时间片内完成(注意,这里的多个线程是分属于不一样进程的)。操做系统不断的从一个线程的执行切换到另外一个线程的执行,如此往复,宏观上看来,就好像是多个线程在一块儿执行。因为这多个线程分属于不一样的进程,所以在咱们看来,就好像是多个进程在同时执行,这样就实现了多任务。

 

虽然有的时候咱们用多任务并发,充分发挥CPU等资源,来达到快速处理的任务的目的,可是是否是并发量越大越快呢,答案显然是不对的。

        咱们打个比方,假设你正在准备考试,天天晚上须要作语文、数学、英语、物理、化学这5科的做业,每项做业耗时1小时。

         若是你先花1小时作语文做业,作完了,再花1小时作数学做业,这样,依次所有作完,一共花5小时,这种方式称为单任务模型,或者批处理任务模型。

        假设你打算切换到多任务模型,能够先作1分钟语文,再切换到数学做业,作1分钟,再切换到英语,以此类推,只要切换速度足够快,这种方式就和单核CPU执行多任务是同样的,以幼儿园小朋友的眼光来看,你就正在同时写5科做业。

        可是,切换做业是有代价的,好比从语文切到数学,要先收拾桌子上的语文书本、钢笔(这叫保存现场),而后,打开数学课本、找出圆规直尺(这叫准备新环境),才能开始作数学做业。操做系统在切换进程或者线程时也是同样的,它须要先保存当前执行的现场环境(CPU寄存器状态、内存页等),而后,把新任务的执行环境准备好(恢复上次的寄存器状态,切换内存页等),才能开始执行。这个切换过程虽然很快,可是也须要耗费时间。若是有几千个任务同时进行,操做系统可能就主要忙着切换任务,根本没有多少时间去执行任务了,这种状况最多见的就是硬盘狂响,点窗口无反应,系统处于假死状态。

        因此,多任务一旦多到一个限度,就会消耗掉系统全部的资源,结果效率急剧降低,全部任务都作很差。

 

I/O 编程

IO在计算机中指Input/Output,也就是输入和输出。因为程序和运行时数据是在内存中驻留,由CPU这个超快的计算核心来执行,涉及到数据交换的地方,一般是磁盘、网络等,就须要IO接口。

        好比你打开浏览器,访问新浪首页,浏览器这个程序就须要经过网络IO获取新浪的网页。浏览器首先会发送数据给新浪服务器,告诉它我想要首页的HTML,这个动做是往外发数据,叫Output,随后新浪服务器把网页发过来,这个动做是从外面接收数据,叫Input。

        因此,一般,程序完成IO操做会有Input和Output两个数据流。固然也有只用一个的状况,好比,从磁盘读取文件到内存,就只有Input操做,反过来,把数据写到磁盘文件里,就只是一个Output操做。

        IO编程中,Stream(流)是一个很重要的概念,能够把流想象成一个水管,数据就是水管里的水,可是只能单向流动。Input Stream就是数据从外面(磁盘、网络)流进内存,Output Stream就是数据从内存流到外面去。对于浏览网页来讲,浏览器和新浪服务器之间至少须要创建两根水管,才能够既能发数据,又能收数据。

因为CPU和内存的速度远远高于外设的速度,因此,在IO编程中,就存在速度严重不匹配的问题。举个例子来讲,好比要把100M的数据写入磁盘,CPU输出100M的数据只须要0.01秒,但是磁盘要接收这100M数据可能须要10秒,怎么办呢?有两种办法:

        第一种是CPU等着,也就是程序暂停执行后续代码,等100M的数据在10秒后写入磁盘,再接着往下执行,这种模式称为同步IO;

        另外一种方法是CPU不等待,只是告诉磁盘,“您老慢慢写,不着急,我接着干别的事去了”,因而,后续代码能够马上接着执行,这种模式称为异步IO。

        很明显,使用异步IO来编写程序性能会远远高于同步IO,可是异步IO的缺点是编程模型复杂,异步IO的复杂度远远高于同步IO。

        操做IO的能力都是由操做系统提供的,每一种编程语言都会把操做系统提供的低级C接口封装起来方便使用,Python也不例外。

 

计算密集型  VS  I/O密集型

是否采用多任务的第二个考虑是任务的类型。咱们能够把任务分为计算密集型和IO密集型。

        计算密集型任务的特色是要进行大量的计算,消耗CPU资源,好比计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也能够用多任务完成,可是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,因此,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。

计算密集型任务因为主要消耗CPU资源,所以,代码运行效率相当重要。Python这样的脚本语言运行效率很低,彻底不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。

        第二种任务的类型是IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特色是CPU消耗不多,任务的大部分时间都在等待IO操做完成(由于IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,好比Web应用。IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间不多,所以,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,彻底没法提高运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。

 

同步  VS  异步

同步:

同步:就是在发出一个功能调用时,在没有获得结果以前,该调用就不返回。
按照这个定义,其实绝大多数函数都是同步调用(例如sin isdigit等)。
可是通常而言,咱们在说同步、异步的时候,特指那些须要其余部件协做或者须要必定时间完成的任务。
最多见的例子就是 SendMessage。
该函数发送一个消息给某个窗口,在对方处理完消息以前,这个函数不返回。
当对方处理完毕之后,该函数才把消息处理函数所返回的值返回给调用者。

异步的概念和同步相对

当一个异步过程调用发出后,调用者不会马上获得结果。
实际处理这个调用的部件是在调用发出后,
经过状态、通知来通知调用者,或经过回调函数处理这个调用。

        考虑到CPU和IO之间巨大的速度差别,一个任务在执行的过程当中大部分时间都在等待IO操做,单进程单线程模型会致使别的任务没法并行执行,所以,咱们才须要多进程模型或者多线程模型来支持多任务并发执行。

        现代操做系统对IO操做已经作了巨大的改进,最大的特色就是支持异步IO。若是充分利用操做系统提供的异步IO支持,就能够用单进程单线程模型来执行多任务,这种全新的模型称为事件驱动模型,Nginx就是支持异步IO的Web服务器,它在单核CPU上采用单进程模型就能够高效地支持多任务。在多核CPU上,能够运行多个进程(数量与CPU核心数相同),充分利用多核CPU。因为系统总的进程数量十分有限,所以操做系统调度很是高效。用异步IO编程模型来实现多任务是一个主要的趋势。

        对应到Python语言,单进程的异步编程模型称为协程,有了协程的支持,就能够基于事件驱动编写高效的多任务程序。

 

阻塞  VS  非阻塞

阻塞/非阻塞, 它们是程序在等待消息(无所谓同步或者异步)时的状态。

阻塞

阻塞调用是指调用结果返回以前,当前线程会被挂起。函数只有在获得结果以后才会返回。
有人也许会把阻塞调用和同步调用等同起来,实际上他是不一样的。
对于同步调用来讲,不少时候当前线程仍是激活的,只是从逻辑上当前函数没有返回而已。

socket接收数据函数recv是一个阻塞调用的例子。
当socket工做在阻塞模式的时候, 若是没有数据的状况下调用该函数,则当前线程就会被挂起,直到有数据为止。

非阻塞

非阻塞和阻塞的概念相对应,指在不能马上获得结果以前,该函数不会阻塞当前线程,而会马上返回。

 

易混淆的点:

不少人也会把异步和非阻塞混淆,
由于异步操做通常都不会在真正的IO 操做处被阻塞,
好比若是用select 函数,当select 返回可读时再去read 通常都不会被阻塞
就比如当你的号码排到时通常都是在你以前已经没有人了,因此你再去柜台办理业务就不会被阻塞.
可见,同步/异步与阻塞/非阻塞是两组不一样的概念,它们能够共存组合,
而不少人之因此把同步和阻塞混淆,我想也是由于没有区分这两个概念,
好比阻塞的read/write 操做中,实际上是把消息通知和处理消息结合在了一块儿,
在这里所关注的消息就是fd 是否可读/写,而处理消息则是对fd 读/写.
当咱们将这个fd 设置为非阻塞的时候,read/write 操做就不会在等待消息通知这里阻塞,
若是fd 不可读/写则操做当即返回.


前方高能 非战斗人员迅速撤离 。。。。。

image

同步/异步与阻塞/非阻塞的组合分析
_____|_____阻塞_______|_____非阻塞_____
同步 |     同步阻塞     |       同步非阻塞
异步 |     异步阻塞     |       异步非阻塞

同步阻塞形式:

  效率是最低的,
  拿上面的例子来讲,就是你专心排队,什么别的事都不作。
  实际程序中
  就是未对fd 设置O_NONBLOCK 标志位的read/write 操做,

 

异步阻塞形式:

  若是在银行等待办理业务的人采用的是异步的方式去等待消息被触发,也就是领了一张小纸条,
  假如在这段时间里他不能离开银行作其它的事情,那么很显然,这我的被阻塞在了这个等待的操做上面;
  异步操做是能够被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息被触发时被阻塞.
  好比select 函数,
  假如传入的最后一个timeout 参数为NULL,那么若是所关注的事件没有一个被触发,
  程序就会一直阻塞在这个select 调用处.


同步非阻塞形式:

其实是效率低下的,
  想象一下你一边打着电话一边还须要抬头看到底队伍排到你了没有,
  若是把打电话和观察排队的位置当作是程序的两个操做的话,
  这个程序须要在这两种不一样的行为之间来回的切换,效率可想而知是低下的;
  不少人会写阻塞的read/write 操做,
  可是别忘了能够对fd 设置O_NONBLOCK 标志位,这样就能够将同步操做变成非阻塞的了;

 

异步非阻塞形式:

效率更高,
  由于打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,
  程序没有在两种不一样的操做中来回切换.
  好比说,这我的忽然发觉本身烟瘾犯了,须要出去抽根烟,
  因而他告诉大堂经理说,排到我这个号码的时候麻烦到外面通知我一下(注册一个回调函数),
  那么他就没有被阻塞在这个等待的操做上面,天然这个就是异步+非阻塞的方式了.
  若是使用异步非阻塞的状况,
  好比aio_*组的操做,当发起一个aio_read 操做时,函数会立刻返回不会被阻塞,
  当所关注的事件被触发时会调用以前注册的回调函数进行处理,

 

 

Python多线程、多进程实现:http://www.cnblogs.com/hellojesson/p/6397173.html

Python操做异步IO\队列\缓存:http://www.cnblogs.com/alex3714/articles/5248247.html

IO多路复用的几种实现机制的分析: http://blog.csdn.net/zhang_shuai_2011/article/details/7675797

知乎关于I/O多路复用的解答:https://www.zhihu.com/question/32163005

 

参考资料:http://www.jianshu.com/p/7ce30a806c51

              https://my.oschina.net/cnyinlinux/blog/422207

相关文章
相关标签/搜索