假期(进程、线程、协程)

"""
理论基础:
    一、操作系统的作用
        1:隐藏丑陋复杂的硬件接口,提供良好的抽象接口
        2:管理、调度进程,并且将多个进程对硬件的竞争变得有序化
    二、多道技术
        1:产生背景:针对单核实现并发
            PS:
                现在得主机一般是多核,那么每个核都会利用多道技术
                有4个CPU,运行于cup1中的某个程序遇到IO阻塞,会等到IO结束再重新调度,会被调度到4个
                cpu中的任意一个,具体由操作系统调度算法决定
        2:空间上的复用:如内存中同时有多道程序
        3:时间上的复用:复用一个CPU的时间片
            强调:遇到IO切,占用CPU时间过长也切,核心在于切之前可以将进程的状态保存下来,这样才能保证下次可以基于上次切走的位置继续运行

一、python并发编程之多进程
    进程:正在进行的一个过程或者说一个任务,而负责执行任务的则是CPU
        eg:(单核+多道,实现多个进程的并发执行)
    进程与程序的区别:程序仅仅是一段代码,而进程指的是程序的运行过程
        - 强调:同一程序执行两次,那也是两个进程;比如登陆QQ,一个登陆大号,一个登陆小号
    并发与并行:无论是并发还是并行,在用户看来都是‘同时’进行的,不管是进程还是线程,都只是一个任务,真正干活的是CPU,一个cup同一时间只能干一个活
        一:并发:是伪并行,即看起来同时运行。单个CPU+多道技术就可以实现并发(并行也属于并发)
        二:并行:即同时运行,只有具备多个CPU才能实现并行(多道技术是针对于单核而言的)
    同步/异步and阻塞/非阻塞:
        同步:所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不会返回。
        异步:异步和同步的概念相对,就是当一个异步功能调用发出后,调用者不能立刻得到结果。
        阻塞:阻塞调用指的是调用结果返回之前,当前的线程就会被挂起(遇到IO阻塞)。
        非阻塞:非阻塞
    进程的创建:
        比如说双击快捷方式;子进程的创建···
    进程的终止:
        1、正常退出
        2、出错退出(自愿)
        3、严重错误(非自愿)
        4、被其它进程杀死(比如电脑管家杀死360卫士)
    进程的层次结构:
        - 无论是Unix系统还是windows系统,进程只有一个父进程,但是它们还有一点稍微的差别*(这个没必要深究,想了解的自己百度看看)
    进程的状态:
        - :运行、阻塞、就绪
            1、进程为等待输入而进入阻塞状态
            2、调度程序选择另一个进程
            3、调度程序选择这个进程
            4、出现有效输入
    进程的并发实现:
        - 进程并发的实现在于,硬件中断一个正在运行的进程,把此时进程运行的所有状态保存下来,为此,操作系统维护一张进程表,每个进程占衣蛾表格
          该表存放了进程状态的重要信息:程序计数器、堆栈指针、内存分配状况、所有打开文件的状态、账号和调度信息,以及其他在进程由运行太转为就绪态或者
          阻塞状态时,必须保存的信息,从而保证该进程在再次启动时,就象从未中断过一样

    代码实现:
        multiprocessing模块介绍:
            1、python中的多线程无法利用多核优势,如果想要充分地使用多核CPU资源,在python中大部分情况需要使用多进程,python为我们提供了multiprocessing模块
            2、multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。
            3、multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process,Queue,Pipe,Lock等组件
            - 需要强调的是:与线程不同,进程没有任何的共享状态,进程修改的数据,改动仅限于该进程内
        Process类的介绍:
            - 创建进程前实例化一个对象p = Process()
                1、需要使用关键字的方式来指定参数
                2、args指定的为传给target函数的位置参数,是一个元组形式,必须加逗号
                - 参数介绍:
                    1、group参数未使用,值始终未None
                    2、target表示调用对象,即子进程要执行的任务
                    3、args表示调用对象的位置参数元组,args=(1,2,'egon',)
                    4、kwargs表示调用对象的字典,kwargs={'name'='egon','age':18}
                    5、name为子进程的名字
                - 方法介绍:
                    1、p.start():启动进程,并调用子进程中的p.run()
                    2、p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
                    3、p.terminate():强制终止进程p,不会进行任何的清理操作,如果p创建了子进程,那么该子进程就会变为僵尸进程,使用该方法需要特别注意。如果p有锁未被释放会导致死锁
                    4、p.is_alive():如果p任然在运行,返回True
                    5、p.join([timeout]):主线程等待p终止,timeout可以是超时时间,需要强调的是p.join()只能join住start开启的进程,而不能join住run开启的进程
                - 属性介绍:
                    1、p.daemon():默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
                    2、p.name():进程的名称
                    3、p.pid():进程的pid
                    4、p.exitcode:进程在运行的时候为None,如果为-N,表示被信号N结束了
                    5、p.authkey():进程的身份验证键,默认有os.urandom()生成的32位字符串
        Process类的使用:
            *:在Windows中Process()必须放到if__name__ == '__main__'下
            创建并开启子进程的两种方式:
                一、首先实例化p = Process类,传入参数(要执行的函数,参数);
                    p.start()    #子进程开启
                二、定义一个类,继承Process类,在这个类里面实现run方法;
                    实例化这个类,p.start()     #子进程开启
            *:进程间的内存空间时隔离的
        守护进程:
            主进程创建守护进程
                其一、守护进程会在主进程代码执行结束后就终止
                其二、守护进程内无法开启子进程
            *:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
        进程同步(锁):
            - 进程之间数据不共享,但是共享一套文件系统,所以访问同一个文件,或者同一个打印终端,是没有问题的,
              而带来的就是竞争,竞争的结果就是错乱,如何控制,就是加锁处理
                1、多个进程共享同一个打印终端
                2、多个进程共享同一个文件(文件作为数据库模拟抢票)
                - 加锁虽然抱枕了数据安全,但是使得代码变成了串行
        队列:
            *进程之间彼此相互隔离,要实现进程之间的通信,multiprocessing模块支持两种形式:基于队列和管道  --都是基于消息实现的
            - 创建队列的类(底层就是以管道和加锁的方式实现)
                Queue([maxsize]):创建共享的进程队列,Queue是多进程的安全队列,可以使用queue实现多精彩之间的数据传递
            - 参数介绍:
                maxsize是队列中允许最大项树,默认五大小限制
            - 方法介绍:
                q.put()方法用作插入数据到队列中,put方法还有连个可选参数:blocked和timeout
                q.get()方法可以从队列读取一个值并且删除一个元素,两个可选参数blocked和timeout
                q.get_nowait():同q.get(False)
                q.put_nowait():同q.put(False)
                q.empty():如果q为空则返回True
                q.full():如果q满了则返回True
                q.qsize():返回队列q中的数量
            - 生产者消费者模型:
                程序中的两类角色:
                    1、负责生产数据(生产者)
                    2、负责处理数据(消费者)
                引入生产者消费者模型为了解决的问题是:
                    平衡生产者与消费者之间的工作能力,从而提高了程序整体的处理数据速度
                如何实现:
                    生产者<-->队列<-->消费者   (生产者消费者模型实现了类程序的解耦合)
                    此时的问题是主进程永远不会结束(生产者生产完就结束了,但是消费者会卡在get这一步,解决的办法是在生产者生产结束后发一个信号)
                - 操作共享数据
        - 信号量
        - 事件
        - 进程池
            - 创建进程池的类:如果指定multiprocessing为3,则进程池中从无到有总共三个进程,然后自始至终只使用这三个进程去执行任务,不会开启其它进程
            - 方法介绍:
                1、p.apple(函数,参数)
                2、p.close() 关闭进程池,防止进一步的操作,如果所有的操作持续挂起,他们将在工作进程终止前完成
                3、p.join():等待所有工作进程退出,该方法只能在close或者teminate之后调用
            - 回调函数:
                ···

二、python并发编程之多线程
    什么是线程:
        - 在操作系统当中,每个进程都有一个地址空间,而且默认就有一个控制线程
        - 进程只是用来把资源集中到一起(进程只是一个资源单位,或者资源集合),而线程才是CPU上的执行单位
        多线程:
            - 在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间;
    创建线程的开销更小:进程之间是竞争关系,线程之间是协作关系
    为何要用多线程:
        - 多线程指的是在一个进程中开启多个线程:
            1、多线程共享一个进程的地址空间
            2、线程比进程更加轻量级,线程比进程更容易创建撤销
            3、如果多个线程之间是CPU密集型的,那么并不能获到性能上的增强,多线程应用在IO密集型任务
            4、在CPU系统中,为了最大限度的利用多核,可以开启多个线程
    多线程的应用举例:
        - 开启一个文字处理的软件进程,该进程肯定需要办不止一件事,比如监听键盘输入,处理文字,定时刷数据到硬盘···
    内核线程与用户级线程:
        内核线程的优缺点:
            - 优点:当有多个处理机时,一个进程的多个线程可以同时执行;
            - 缺点:由内核进行调度
        用户进程的优缺点:
            - 优点:
                1、线程的调度不需要内核直接参与,控制简单
                2、可以在不支持线程的操作系统中实现
                3、创建和销毁线程、线程切换代价等线程管理的代价比内核线程少得多
                4、允许每个进程定制自己的调度算法、线程管理比较灵活
                5、线程能够利用的表空间和堆栈空间比内核级线程多
                6、同一进程中只能同时有一个线程在运行,如果有一个线程使用了系统的调用而阻塞,那么整个进程都会被挂起。另外,页面失效也会产生同样的问题。
            - 缺点:
                资源调度按照进程进行,多个处理机下,同一个进程中的线程只能在同一个处理机下分时复用
    混合实现:用户级与内核级的多路复用,内核同一调度内核线程,每个内核线程对应n个用户线程

    代码实现:
        -threading模块介绍
            threading模块和multiprocessing模块实现了相同的接口,二者在使用层面,有很大的相似性
        - 开启线程的两种方式
            - 和进程完全相同,线实例化对象,然后t.start()
        - 统一进程开启多个线程与统一进程开启多个子进程的区别
            1、开启速度谁快谁满
            2、愁一愁pid就知道了
            3、数据是否共享
        - 其它相关方法
            Thread实例对象的 方法
                isAlive:返回线程是否活动
                getName:返回线程名
                setNmae:设置线程名
            一些方法:
                1、threading.currentThread:返回当前的线程变量
                2、threading.enumerate:返回一个包含正在运行线程的list
                3、threading.activeCount:返回正在运行的线程的数量
            守护线程:
                无论是进程还是线程,都遵循:守护线程会等到住线程运行完毕后被销毁(运行完毕并非终止运行)
            python 的GIL锁:
            同步锁:
            死锁与递归锁:
            信号量semaphore
            Event:
            条件condition
            定时器:
            线程queue:
            concurrent.futures模块





三、python并发编程之协程
    基于单线程实现并发,即只用一个主线程的情况下实现并发
        并发的本质:切换+保存状态
        CPU在运行任务的时候会在两种情况下切走去执行其他任务
            1、该任务发生了阻塞
            2、该任务计算的时间过长或者有一个优先级更高的程序替代了它
            - yield可以保存状态,但是yield是代码级别控制的,更加轻量级
            - 单纯的切换反而会降低运行的效率
            - yield并不能实现遇到IO切换
        协程的本质:在单线程下,由用户自己控制一个任务遇到IO阻塞了就切换另一个任务去执行,从此来提升效率
    协程的介绍:
        协程:是单线程下的并发,又称为微线程,纤程。
            - 协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的
                1、python的线程属于内核级别的,即由操作系统控制调度
                2、单线程内开启协程,一旦遇到IO,就会从应用程序级别控制切换,以此来提升效率
            - 对比操作系统控制线程的切换,用户在单线程内控制协程的切换
                优点:
                    1、线程的开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
                    2、单线程内就可以实现并发的效果,最大限度的利用CPU
                缺点:
                    1、协程的本质就是单线程先,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
                    2、协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
    协程的特点:
        1、必须在只有一个单线程里实现并发
        2、修改共享数据不需要加锁
        3、用户程序里自己保存多个控制流的上下文栈
        4、FZ:一个协程遇到IO操作自动切换到其它协程

    代码实现:
        - Greenlet模块
            如果我们在单个线程内有20多个任务,要想实现任务的切换,使用yield过于麻烦,使用greenlet模块会很方便
        _ Gevent 介绍
            - from gevent import monkey;monkey.patch_all()
            gevent是第三方库,可以轻松的通过gevent实现并发同步或者是异步编程
            用法:g.saawn(function,tarts)
            遇到IO阻塞会自动切换任务




四、python并发编程之IO模型
    IO模型介绍:
        - 同步,异步,阻塞,非阻塞
    同步:
        -
    异步:
        - 非阻塞IO中,用户进程其实是需要不断的主动询问内核数据准备好了没有
    阻塞:
        - 阻塞IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block掉了
    非阻塞:
        - 非阻塞IO中,用户进程其实是需要不断的主动询问内核数据准备好了没有
    多路复用IO:
        - 如果处理的连接数不是很高的话,使用select/epoll 的web servre 不一定比使用multi-threading+blocking IO的webserver性能更好,
          可能延迟还更大。select/epoll 的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接
        - 在多路复用模型中,对于每一个socket,一般都设置为非阻塞,但是非阻塞整个用户的进程一直都在被阻塞,只不过process是被select
          函数阻塞,而不是被socket IO给 阻塞
              - 结论:select的优势在于可以处理多个连接,不适用于单个连接
    - selector模块
        - select,poll,epoll
            - 这三种IO多路复用模型在不同的平台有着不同的支持,而epoll在Windows下就不支持,好在我们有selector模块,帮助我们默认选择当前平台下最合适的

例子可以用网络编程的socket客户端服务端测试


"""
原文地址:https://www.cnblogs.com/52-qq/p/8450282.html