• Python自动化开发学习的第九周----线程、进程、协程


    一、计算机操作系统的简介

    手工操作(无操作系统)

    1946年第一台计算机诞生--20世纪50年代中期,还未出现操作系统,计算机工作采用手工操作方式。

    程序员将对应于程序和数据的已穿孔的纸带(或卡片)装入输入机,然后启动输入机把程序和数据输入计算机内存,接着通过控制台开关启动程序针对数据运行;计算完毕,打印机输出计算结果;用户取走结果并卸下纸带(或卡片)后,才让下一个用户上机。

    手工操作方式两个特点:
    (1)用户独占全机。不会出现因资源已被其他用户占用而等待的现象,但资源的利用率低。
    (2)CPU 等待手工操作。CPU的利用不充分。

    20世纪50年代后期,出现人机矛盾:手工操作的慢速度和计算机的高速度之间形成了尖锐矛盾,手工操作方式已严重损害了系统资源的利用率(使资源利用率降为百分之几,甚至更低),不能容忍。唯一的解决办法:只有摆脱人的手工操作,实现作业的自动过渡。这样就出现了成批处理。

    批处理系统

    批处理系统:加载在计算机上的一个系统软件,在它的控制下,计算机能够自动地、成批地处理一个或多个用户的作业(这作业包括程序、数据和命令)。

    联机批处理系统

    首先出现的是联机批处理系统,即作业的输入/输出由CPU来处理。
    主机与输入机之间增加一个存储设备——磁带,在运行于主机上的监督程序的自动控制下,计算机可自动完成:成批地把输入机上的用户作业读入磁带,依次把磁带上的用户作业读入主机内存并执行并把计算结果向输出机输出。完成了上一批作业后,监督程序又从输入机上输入另一批作业,保存在磁带上,并按上述步骤重复处理。

    监督程序不停地处理各个作业,从而实现了作业到作业的自动转接,减少了作业建立时间和手工操作时间,有效克服了人机矛盾,提高了计算机的利用率。

    但是,在作业输入和结果输出时,主机的高速CPU仍处于空闲状态,等待慢速的输入/输出设备完成工作: 主机处于“忙等”状态。 

    脱机批处理系统

    为克服与缓解高速主机与慢速外设的矛盾,提高CPU的利用率,又引入了脱机批处理系统,即输入/输出脱离主机控制。
    这种方式的显著特征是:增加一台不与主机直接相连而专门用于与输入/输出设备打交道的卫星机。
    其功能是:
    (1)从输入机上读取用户作业并放到输入磁带上。
    (2)从输出磁带上读取执行结果并传给输出机。

    这样,主机不是直接与慢速的输入/输出设备打交道,而是与速度相对较快的磁带机发生关系,有效缓解了主机与设备的矛盾。主机与卫星机可并行工作,二者分工明确,可以充分发挥主机的高速计算能力。

    脱机批处理系统:20世纪60年代应用十分广泛,它极大缓解了人机矛盾及主机与外设的矛盾。IBM-7090/7094:配备的监督程序就是脱机批处理系统,是现代操作系统的原型。

    不足:每次主机内存中仅存放一道作业,每当它运行期间发出输入/输出(I/O)请求后,高速的CPU便处于等待低速的I/O完成状态,致使CPU空闲。

    为改善CPU的利用率,又引入了多道程序系统。

    多道程序系统

    多道程序设计技术

    所谓多道程序设计技术,就是指允许多个程序同时进入内存并运行。即同时把多个程序放入内存,并允许它们交替在CPU中运行,它们共享系统中的各种硬、软件资源。当一道程序因I/O请求而暂停运行时,CPU便立即转去运行另一道程序。

    单道程序的运行过程:
    在A程序计算时,I/O空闲, A程序I/O操作时,CPU空闲(B程序也是同样);必须A工作完成后,B才能进入内存中开始工作,两者是串行的,全部完成共需时间=T1+T2。

    多道程序的运行过程:
    将A、B两道程序同时存放在内存中,它们在系统的控制下,可相互穿插、交替地在CPU上运行:当A程序因请求I/O操作而放弃CPU时,B程序就可占用CPU运行,这样 CPU不再空闲,而正进行A I/O操作的I/O设备也不空闲,显然,CPU和I/O设备都处于“忙”状态,大大提高了资源的利用率,从而也提高了系统的效率,A、B全部完成所需时间<<T1+T2。

    多道程序设计技术不仅使CPU得到充分利用,同时改善I/O设备和内存的利用率,从而提高了整个系统的资源利用率和系统吞吐量(单位时间内处理作业(程序)的个数),最终提高了整个系统的效率。

    单处理机系统中多道程序运行时的特点:
    (1)多道:计算机内存中同时存放几道相互独立的程序;
    (2)宏观上并行:同时进入系统的几道程序都处于运行过程中,即它们先后开始了各自的运行,但都未运行完毕;
    (3)微观上串行:实际上,各道程序轮流地用CPU,并交替运行。

    多道程序系统的出现,标志着操作系统渐趋成熟的阶段,先后出现了作业调度管理、处理机管理、存储器管理、外部设备管理、文件系统管理等功能。

    多道批处理系统

    20世纪60年代中期,在前述的批处理系统中,引入多道程序设计技术后形成多道批处理系统(简称:批处理系统)。
    它有两个特点:
    (1)多道:系统内可同时容纳多个作业。这些作业放在外存中,组成一个后备队列,系统按一定的调度原则每次从后备作业队列中选取一个或多个作业进入内存运行,运行作业结束、退出运行和后备作业进入运行均由系统自动实现,从而在系统中形成一个自动转接的、连续的作业流。
    (2)成批:在系统运行过程中,不允许用户与其作业发生交互作用,即:作业一旦进入系统,用户就不能直接干预其作业的运行。

    批处理系统的追求目标:提高系统资源利用率和系统吞吐量,以及作业流程的自动化。

    批处理系统的一个重要缺点:不提供人机交互能力,给用户使用计算机带来不便。
    虽然用户独占全机资源,并且直接控制程序的运行,可以随时了解程序运行情况。但这种工作方式因独占全机造成资源效率极低。

    一种新的追求目标:既能保证计算机效率,又能方便用户使用计算机。 20世纪60年代中期,计算机技术和软件技术的发展使这种追求成为可能。

    分时系统

    由于CPU速度不断提高和采用分时技术,一台计算机可同时连接多个用户终端,而每个用户可在自己的终端上联机使用计算机,好象自己独占机器一样。

    分时技术:把处理机的运行时间分成很短的时间片,按时间片轮流把处理机分配给各联机作业使用。

    若某个作业在分配给它的时间片内不能完成其计算,则该作业暂时中断,把处理机让给另一作业使用,等待下一轮时再继续其运行。由于计算机速度很快,作业运行轮转得很快,给每个用户的印象是,好象他独占了一台计算机。而每个用户可以通过自己的终端向系统发出各种操作控制命令,在充分的人机交互情况下,完成作业的运行。

    具有上述特征的计算机系统称为分时系统,它允许多个用户同时联机使用计算机。

    特点:
    (1)多路性。若干个用户同时使用一台计算机。微观上看是各用户轮流使用计算机;宏观上看是各用户并行工作。
    (2)交互性。用户可根据系统对请求的响应结果,进一步向系统提出新的请求。这种能使用户与系统进行人机对话的工作方式,明显地有别于批处理系统,因而,分时系统又被称为交互式系统。
    (3)独立性。用户之间可以相互独立操作,互不干扰。系统保证各用户程序运行的完整性,不会发生相互混淆或破坏现象。
    (4)及时性。系统可对用户的输入及时作出响应。分时系统性能的主要指标之一是响应时间,它是指:从终端发出命令到系统予以应答所需的时间。


    分时系统的主要目标:对用户响应的及时性,即不至于用户等待每一个命令的处理时间过长。

    分时系统可以同时接纳数十个甚至上百个用户,由于内存空间有限,往往采用对换(又称交换)方式的存储方法。即将未“轮到”的作业放入磁盘,一旦“轮到”,再将其调入内存;而时间片用完后,又将作业存回磁盘(俗称“滚进”、“滚出“法),使同一存储区域轮流为多个用户服务。

    多用户分时系统是当今计算机操作系统中最普遍使用的一类操作系统。 

    实时系统

    虽然多道批处理系统和分时系统能获得较令人满意的资源利用率和系统响应时间,但却不能满足实时控制与实时信息处理两个应用领域的需求。于是就产生了实时系统,即系统能够及时响应随机发生的外部事件,并在严格的时间范围内完成对该事件的处理。
    实时系统在一个特定的应用中常作为一种控制设备来使用。

    实时系统可分成两类:
    (1)实时控制系统。当用于飞机飞行、导弹发射等的自动控制时,要求计算机能尽快处理测量系统测得的数据,及时地对飞机或导弹进行控制,或将有关信息通过显示终端提供给决策人员。当用于轧钢、石化等工业生产过程控制时,也要求计算机能及时处理由各类传感器送来的数据,然后控制相应的执行机构。
    (2)实时信息处理系统。当用于预定飞机票、查询有关航班、航线、票价等事宜时,或当用于银行系统、情报检索系统时,都要求计算机能对终端设备发来的服务请求及时予以正确的回答。此类对响应及时性的要求稍弱于第一类。

    实时操作系统的主要特点:
    (1)及时响应。每一个信息接收、分析处理和发送的过程必须在严格的时间限制内完成。
    (2)高可靠性。需采取冗余措施,双机系统前后台工作,也包括必要的保密措施等。 

    操作系统发展图谱

    二、进程和线程的区别

    什么是进程(process)?

    程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。

    程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。

    在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。

    有了进程为什么还要线程?

    进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:

    • 进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。

    • 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。

    例如,我们在使用qq聊天, qq做为一个独立进程如果同一时间只能干一件事,那他如何实现在同一时刻 即能监听键盘输入、又能监听其它人给你发的消息、同时还能把别人发的消息显示在屏幕上呢?你会说,操作系统不是有分时么?但我的亲,分时是指在不同进程间的分时呀, 即操作系统处理一会你的qq任务,又切换到word文档任务上了,每个cpu时间片分给你的qq程序时,你的qq还是只能同时干一件事呀。

    再直白一点, 一个操作系统就像是一个工厂,工厂里面有很多个生产车间,不同的车间生产不同的产品,每个车间就相当于一个进程,且你的工厂又穷,供电不足,同一时间只能给一个车间供电,为了能让所有车间都能同时生产,你的工厂的电工只能给不同的车间分时供电,但是轮到你的qq车间时,发现只有一个干活的工人,结果生产效率极低,为了解决这个问题,应该怎么办呢?。。。。没错,你肯定想到了,就是多加几个工人,让几个人工人并行工作,这每个工人,就是线程!

    什么是线程(thread)?

    线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

    进程和线程的区别?

    1.Threads share the address space of the process that created it; processes have their own address space.

    线程共享创建它的进程的地址空间;进程有自己的地址空间。
    2.Threads have direct access to the data segment of its process; processes have their own copy of the data segment of the parent process.

    线程直接访问进程的数据段;进程拥有父进程的数据段的自身副本。
    3.Threads can directly communicate with other threads of its process; processes must use interprocess communication to communicate with sibling processes.

    线程可以直接与其他线程的过程;过程必须使用进程间通信与兄弟姐妹的过程。
    4.New threads are easily created; new processes require duplication of the parent process.

    很容易创建新线程;新进程需要重复父进程。
    5.Threads can exercise considerable control over threads of the same process; processes can only exercise control over child processes.

    线程可以对相同进程的线程进行相当的控制;进程只能对子进程进行控制。
    6.Changes to the main thread (cancellation, priority change, etc.) may affect the behavior of the other threads of the process; changes to the parent process does not affect child processes.

    对主线程的更改(取消、优先级更改等)可能影响进程的其他线程的行为;对父进程的更改不会影响子进程。

    Python GIL(Global Interpreter Lock)【Python GIL 全局解释器锁】

    In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

    上面的核心意思就是,无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行,擦。。。,那这还叫什么多线程呀?莫如此早的下结结论,听我现场讲。

    首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL

    这篇文章透彻的剖析了GIL对python多线程的影响,强烈推荐看一下:http://www.dabeaz.com/python/UnderstandingGIL.pdf 

    Python threading模块

    线程有2种调用方式,如下:

     1 import threading
     2 import time
     3  
     4 def sayhi(num): #定义每个线程要运行的函数
     5  
     6     print("running on number:%s" %num)
     7  
     8     time.sleep(3)
     9  
    10 if __name__ == '__main__':
    11  
    12     t1 = threading.Thread(target=sayhi,args=(1,)) #生成一个线程实例
    13     t2 = threading.Thread(target=sayhi,args=(2,)) #生成另一个线程实例
    14  
    15     t1.start() #启动线程
    16     t2.start() #启动另一个线程
    17  
    18     print(t1.getName()) #获取线程名
    19     print(t2.getName())
    直接调用 
     1 import threading
     2 import time
     3  
     4  
     5 class MyThread(threading.Thread):
     6     def __init__(self,num):
     7         threading.Thread.__init__(self)
     8         self.num = num
     9  
    10     def run(self):#定义每个线程要运行的函数
    11  
    12         print("running on number:%s" %self.num)
    13  
    14         time.sleep(3)
    15  
    16 if __name__ == '__main__':
    17  
    18     t1 = MyThread(1)
    19     t2 = MyThread(2)
    20     t1.start()
    21     t2.start()
    继承式调用

    Join & Daemon

    一些线程做后台任务,如发送实时数据包,或进行定期的垃圾收集,或什么的。只有当主程序运行时,这些才有用,一旦另一个非守护进程线程退出,就可以把它们杀掉。

    没有守护线程,您必须跟踪它们,并告诉它们在程序完全退出之前退出。通过将它们设置为守护线程,可以让它们运行并忘记它们,并且当程序退出时,任何守护线程都会自动终止。

     1 #_*_coding:utf-8_*_
     2 __author__ = 'Alex Li'
     3  
     4 import time
     5 import threading
     6  
     7  
     8 def run(n):
     9  
    10     print('[%s]------running----
    ' % n)
    11     time.sleep(2)
    12     print('--done--')
    13  
    14 def main():
    15     for i in range(5):
    16         t = threading.Thread(target=run,args=[i,])
    17         t.start()
    18         t.join(1)
    19         print('starting thread', t.getName())
    20  
    21  
    22 m = threading.Thread(target=main,args=[])
    23 m.setDaemon(True) #将main线程设置为Daemon线程,它做为程序主线程的守护线程,当主线程退出时,m线程也会退出,由m启动的其它子线程会同时退出,不管是否执行完任务
    24 m.start()
    25 m.join(timeout=2)
    26 print("---main thread done----")
    Join & Daemon 

    线程锁(互斥锁Mutex)

    一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况?

    import time
    import threading
     
    def addNum():
        global num #在每个线程中都获取这个全局变量
        print('--get num:',num )
        time.sleep(1)
        num  -=1 #对此公共变量进行-1操作
     
    num = 100  #设定一个共享变量
    thread_list = []
    for i in range(100):
        t = threading.Thread(target=addNum)
        t.start()
        thread_list.append(t)
     
    for t in thread_list: #等待所有线程执行完毕
        t.join()
     
     
    print('final num:', num )
    

    正常来讲,这个num结果应该是0, 但在python 2.7上多运行几次,会发现,最后打印出来的num结果不总是0,为什么每次运行的结果不一样呢? 哈,很简单,假设你有A,B两个线程,此时都 要对num 进行减1操作, 由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了num=100这个初始变量交给cpu去运算,当A线程去处完的结果是99,但此时B线程运算完的结果也是99,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是99。那怎么办呢? 很简单,每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。 

    *注:不要在3.x上运行,不知为什么,3.x上的结果总是正确的,可能是自动加了锁

    import time
    import threading
     
    def addNum():
        global num #在每个线程中都获取这个全局变量
        print('--get num:',num )
        time.sleep(1)
        lock.acquire() #修改数据前加锁
        num  -=1 #对此公共变量进行-1操作
        lock.release() #修改后释放
     
    num = 100  #设定一个共享变量
    thread_list = []
    lock = threading.Lock() #生成全局锁
    for i in range(100):
        t = threading.Thread(target=addNum)
        t.start()
        thread_list.append(t)
     
    for t in thread_list: #等待所有线程执行完毕
        t.join()
     
    print('final num:', num )
    

      

    GIL VS Lock 

    机智的同学可能会问到这个问题,就是既然你之前说过了,Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock? 注意啦,这里的lock是用户级的lock,跟那个GIL没关系 ,具体我们通过下图来看一下+配合我现场讲给大家,就明白了。

    那你又问了, 既然用户程序已经自己有锁了,那为什么C python还需要GIL呢?加入GIL主要的原因是为了降低程序的开发的复杂度,比如现在的你写python不需要关心内存回收的问题,因为Python解释器帮你自动定期进行内存回收,你可以理解为python解释器里有一个独立的线程,每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,假设你的线程删除了一个变量,py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,结果就有可能新赋值的数据被删除了,为了解决类似的问题,python解释器简单粗暴的加了锁,即当一个线程运行时,其它人都不能动,这样就解决了上述的问题,  这可以说是Python早期版本的遗留问题。

    RLock(递归锁)

    就是在一个大锁中还要再包含子锁

    import threading,time
     
    def run1():
        print("grab the first part data")
        lock.acquire()
        global num
        num +=1
        lock.release()
        return num
    def run2():
        print("grab the second part data")
        lock.acquire()
        global  num2
        num2+=1
        lock.release()
        return num2
    def run3():
        lock.acquire()
        res = run1()
        print('--------between run1 and run2-----')
        res2 = run2()
        lock.release()
        print(res,res2)
     
     
    if __name__ == '__main__':
     
        num,num2 = 0,0
        lock = threading.RLock()
        for i in range(10):
            t = threading.Thread(target=run3)
            t.start()
     
    while threading.active_count() != 1:
        print(threading.active_count())
    else:
        print('----all threads done---')
        print(num,num2)

    Semaphore(信号量)

    互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。

    import threading,time
     
    def run(n):
        semaphore.acquire()
        time.sleep(1)
        print("run the thread: %s
    " %n)
        semaphore.release()
     
    if __name__ == '__main__':
     
        num= 0
        semaphore  = threading.BoundedSemaphore(5) #最多允许5个线程同时运行
        for i in range(20):
            t = threading.Thread(target=run,args=(i,))
            t.start()
     
    while threading.active_count() != 1:
        pass #print threading.active_count()
    else:
        print('----all threads done---')
        print(num)
    

    Events(事件)

    事件是一个简单的同步对象;
    该事件表示内部标志和线程。
    可以等待标志设置,或设置或清除标志本身。
    事件= event()线程。
    #客户端线程可以等待标志被设置
    wait()事件。
    #服务器线程可以设置或重置
    set()事件。
    clear()事件。
    如果设置了标志,则等待方法不执行任何操作。
    如果标志已清除,等待将阻塞,直到它再次设置。
    任意数量的线程都可能等待相同的事件。

    通过Event来实现两个或多个线程间的交互,下面是一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则。

     1 import threading,time
     2 import random
     3 def light():
     4     if not event.isSet():
     5         event.set() #wait就不阻塞 #绿灯状态
     6     count = 0
     7     while True:
     8         if count < 10:
     9             print('33[42;1m--green light on---33[0m')
    10         elif count <13:
    11             print('33[43;1m--yellow light on---33[0m')
    12         elif count <20:
    13             if event.isSet():
    14                 event.clear()
    15             print('33[41;1m--red light on---33[0m')
    16         else:
    17             count = 0
    18             event.set() #打开绿灯
    19         time.sleep(1)
    20         count +=1
    21 def car(n):
    22     while 1:
    23         time.sleep(random.randrange(10))
    24         if  event.isSet(): #绿灯
    25             print("car [%s] is running.." % n)
    26         else:
    27             print("car [%s] is waiting for the red light.." %n)
    28 if __name__ == '__main__':
    29     event = threading.Event()
    30     Light = threading.Thread(target=light)
    31     Light.start()
    32     for i in range(3):
    33         t = threading.Thread(target=car,args=(i,))
    34         t.start()
    红绿灯

    这里还有一个event使用的例子,员工进公司门要刷卡, 我们这里设置一个线程是“门”, 再设置几个线程为“员工”,员工看到门没打开,就刷卡,刷完卡,门开了,员工就可以通过。

     1 #_*_coding:utf-8_*_
     2 __author__ = 'Alex Li'
     3 import threading
     4 import time
     5 import random
     6 
     7 def door():
     8     door_open_time_counter = 0
     9     while True:
    10         if door_swiping_event.is_set():
    11             print("33[32;1mdoor opening....33[0m")
    12             door_open_time_counter +=1
    13 
    14         else:
    15             print("33[31;1mdoor closed...., swipe to open.33[0m")
    16             door_open_time_counter = 0 #清空计时器
    17             door_swiping_event.wait()
    18 
    19 
    20         if door_open_time_counter > 3:#门开了已经3s了,该关了
    21             door_swiping_event.clear()
    22 
    23         time.sleep(0.5)
    24 
    25 
    26 def staff(n):
    27 
    28     print("staff [%s] is comming..." % n )
    29     while True:
    30         if door_swiping_event.is_set():
    31             print("33[34;1mdoor is opened, passing.....33[0m")
    32             break
    33         else:
    34             print("staff [%s] sees door got closed, swipping the card....." % n)
    35             print(door_swiping_event.set())
    36             door_swiping_event.set()
    37             print("after set ",door_swiping_event.set())
    38         time.sleep(0.5)
    39 door_swiping_event  = threading.Event() #设置事件
    40 
    41 
    42 door_thread = threading.Thread(target=door)
    43 door_thread.start()
    44 
    45 
    46 
    47 for i in range(5):
    48     p = threading.Thread(target=staff,args=(i,))
    49     time.sleep(random.randrange(3))
    50     p.start()
    View Code

    Queue队列

    队列在线程编程中特别有用,因为信息必须在多线程之间安全交换。

    class queue.Queue(maxsize=0) #先入先出
    class queue.LifoQueue(maxsize=0) #last in fisrt out 
    class queue.PriorityQueue(maxsize=0) #存储数据时可设置优先级的队列

    优先级队列的构造函数。为是集上,可以放置在队列中的项的数量极限上限整数。一旦达到这个大小,插入将阻塞,直到队列项被消耗。如果不能小于或等于零,队列的大小是无限的。

    首先检索最低值条目(最低值条目是按排序(列表(条目))[ 0 ]返回的条目。作品的典型模式是在形成一个元组:(priority_number,数据)。

    exception queue.Empty

    异常时引发的非阻塞get()(或get_nowait())被一个队列对象是空的。

    exception queue.Full
    异常时引发的非阻塞put()(或put_nowait())被一个队列对象是全。

    Queue.qsize()
    Queue.empty() #return True if empty  
    Queue.full() # return True if full 
    Queue.put(itemblock=Truetimeout=None)


    把项目放入队列中。如果可选的参数块是真实和超时没有(默认),如果有必要,直到块空闲时隙可用。如果超时是一个正数,它最多会阻塞超时时间,如果没有空闲空闲时间,就会引发完全异常。否则(块为false),如果立即可用空闲槽,将一个项放在队列上,否则引发完整的异常(在这种情况下忽略超时)。

    Queue.put_nowait(item)等同于put(item, False).。

    Queue.get(block=Truetimeout=None)
    从队列中删除并返回一个项。如果可选的参数块是真实和超时没有(默认),如果有必要,直到项目块是可用的。如果超时是一个正数,它最多会阻塞超时时间,如果在该时间内没有可用项,则会引发空的异常。否则(块为false),如果立即可用,返回一个项,否则引发空异常(在这种情况下忽略超时)。

    Queue.get_nowait()等于 get(False).

    两方法提供给支持跟踪是否入队的任务已经由后台消费者线程完全处理。

    Queue.task_done()
    表明以前排队任务完成。队列消费线程使用。每个get()用于获取任务,随后打电话告诉task_done()队列的任务处理完成。

    如果一个join()目前阻塞,它将恢复在已处理所有的项目(即task_done()收到电话每项已put()插队)。

    引发ValueError如果有比放在队列中的项的次数。

    Queue.join() block直到queue被消费完毕。

    生产者消费者模型

    在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。

    为什么要使用生产者和消费者模式

    在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

    什么是生产者消费者模式

    生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

    下面来学习一个最基本的生产者消费者模型的例子

    import threading
    import queue
     
    def producer():
        for i in range(10):
            q.put("骨头 %s" % i )
     
        print("开始等待所有的骨头被取走...")
        q.join()
        print("所有的骨头被取完了...")
     
     
    def consumer(n):
     
        while q.qsize() >0:
     
            print("%s 取到" %n  , q.get())
            q.task_done() #告知这个任务执行完了
     
     
    q = queue.Queue()
     
     
     
    p = threading.Thread(target=producer,)
    p.start()
     
    c1 = consumer("李闯")
    

      

    import time,random
    import queue,threading
    q = queue.Queue()
    def Producer(name):
      count = 0
      while count <20:
        time.sleep(random.randrange(3))
        q.put(count)
        print('Producer %s has produced %s baozi..' %(name, count))
        count +=1
    def Consumer(name):
      count = 0
      while count <20:
        time.sleep(random.randrange(4))
        if not q.empty():
            data = q.get()
            print(data)
            print('33[32;1mConsumer %s has eat %s baozi...33[0m' %(name, data))
        else:
            print("-----no baozi anymore----")
        count +=1
    p1 = threading.Thread(target=Producer, args=('A',))
    c1 = threading.Thread(target=Consumer, args=('B',))
    p1.start()
    c1.start()
    

    三、多进程multiprocessing

    多进程使用场景和基本语法

    多进程模块是一个支持大量生产过程使用一个API类似于线程模块。

    多进程提供了本地和远程的并发性,有效地利用子进程而不是线程侧步全局解释器锁。因此,多处理器模块允许程序员充分利用多处理器机器上的优势。它在UNIX和Windows上运行。

    from multiprocessing import Process
    import time
    def f(name):
        time.sleep(2)
        print('hello', name)
     
    if __name__ == '__main__':
        p = Process(target=f, args=('bob',))
        p.start()
        p.join()
    

    为了显示所涉及的各个进程ID,这里是一个扩展示例:

    from multiprocessing import Process
    import os
     
    def info(title):
        print(title)
        print('module name:', __name__)
        print('parent process:', os.getppid())
        print('process id:', os.getpid())
        print("
    
    ")
     
    def f(name):
        info('33[31;1mfunction f33[0m')
        print('hello', name)
     
    if __name__ == '__main__':
        info('33[32;1mmain process line33[0m')
        p = Process(target=f, args=('bob',))
        p.start()
        p.join()
    

    进程间的数据交互和共享

    不同进程间内存是不共享的,要想实现两个进程间的数据交换,可以用以下方法:

    Queues

    使用方法跟threading里的queue差不多

    from multiprocessing import Process, Queue
     
    def f(q):
        q.put([42, None, 'hello'])
     
    if __name__ == '__main__':
        q = Queue()
        p = Process(target=f, args=(q,))
        p.start()
        print(q.get())    # prints "[42, None, 'hello']"
        p.join()
    

    Pipes

    The Pipe() function returns a pair of connection objects connected by a pipe which by default is duplex (two-way). For example:

    from multiprocessing import Process, Pipe
     
    def f(conn):
        conn.send([42, None, 'hello'])
        conn.close()
     
    if __name__ == '__main__':
        parent_conn, child_conn = Pipe()
        p = Process(target=f, args=(child_conn,))
        p.start()
        print(parent_conn.recv())   # prints "[42, None, 'hello']"
        p.join()
    

    Managers

    A manager object returned by Manager() controls a server process which holds Python objects and allows other processes to manipulate them using proxies.

    A manager returned by Manager() will support types listdictNamespaceLockRLockSemaphoreBoundedSemaphoreConditionEventBarrierQueueValue and Array. For example,

    from multiprocessing import Process, Manager
     
    def f(d, l):
        d[1] = '1'
        d['2'] = 2
        d[0.25] = None
        l.append(1)
        print(l)
     
    if __name__ == '__main__':
        with Manager() as manager:
            d = manager.dict()
     
            l = manager.list(range(5))
            p_list = []
            for i in range(10):
                p = Process(target=f, args=(d, l))
                p.start()
                p_list.append(p)
            for res in p_list:
                res.join()
     
            print(d)
            print(l)

     进程同步

    如果不使用来自不同进程的锁输出,就很容易混淆起来。

    from multiprocessing import Process, Lock
     
    def f(l, i):
        l.acquire()
        try:
            print('hello world', i)
        finally:
            l.release()
     
    if __name__ == '__main__':
        lock = Lock()
     
        for num in range(10):
            Process(target=f, args=(lock, num)).start()

    进程池使用

    进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。

    进程池中有两个方法:

    • apply
    • apply_async
    from  multiprocessing import Process,Pool
    import time
     
    def Foo(i):
        time.sleep(2)
        return i+100
     
    def Bar(arg):
        print('-->exec done:',arg)
     
    pool = Pool(5)
     
    for i in range(10):
        pool.apply_async(func=Foo, args=(i,),callback=Bar)
        #pool.apply(func=Foo, args=(i,))
     
    print('end')
    pool.close()
    pool.join()#进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。

    四、协程

    协程的定义和语法

    协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程

    协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:

    协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。

    协程的好处:

    • 无需线程上下文切换的开销
    • 无需原子操作锁定及同步的开销
      •   "原子操作(atomic operation)是不需要synchronized",所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。视作整体是原子性的核心。
    • 方便切换控制流,简化编程模型
    • 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

    缺点:

    • 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
    • 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

    使用yield实现协程操作例子 

    import time
    import queue
    def consumer(name):
        print("--->starting eating baozi...")
        while True:
            new_baozi = yield
            print("[%s] is eating baozi %s" % (name,new_baozi))
            #time.sleep(1)
     
    def producer():
     
        r = con.__next__()
        r = con2.__next__()
        n = 0
        while n < 5:
            n +=1
            con.send(n)
            con2.send(n)
            print("33[32;1m[producer]33[0m is making baozi %s" %n )
     
     
    if __name__ == '__main__':
        con = consumer("c1")
        con2 = consumer("c2")
        p = producer()
    

    看楼上的例子,我问你这算不算做是协程呢?你说,我他妈哪知道呀,你前面说了一堆废话,但是并没告诉我协程的标准形态呀,我腚眼一想,觉得你说也对,那好,我们先给协程一个标准定义,即符合什么条件就能称之为协程:

    1. 必须在只有一个单线程里实现并发
    2. 修改共享数据不需加锁
    3. 用户程序里自己保存多个控制流的上下文栈
    4. 一个协程遇到IO操作自动切换到其它协程

    基于上面这4点定义,我们刚才用yield实现的程并不能算是合格的线程,因为它有一点功能没实现,哪一点呢?

    Greenlet

    greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator

    # -*- coding:utf-8 -*-
     
     
    from greenlet import greenlet
     
     
    def test1():
        print(12)
        gr2.switch()
        print(34)
        gr2.switch()
     
     
    def test2():
        print(56)
        gr1.switch()
        print(78)
     
     
    gr1 = greenlet(test1)
    gr2 = greenlet(test2)
    gr1.switch()
    

       

    ****************未完待续****************

  • 相关阅读:
    自己动手用Javascript写一个无刷新分页控件
    自己动手写一个通用的分页存储过程(适用于多表查询)
    Towards Accurate Multiperson Pose Estimation in the Wild 论文阅读
    统计学习方法c++实现之一 感知机
    2018百度之星开发者大赛-paddlepaddle学习(二)将数据保存为recordio文件并读取
    2018百度之星开发者大赛-paddlepaddle学习
    [转载]C#_Path类常用操作
    安装SQL2K是的文件挂起错误
    相见恨晚MySQL 多表查询
    php截取字符串,出现乱码
  • 原文地址:https://www.cnblogs.com/garrett0220/p/7884711.html
Copyright © 2020-2023  润新知