• 第十章 Python——并发编程


    目录

    一、计算机操作系统介绍

    二、进程的概念与应用

    三、线程的概念与应用

    四、进程池与线程池

    五、协程的概念与应用

    六、IO模型

    一、计算机操作系统介绍

    (一)计算机操作系统概念

    什么是操作系统(what):

    操作系统就是一个协调、管理和控制计算机硬件资源和软件资源的控制程序。

    为什么有操作系统(why):

    程序员无法把所有的硬件操作细节都了解到,管理这些硬件并且加以优化使用是非常繁琐的工作,这个繁琐的工作就是操作系统来干的,有了他,程序员就从这些繁琐的工作中解脱了出来,只需要考虑自己的应用软件的编写就可以了,应用软件直接使用操作系统提供的功能来间接使用硬件。

    操作系统的功能:

    ①隐藏了丑陋的硬件调用接口,为应用程序员提供调用硬件资源的更好,更简单,更清晰的模型(系统调用接口)。应用程序员有了这些接口后,就不用再考虑操作硬件的细节,专心开发自己的应用程序即可。

    ②将应用程序对硬件资源的竞态请求变得有序化。

    (二)计算机操作系统发展史

    第一代计算机(1940~1955):真空管和穿孔卡片

    工作过程:

    特点:

    1、没有操作系统的概念
    2、所有的程序设计都是直接操控硬件

    优点:

    程序员在申请的时间段内独享整个资源,可以即时地调试自己的程序(有bug可以立刻处理)

    缺点:

    浪费计算机资源,一个时间段内只有一个人用。

    Tips:

    同一时刻只有一个程序在内存中,被cpu调用执行,比方说10个程序的执行,是串行的。

    第二代计算机(1955~1965):晶体管和批处理系统

    工作过程:

    特点:

    1、把一堆人的输入攒成一大波输入
    2、然后顺序计算(这是有问题的,但是第二代计算也没有解决)
    3、把一堆人的输出攒成一大波输出

    优点:

    批处理,节省了机时。

    缺点:

    1、整个流程需要人参与控制,将磁带搬来搬去(中间俩小人)

    2、计算的过程仍然是顺序计算-》串行

    3、程序员原来独享一段时间的计算机,现在必须被统一规划到一批作业中,等待结果和重新调试的过程都需要等同批次的其他程序都运作完才可以(这极大的影响了程序的开发效率,无法及时调试程序)

    第三代计算机(1965~1980):集成电路芯片和多道程序设计

    解决的问题:

    ①spooling技术:淘汰了IBM1401机,不必将磁带搬来搬去。

    ②多道技术:多道技术中的多道指的是多个程序,多道技术的实现是为了解决多个程序竞争或者说共享同一个资源(比如cpu)的有序调度问题,解决方式即多路复用,多路复用分为时间上的复用和空间上的复用。

    a)空间上的复用:多个进程共用一个内存条。

    b)时间上的复用:多个进程复用同一个CPU时间。

      CPU遇到IO切换:可以提成效率。

      一个进程CPU占用时间过长也会切走,为了实现并发效果不得已而为之,反而会降低程序执行的效率。

    ③分时操作系统:多个联机终端+多道技术。

    二、进程的概念与应用

    什么是进程(what):

    进程指的就是一个正在运行的程序,或者说是程序的运行过程,即进程是一个抽象的概念。
    进程是起源于操作系统的,是操作系统最核心的概念,操作系统所有其他的概念都是围绕进程展开的。

    孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

    僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。

    为什么要使用进程(why):

    实现并发。

      
    1、串行:
        一个任务完完整整地运行完毕后,才能运行下一个任务
    
    2、并发
        看起来多个任务是同时运行的即可,单核也可以实现并发
    
    3、并行:
        真正意义上多个任务的同时运行,只有多核才实现并行
    
    
    4、cpu的功能:
        cpu是用来做计算,cpu是无法执行IO操作的,一旦遇到io操作,应该让cpu去执行别的任务
    
    
    5、多道技术
        1、空间上的复用=》多个进程共用一个内存条
        2、时间上的复用-》多个进程复用同一个cpu的时间
            cpu遇到IO切换:可以提升效率
            一个进程占用cpu时间过长也会切走:为了实现并发效果不得已而为之,反而会降低程序的执行效率

    进程状态

    tail -f access.log |grep '404'

      执行程序tail,开启一个子进程,执行程序grep,开启另外一个子进程,两个进程之间基于管道'|'通讯,将tail的结果作为grep的输入。

      进程grep在等待输入(即I/O)时的状态称为阻塞,此时grep命令都无法运行

      其实在两种情况下会导致一个进程在逻辑上不能运行,

      1. 进程挂起是自身原因,遇到I/O阻塞,便要让出CPU让其他进程去执行,这样保证CPU一直在工作

      2. 与进程无关,是操作系统层面,可能会因为一个进程占用时间过多,或者优先级等原因,而调用其他的进程去使用CPU。

      因而一个进程由三种状态

    进程并发的实现

    进程并发的实现在于,硬件中断一个正在运行的进程,把此时进程运行的所有状态保存下来,为此,操作系统维护一张表格,即进程表(process table),每个进程占用一个进程表项(这些表项也称为进程控制块)

    如何使用进程(how):

    调用multiprocessing模块,创建进程类

    Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)
    
    强调:
    1. 需要使用关键字的方式来指定参数
    2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

    参数介绍:

    group参数未使用,值始终为None
    
    target表示调用对象,即子进程要执行的任务
    
    args表示调用对象的位置参数元组,args=(1,2,'egon',)
    
    kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}
    
    name为子进程的名称

    方式一:

    from multiprocessing import Process
    import time
    
    def task(x):
        print('%s is running' %x)
        time.sleep(3)
        print('%s is done' %x)
    
    if __name__ == '__main__':
        # Process(target=task,kwargs={'x':'子进程'})
        p=Process(target=task,args=('子进程',)) # 如果args=(),括号内只有一个参数,一定记住加逗号
        p.start() # 只是在操作系统发送一个开启子进程的信号
    
        print('')

    方式二:

    from multiprocessing import Process
    import time
    
    class Myprocess(Process):
        def __init__(self,x):
            super().__init__()
            self.name=x
    
        def run(self):
            print('%s is running' %self.name)
            time.sleep(3)
            print('%s is done' %self.name)
    
    if __name__ == '__main__':
        p=Myprocess('子进程1')
        p.start()  #p.run()
        print('')
    from multiprocessing import Process
    import time
    
    x=100
    def task():
        global x
        x=0
        print('done')
    if __name__ == '__main__':
        p=Process(target=task)
        p.start()
        time.sleep(500) # 让父进程在原地等待,等了500s后,才执行下一行代码
        print(x)
    证明进程与进程之间的内存空间是隔离的

    (一)进程对象相关的属性或方法:

    ①join():让父进程在原地等待,等到子进程运行完毕后,回收对应子进程的系统资源,再执行下一行代码。

    from multiprocessing import Process
    import time
    
    def task(name,n):
        print('%s is running ' %name)
        time.sleep(n)
        print('%s is done ' % name)
    
    
    if __name__ == '__main__':
        p1=Process(target=task,args=('子进程1',1))
        p2=Process(target=task,args=('子进程2',2))
        p3=Process(target=task,args=('子进程3',3))
    
        start_time=time.time()
        p1.start()
        p2.start()
        p3.start()
    
        p3.join()
        p1.join()
        p2.join()
    
        stop_time=time.time()
        print('',(stop_time-start_time))
    join实际是并发
    from multiprocessing import Process
    import time
    
    def task(name,n):
        print('%s is running ' %name)
        time.sleep(n)
        print('%s is done ' % name)
    
    
    if __name__ == '__main__':
        p1=Process(target=task,args=('子进程1',1))
        p2=Process(target=task,args=('子进程2',2))
        p3=Process(target=task,args=('子进程3',3))
    
        start=time.time()
        p1.start()
        p1.join()
        p2.start()
        p2.join()
        p3.start()
        p3.join()
    
        stop=time.time()
        print('',(stop-start))
    这种join还是串行

    ②用循环实现创建多个进程

    from multiprocessing import Process
    import time
    
    def task(name,n):
        print('%s is running ' %name)
        time.sleep(n)
        print('%s is done ' % name)
    
    
    if __name__ == '__main__':
        p_l=[]
        start=time.time()
        for i in range(1,4):
            p=Process(target=task,args=('子进程%s' %i,i))
            p_l.append(p)
            p.start()
    
        # print(p_l)
        for p in p_l:
            p.join()
    
        stop=time.time()
    
        print('',(stop-start))
    用循环实现创建多个进程

    ③pid(getpid、getppid)

    注意:在join()之后继续执行查看子进程pid的操作(p1.pid)仍然可以打印它的值,其原因是:join()回收的是该子进程占用的操作系统资源(包括pid),而python内的p1.pid仅仅是一个值而已。

    from multiprocessing import Process
    import time
    import os
    
    def task():
        print('自己的id:%s 父进程的id:%s ' %(os.getpid(),os.getppid()))
        time.sleep(1)
    
    if __name__ == '__main__':
        p1=Process(target=task)
        p1.start()
        p1.join()
        print('',os.getpid(),os.getppid())
    View Code

    ④name

    from multiprocessing import Process,current_process
    import time
    
    def task():
        print('子进程[%s]运行。。。。' %current_process().name)
        time.sleep(200)
    
    if __name__ == '__main__':
        p1=Process(target=task,name='子进程1')
        p1.start()
        # print(p1.name)
        print('')
    View Code

    ⑤is_alive、terminnate

    from multiprocessing import Process,current_process
    import time
    
    def task():
        print('子进程[%s]运行。。。。' %current_process().name)
        time.sleep(2)
    
    if __name__ == '__main__':
        p1=Process(target=task,name='子进程1')
        p1.start()
        print(p1.is_alive())
        p1.terminate()
        print(p1.is_alive())
        p1.join()
        print(p1.is_alive())
    View Code

    (二)守护进程

    什么是守护进程(what):

    守护进程其实就是一个“子进程”
    守护=》伴随
    守护进程会伴随主进程的代码运行完毕后而死掉。

    为什么要用守护进程(why):

    进程:
    当父进程需要将一个任务并发出去执行,需要将该任务放到一个子进程里
    守护:
    当该子进程内的代码在父进程代码运行完毕后就没有存在的意义了,就应该
    将该子进程设置为守护进程,会在父进程代码结束后死掉。

    如何用守护进程(how):

    from multiprocessing import Process
    import time,os
    
    def task(name):
        print('%s is running' %name)
        time.sleep(3)
    
    if __name__ == '__main__':
        p1=Process(target=task,args=('守护进程',))
        p2=Process(target=task,args=('正常的子进程',))
    
        p1.daemon = True # 一定要放到p.start()之前
        p1.start()
        p2.start()
    
        print('')
    # 主进程代码运行完毕,守护进程就会结束

    (三)进程同步(互斥锁)

    什么是互斥锁(what):

    可以将要执行任务的部分代码(只涉及到修改共享数据的代码)变成串行。

    互斥锁与join()的区别:

    join是要执行任务的所有代码整体串行。

    如何用互斥锁(how):

    from multiprocessing import Process,Lock
    import json
    import os
    import time
    import random
    
    def check():
        time.sleep(1) # 模拟网路延迟
        with open('db.txt','rt',encoding='utf-8') as f:
            dic=json.load(f)
        print('%s 查看到剩余票数 [%s]' %(os.getpid(),dic['count']))
    
    def get():
        with open('db.txt','rt',encoding='utf-8') as f:
            dic=json.load(f)
        time.sleep(2)
        if dic['count'] > 0:
            # 有票
            dic['count']-=1
            time.sleep(random.randint(1,3))
            with open('db.txt','wt',encoding='utf-8') as f:
                json.dump(dic,f)
            print('%s 购票成功' %os.getpid())
        else:
            print('%s 没有余票' %os.getpid())
    
    
    def task(mutex):
        # 查票
        check()
    
        #购票
        mutex.acquire() # 互斥锁不能连续的acquire,必须是release以后才能重新acquire
        get()
        mutex.release()
    
    
    
        # with mutex:
        #     get()
    
    if __name__ == '__main__':
        mutex=Lock()
        for i in  range(10):
            p=Process(target=task,args=(mutex,))
            p.start()
    模拟抢票

    (四)进程间通信(IPC)

    什么是IPC(what):

    进程彼此之间互相隔离,要实现进程间通信(IPC)。

    如何实现进程间通信(how):

    两种实现方式:

    ①PIPE管道(不推荐使用)

    暂不介绍

    ②Queue队列(推荐使用)——>PIPE+锁

    Tips:

    a)队列占用的是内存空间。

    b)不应该往队列中存放大数据,应该只存放数据量较小的信息。

    c)先进先出概念

    from multiprocessing import Queue
    #q = Queue([maxsize])
    #创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。
    #maxsize是队列中允许最大项数,省略则无大小限制。
    q=Queue(3)
    from multiprocessing import Queue
    
    
    q=Queue(3) #先进先出
    
    #put(obj,block,timeout)
    #1、obj——任意类型
    #2、block=True——允许阻塞
    #   block=False——不允许阻塞
    #3、timeout——设置阻塞时间(当block=True时使用)
    #4、当block=True,并设置timeout时间时,程序会在等待timeout时间#后抛出异常,显示队列已满full
    #5、当block=False,程序直接抛出异常,显示队列已满full
    
    q.put('first')
    q.put({'k':'sencond'})
    q.put(['third',])
    #q.put(4)
    #如果这里输入上行注释内容,程序会一直等待,因为队列已满,无法继续#加入队列。
    
    
    #get(block,timeout)
    #1、block=True——允许阻塞
    #   block=False——不允许阻塞
    #2、timeout——设置阻塞时间(当block=True时使用)
    #3、当block=True,并设置timeout时间时,程序会在等待timeout时间#后抛出异常,显示队列已空empty
    #4、当block=False,程序直接抛出异常,显示队列已空empty
    
    print(q.get())
    print(q.get())
    print(q.get())
    #print(q.get())
    #如果这里输入上行注释内容,程序会一直等待,因为队列内容已经取完,程序会一直等待队列出现新内容并取走。
    q.get_nowait():同q.get(False)
    q.put_nowait():同q.put(False)
    put_nowait与get_nowait

    (五)生产者消费者模型

    什么是生产者消费者模型(what)?

    生产者:比喻的是程序中负责产生数据的任务
    消费者:比喻的是程序中负责处理数据的任务

    生产者——>共享的介质(队列)<——消费者

    为什么用生产者消费者模型(why)?

    实现了生产者与消费者的解耦和,生产者可以不停地生产,消费者也可以不停地消费,
    从而平衡了生产者的生产能力与消费者消费能力,提升了程序整体运行的效率。

    当我们的程序中存在明显的两类任务,一类负责产生数据,另外一类负责处理数据
    此时就应该考虑使用生产者消费者模型来提升程序的效率。

    如何使用生产者消费者模型(how)?

    from multiprocessing import JoinableQueue,Process
    import time
    import os
    import random
    
    def producer(name,food,q):
        for i in range(3):
            res='%s%s' %(food,i)
            time.sleep(random.randint(1,3))
            # 往队列里丢
            q.put(res)
            print('33[45m%s 生产了 %s33[0m' %(name,res))
        # q.put(None)
    
    def consumer(name,q):
        while True:
            #从队列里取走
            res=q.get()
            if res is None:break
            time.sleep(random.randint(1,3))
            print('33[46m%s 吃了 %s33[0m' %(name,res))
            q.task_done()
    
    if __name__ == '__main__':
        q=JoinableQueue()
        # 生产者们
        p1=Process(target=producer,args=('egon','包子',q,))
        p2=Process(target=producer,args=('杨军','泔水',q,))
        p3=Process(target=producer,args=('猴老师','',q,))
        # 消费者们
        c1=Process(target=consumer,args=('Alex',q,))
        c2=Process(target=consumer,args=('wupeiqidsb',q,))
        c1.daemon=True
        c2.daemon=True
    
        p1.start()
        p2.start()
        p3.start()
        c1.start()
        c2.start()
    
        p1.join()
        p2.join()
        p3.join()
    
        q.join() #等待队列被取干净
        # q.join() 结束意味着
        # 主进程的代码运行完毕--->(生产者运行完毕)+队列中的数据也被取干净了->消费者没有存在的意义
    
        # print('主')
    模拟生产食物与吃食物

     JoinableQueue类:与Queue类似,但是这个队列是可以被join()住的,作用是等到队列结束在运行下一段代码。

    q.get()与之对应的是q.task_done(),其含义是告诉队列取走一个数据。

    三、线程的概念与应用

    什么是线程(what):

    进程其实不是一个执行单位,进程是一个资源单位。
    每个进程内自带一个线程,线程才是cpu上的执行单位。

    如果把操作系统比喻为一座工厂
    在工厂内每造出一个车间===》启动一个进程
    每个车间内至少有一条流水线===》每个进程内至少有一个线程

    线程=》单指代码的执行过程
    进程-》资源的申请与销毁的过程

    为什么用线程(why):

    多线程指的是,在一个进程中开启多个线程,简单的讲:如果多个任务共用一块地址空间,那么必须在一个进程内开启多个线程。详细的讲分为4点:

      1. 多线程共享一个进程的地址空间

          2. 线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用

          3. 若多个线程都是cpu密集型的,那么并不能获得性能上的增强,但是如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度。

          4. 在多cpu系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不适用于python)

    如何使用线程(how):

    调用threading模块,创建Thread类

    Thread([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个线程
    
    强调:
    1. 需要使用关键字的方式来指定参数
    2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

    参数介绍:

    group参数未使用,值始终为None
    
    target表示调用对象,即子线程要执行的任务
    
    args表示调用对象的位置参数元组,args=(1,2,'egon',)
    
    kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}
    
    name为子线程的名称

    方式一:

    
    
    from threading import Thread
    import time

    def
    task(name): print('%s is running' %name) time.sleep(3) print('%s is done' %name) if __name__ == '__main__': t=Thread(target=task,args=('子线程',)) t.start() print('')

    方式二:

    
    
    from threading import Thread
    import time

    class
    Mythread(Thread): def run(self): print('%s is running' %self.name) time.sleep(3) print('%s is done' %self.name) if __name__ == '__main__': t=Mythread() t.start() print('')

    线程与进程优劣势对比:

    from threading import Thread
    from multiprocessing import Process
    import time
    
    def task(name):
        print('%s is running' %name)
        time.sleep(3)
        print('%s is done' %name)
    
    if __name__ == '__main__':
        t=Thread(target=task,args=('子线程',))
        # t=Process(target=task,args=('子进程',))
        t.start()
        print('')
    线程的开启速度更快
    from threading import Thread
    import time
    
    x=100
    def task():
        global x
        x=0
    
    if __name__ == '__main__':
        t=Thread(target=task,)
        t.start()
        # time.sleep(3)
        t.join()
        print('',x)
    同一进程下的多个线程共享该进程内的数据
    from threading import Thread
    import time,os
    
    def task():
        print(os.getpid())
    
    if __name__ == '__main__':
        t=Thread(target=task,)
        t.start()
        print('',os.getpid())
    线程没有PID,只能查看当前线程所在进程的PID
    from threading import Thread
    import time
    
    def task(name):
        print('%s is running' %name)
        time.sleep(3)
        print('%s is done' %name)
    if __name__ == '__main__':
        t=Thread(target=task,args=('子线程',))
        t.start()
        print('')
    主进程等子进程是因为主进程要给子进程收操作系统资源,进程必须等待其内部所有线程都运行完毕才结束

    (一)线程对象相关的属性或方法:

    Thread实例对象的方法
      # isAlive(): 返回线程是否活动的。
      # getName(): 返回线程名。
      # setName(): 设置线程名。
    
    threading模块提供的一些方法:
      # threading.currentThread(): 返回当前的线程变量。
      # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
      # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
    from threading import Thread,current_thread,active_count,enumerate
    import time
    
    def task():
        print('%s is running' % current_thread().name)
        time.sleep(3)
        print('%s is done' % current_thread().name)
    
    if __name__ == '__main__':
        t = Thread(target=task,name='xxx')
        t.start()
        t.join()
        print(t.is_alive()) #查看线程是否存活
        print(t.getName()) #查看线程名
        print(t.name) #查看线程名
        print('',active_count()) #查看活跃的线程数
        print(enumerate()) #枚举线程
    
        t.join()
        current_thread().setName('主线程') #改线程名
        print('',current_thread().name) #查看线程名

    (二)守护线程

    守护线程会在本进程内所有非守护的线程都死掉了才跟着死。
    即:
    守护线程其实守护的是整个进程的运行周期(进程内所有的非守护线程都运行完毕)

    from threading import Thread,current_thread
    import time
    
    
    def task():
        print('%s is running' % current_thread().name)
        time.sleep(3)
        print('%s is done' % current_thread().name)
    
    
    if __name__ == '__main__':
        t = Thread(target=task,name='守护线程')
        t.daemon=True
        t.start()
        print('')

    (三)线程同步(互斥锁)

    什么是互斥锁(what):

    可以将要执行任务的部分代码(只涉及到修改共享数据的代码)变成串行。

    互斥锁与join()的区别:

    join是要执行任务的所有代码整体串行。

    如何用互斥锁(how):

    from threading import Thread,Lock
    import time
    
    mutex=Lock()
    
    x=100
    def task():
        global x
        mutex.acquire()
        temp=x
        time.sleep(0.1)
        x=temp-1
        mutex.release()
    
    if __name__ == '__main__':
        t_l=[]
        start=time.time()
        for i in range(100):
            t=Thread(target=task)
            t_l.append(t)
            t.start()
    
        for t in t_l:
            t.join()
    
        stop=time.time()
        print(x,stop-start)
    View Code

    (四)死锁现象与解决方案(递归锁)

    所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁。

    解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

    这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。

    from threading import Thread,Lock,active_count,RLock
    import time
    
    # mutexA=Lock()
    # mutexB=Lock()
    obj=RLock() #递归锁的特点:可以连续的acquire
    mutexA=obj
    mutexB=obj
    
    class Mythread(Thread):
        def run(self):
            self.f1()
            self.f2()
    
        def f1(self):
            mutexA.acquire()
            print('%s 拿到A锁' %self.name)
    
            mutexB.acquire()
            print('%s 拿到B锁' %self.name)
            mutexB.release()
    
            mutexA.release()
    
        def f2(self):
            mutexB.acquire()
            print('%s 拿到B锁' %self.name)
            time.sleep(1)
    
            mutexA.acquire()
            print('%s 拿到A锁' %self.name)
            mutexA.release()
    
            mutexB.release()
    
    if __name__ == '__main__':
        for i in range(10):
            t=Mythread()
            t.start()
        # print(active_count())
    递归锁

    (五)信号量

    信号量是控制同一时刻并发执行的任务数。

    from threading import Thread,Semaphore,current_thread
    import time,random
    
    sm=Semaphore(5)
    
    def task():
        with sm:
            print('%s 正在上厕所' %current_thread().name)
            time.sleep(random.randint(1,4))
    
    
    if __name__ == '__main__':
        for i in range(20):
            t=Thread(target=task)
            t.start()
    信号量演示

    (六)线程队列Queue

    queue队列 :使用import queue,用法与进程Queue一样

    #队列:先进先出
    q=queue.Queue(3)
    q.put(1)
    q.put(2)
    q.put(3)
    
    print(q.get())
    print(q.get())
    print(q.get())
    队列:先进先出
    # 堆栈:先进后出
    q=queue.LifoQueue()
    q.put(1)
    q.put(2)
    q.put(3)
    print(q.get())
    print(q.get())
    print(q.get())
    堆栈:先进后出
    # 优先级队列:优先级高先出来,数字越小,优先级越高
    q=queue.PriorityQueue()
    q.put((3,'data1'))
    q.put((-10,'data2'))
    q.put((11,'data3'))
    
    print(q.get())
    print(q.get())
    print(q.get())
    优先级队列:优先级高先出来,数字越小,优先级越高

    (七)定时器

    定时器,指定n秒后执行某操作

    from threading import Timer,current_thread
    
    
    def task(x):
        print('%s run....' %x)
        print(current_thread().name)
    
    
    if __name__ == '__main__':
        t=Timer(3,task,args=(10,))
        t.start()
        print('')

    四、进程池与线程池

    (一)同步/异步

    同步、异步指的是提交任务的两种方式:

    同步:提交完任务后就在原地等待,直到任务运行完毕后拿到任务的返回值,再继续运行下一行代码。
    异步:提交完任务(绑定一个回调函数)后根本就不在原地等待,直接运行下一行代码,等到任务有返回值后会自动触发回调函数。

    (二)进程池与线程池

    什么是池(what):

    池的功能是限制启动的进程数或线程数。

    为什么用进程池/线程池(why):

    当并发的任务数远远超过了计算机的承受能力时,即无法一次性开启过多的进程数或线程数时
    就应该用池的概念将开启的进程数或线程数限制在计算机可承受的范围内。

    from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
    from threading import current_thread
    import os
    import time
    import random
    
    def task(n):
        print('%s run...' %current_thread().name)
        time.sleep(5)
        return n**2
    
    def parse(future):
        time.sleep(1)
        res=future.result()
        print('%s 处理了 %s' %(current_thread().name,res))
    
    if __name__ == '__main__':
        pool=ThreadPoolExecutor(4)
        start=time.time()
        for i in range(1,5):
            future=pool.submit(task,i)
            future.add_done_callback(parse) # parse会在futrue有返回值时立刻触发,并且将future当作参数传给parse
        pool.shutdown(wait=True)
        stop=time.time()
        print('',current_thread().name,(stop - start))
    异步进程池

    五、协程的概念与应用

    什么是协程(what):

    单线程下实现并发,在应用程序里控制多个任务的切换+保存状态

    总结协程特点:

    1. 必须在只有一个单线程里实现并发
    2. 修改共享数据不需加锁
    3. 用户程序里自己保存多个控制流的上下文栈
    4. 附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))

    为什么用协程(why):

    想要在单线程下实现并发
    并发指的是多个任务看起来是同时运行的
    并发=切换+保存状态

    优点:
    应用程序级别速度要远远高于操作系统的切换
    缺点:
    多个任务一旦有一个阻塞没有切,整个线程都阻塞在原地,该线程内的其他的任务都不能执行了。
    一旦引入协程,就需要检测单线程下所有的IO行为。
    实现遇到IO就切换,少一个都不行,以为一旦一个任务阻塞了,整个线程就阻塞了,其他的任务即便是可以计算,但是也无法运行了。

    如何实现协程(how): 

    方式一:yield

    import time
    def func1():
        while True:
            print('func1')
            yield
    
    def func2():
        g=func1()
        for i in range(10000000):
            i+1
            next(g)
            time.sleep(3)
            print('func2')
    start=time.time()
    func2()
    stop=time.time()
    print(stop-start)
    
    yield不能检测IO,实现遇到IO自动切换
    yield不能检测IO,实现遇到IO自动切换

    方式二:Greenlet

    greenlet只是提供了一种比generator更加便捷的切换方式,当切到一个任务执行时如果遇到io,那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题。

    #安装
    pip3 install greenlet
    from greenlet import greenlet
    
    def eat(name):
        print('%s eat 1' %name)
        g2.switch('egon')
        print('%s eat 2' %name)
        g2.switch()
    def play(name):
        print('%s play 1' %name)
        g1.switch()
        print('%s play 2' %name)
    
    g1=greenlet(eat)
    g2=greenlet(play)
    
    g1.switch('egon')#可以在第一次switch时传入参数,以后都不需要

    (三)方式三:Gevent

    Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

    #安装
    pip3 install gevent
    from gevent import monkey,spawn;monkey.patch_all()
    from threading import current_thread
    import time
    
    def eat():
        print('%s eat 1' %current_thread().name)
        time.sleep(3)
        print('%s eat 2' %current_thread().name)
    
    def play():
        print('%s play 1' %current_thread().name)
        time.sleep(1)
        print('%s play 2' %current_thread().name)
    
    g1=spawn(eat,)
    g2=spawn(play,)
    
    print(current_thread().name)
    g1.join()
    g2.join()

    (四)通过Gevent实现单线程socket并发

    通过gevent实现单线程下的socket并发(from gevent import monkey;monkey.patch_all()一定要放到导入socket模块之前,否则gevent无法识别socket的阻塞)

    from gevent import spawn,monkey;monkey.patch_all()
    from socket import *
    from threading import Thread
    
    def talk(conn):
        while True:
            try:
                data=conn.recv(1024)
                if len(data) == 0:break
                conn.send(data.upper())
            except ConnectionResetError:
                break
        conn.close()
    
    def server(ip,port,backlog=5):
        server = socket(AF_INET, SOCK_STREAM)
        server.bind((ip, port))
        server.listen(backlog)
    
        print('starting...')
        while True:
            conn, addr = server.accept()
            spawn(talk, conn,)
    
    if __name__ == '__main__':
        g=spawn(server,'127.0.0.1',8080)
        g.join()
    服务端
    from threading import Thread,current_thread
    from socket import *
    import os
    
    def task():
        client=socket(AF_INET,SOCK_STREAM)
        client.connect(('127.0.0.1',8080))
    
        while True:
            msg='%s say hello' %current_thread().name
            client.send(msg.encode('utf-8'))
            data=client.recv(1024)
            print(data.decode('utf-8'))
    
    if __name__ == '__main__':
        for i in range(500):
            t=Thread(target=task)
            t.start()
    客户端

    六、IO模型

    待补

  • 相关阅读:
    Linux Commands
    sizeof操作符的使用详解
    在Vim中使用cscope
    MySQL学习笔记
    Online judge for leetcode
    使用Vim,让你工作效率更高
    Ext JS笔记
    安装J2EE开发环境
    这些都是什么啊
    QrCode二维码的实现原理
  • 原文地址:https://www.cnblogs.com/neymargoal/p/9295237.html
Copyright © 2020-2023  润新知