• 并发编程


    多道技术:

    空间复用:

    同一时间在内存中加载不同的数据,其内存之间是相互隔离。

    时间上的复用:切换加保存。

    切换的两种情况:1一个进程遇到IO操作会切换到另一个进程

    2.时间片用完了也会被强行切换,切换的时候会记录状态。

    多道技术的出现使计算机由串行执行任务变成并发执行任务。

    进程:

    进程就是一个运行的程序,一个程序运行可以产生多个进程,但进程与进程之间是相互隔离的,

    它也是一个资源单位,包含运行程序所需要的所有资源。

    为什么使用进程?

      为了并发的执行多个任务。

    两种使用方式:

      创建Process实例

      继承Process类,覆盖run方法

    注意:开启进程的代码必须放在判断下面,因为windows开启子进程时,会导入代码执行一遍来回去要执行的任务。

    守护进程:

      在被守护进程结束时,守护进程也会随之结束。当然守护进程也可以提前结束。

    常用属性和方法:

       join 提高子进程的优先级,让子进程先于父进程执行代码,父进程的代码必须等被join的子进程的代码执行完才会执行。

       is_alive 进程是否存活

       pid 查看进程id

       terminate  终止进程

         exitcode  获取进程的退出码

       name 查看进程的名字

       daemon 设置为守护进程

    僵尸和孤儿进程:

    孤儿  父进程先于子进程结束,子进程会被操作系统接管,孤儿进程有其存在的意义。

    僵尸进程  :在linux中有一个机制,可以保证父进程在任何时候都可以访问到子进程的一些信息,所以子进程结束后并不会立即清除所有数据,这时候就是僵尸进程。

    僵尸进程会占用一些系统资源,需要父进程调用waitpid来进行清理,python会自动清理僵尸进程。

    IPC

    进程间通讯

    因为每个进程之间内存是物理隔离的,很多时候我们需要将数据讲给另外一个进程,例如:美团要将支付信息交给支付宝

    1.共享文件

      特点:数据量没有什么限制,但读写速度慢

    2.共享内存区域

      特点:数据量小,但读写速度快

    3.管道

      特点:只能单向通讯。而且要二进制解码

    4.socket

      特点:代码结构复杂

    主要方式:共享内存:

      1.Manager 提供一系列常用的数据结构,但是没有处理锁的问题

      2.进程Queue 是一种特殊的容器,队列,规定了先进先出,并且支持IPC,已经处理好了锁了。

    互斥锁:

    相互排斥的锁 mutex

    其本质就是一个标志,不是说锁住了代码,只是限制了代码是否能够执行

    为什么需要锁:因为多个进程使用同一个资源时会造成数据错乱

    特点:加锁会使进程由并发变成串行,提高了安全性但降低了效率。

    与join的区别:

    join是把整个进程代码全部变为串行,这样失去了并发的本意,而且限制了主进程的代码执行

    锁是想锁哪里就锁哪里,锁的部分变成的串行,其他仍然并发,且不限制主进程代码的执行。

    锁的粒度越小效率越高。

    消费者生产者模型:

       要解决的问题:生产者与消费者处理能力不平衡,如果串行执行任务,效率极低。

    解决的方案:

      1.将生产者与消费者分开耦合

      2.将双方并发执行

      3.提供一个共享的容器

      4.生产者将数据放入容器

      5.消费者从容器中取数据

    多线程:

    线程:cpu最小的执行单位,操作系统最小的调度单位,一个固定的执行流程的总称。

    一个进程至少包含一个线程,称之为主线程,是由操作系统自动开启的。

    运行过程中自己开启的线程称为子线程

    线程之间是平等的,没有父子之分,而且线程的开启代码可以放在任何位置,不需要放在判断下面。

    特点:线程的创建开销比进程小。2.同一个进程内的多个线程可以共享同一个进程内的资源。不同进程的线程也是隔离的。

    使用方式:和进程的使用方式一样,只是代码可以放到任意位置

    常用方法:

      currentthread()获取当前的线程对象

      active_cournt 获取存活的线程个数

      enumerate()获取所有运行中的线程对象。

    线程队列:

    queue:

      Queue 普通队列,作用和joinablequeue一样,但不能作为IPC使用

      LifoQueue 先进后出,后进先出,堆栈

      priorityQueue 优先级队列,里面必须是可以被运算的,打印出来,越小的优先级越高

    线程锁:

    lock互斥锁

    RLock递归锁,同一个线程可以多次加锁,但还是要按照规定几次加锁对应几次解锁

    信号量 semaphore 可以限制同一时间多少线程可以同时并发执行

    死锁:当一个资源的访问,需要具备多把锁时,然而不同的锁被不同线程持有了,陷入相互等待中。

      1.尽量使用一个锁,设置超时释放手里的锁

      2.抢锁时,按照顺序抢

    GIL互斥锁:

    全局解释器锁,是锁解释器的,因为cpython的内存管理是非线程安全的,它本质上也是一个互斥锁,为了防止多个本地线程同时执行python的字节码。

    有了这把锁,线程要执行代码必须先抢锁,谁抢到就谁先执行。

    优点:解决了cpython的内存管理的线程安全。

    缺点:多个线程不能并行执行,失去了多核的优势。

    为什么不处理它,因为去掉这个锁的话,会有很多代码需要重构,并且需要程序自己来处理很多的安全问题,这样成本太大,而且很复杂、

    如何避免性能影响?

    首先判断任务是IO密集型还是计算密集型

    IO密集型使用多线程

    计算密集型使用多进程

    它与自定义锁的区别:

    它只保证解释器级别的数据安全,比如引用计数,如果我们自己开启了一些不属于解释器的资源,比如共享文件,那么还是需要我们自己加锁来保证安全。

    加锁和释放:

      拿到解释器要执行的时候立即加锁

      遇到IO时解锁

      cpu时间片用完了,注意解释器的超时时间与cpu的超时时间不同,为100nm(纳秒)

    进程池和线程池:

    池:就是一个容器

    线程池:就是存储线程的容器

    为什么使用线程池:

      1.可以限制线程数量  通过压力测试来得出最大数量

      2.可以管理线程的创建和销毁

      3.可以负责任务的分配

    进程池一样

    使用:创建池,然后submit提交任务

    异步任务将返回future对象,调用add_done_callback可以添加回调函数

    在任务结束时还会自动调用回调函数并传入future本身,调用result()可以拿到任务的结果。

      不常用的两种方式

      shutdown 可以关闭线程池,会阻塞直到所有任务全部完成

      直接调用result 如果任务没有完成会进入阻塞状态、

    异步和同步:

      同步是指提交任务后,必须等待任务执行完后的结果才能继续执行

      异步是指提交任务后,不需要等待任务执行完毕,可以去做其他事情

    异步回调:

      本质上就是一个普通函数,该函数会在任务执行完成后自动被调用

      线程池,谁有空谁执行

      进程池,都是在父进程中回调

    协程:

    单线程实现并发,协程也称轻量级线程,也称微线程,可以由应用程序自己来控制调度。

    好处:可以在一个任务遇到IO操作时,自主切换到自己进程中其他线程

      如果任务足够多的,就可以充分利用cpu 的时间片

    缺点:

      仅适用于IO密集型任务,计算密集型,如果是单线程下的串行效率更高,建议使用多进程来处理

      当单个任务耗时较长时,协程效率反而不高

    我们的目的就是尽可能的提高效率

      进程   线程  协程

      可以多进程 +线程+协程

    协程对比多线程:

    线程池可以解决一定的并发数量,但是如果并发量超过了机器能承受最大限制,线程池就出现瓶颈了

    协程的使用:

    gevent 需要自己安装

    1.先打补丁 (本质是将原本阻塞的代码替换成非阻塞的代码)

    2.gevent.spawn(任务) 来提交任务

    3.必须保证主线不会结束 使用join 或是join all

    IO模型:

    模型就是解决某个问题的套路

    IO问题:

    输入输出

    我要一个用户名用来执行登陆操作,问题用户名需要用户输入,输入需要耗时, 如果输入没有完成,后续逻辑无法继续,所以默认的处理方式就是 等

    将当前进程阻塞住,切换至其他进程执行,等到按下回车键,拿到了一个用户名,再唤醒刚才的进程,将状态调整为就绪态

     

    以上处理方案 就称之为阻塞IO模型

     

    存在的问题:

    当执行到recv时,如果对象并没有发送数据,程序阻塞了,无法执行其他任务

    解决方案:

    多线程或多进程,

    当客户端并发量非常大的时候,服务器可能就无法开启新的线程或进程,如果不对数量加以限制 服务器就崩溃了

    线程池或进程池

    首先限制了数量 保证服务器正常运行,但是问题是,如果客户端都处于阻塞状态,这些线程也阻塞了

    协程:

    使用一个线程处理所有客户端,当一个客户端处于阻塞状态时可以切换至其他客户端任务

    非阻塞IO模型

    阻塞IO模型在执行recv 和 accept 时 都需要经历wait_data

    非阻塞IO即 在执行recv 和accept时 不会阻塞 可以继续往下执行

     

    如何使用:

    将server的blocking设置为False 即设置非阻塞

     

    存在的问题 :

    这样一来 你的进程 效率 非常高 没有任何的阻塞

    很多情况下 并没有数据需要处理,但是我们的进程也需要不停的询问操作系统 会导致CPU占用过高

    而且是无意义的占用

    import socket
    import time
    
    server = socket.socket()
    server.bind(("192.168.13.103",1688))
    server.listen()
    server.setblocking(False) # 默认为阻塞    设置为False 表示非阻塞
    
    # 用来存储客户端的列表
    clients = []
    
    # 链接客户端的循环
    while True:
        try:
            client,addr = server.accept()   # 接受三次握手信息
            # print("来了一个客户端了.... %s" % addr[1])
            # 有人链接成功了
            clients.append(client)
        except BlockingIOError as e:
            # print("还没有人连过来.....")
            # time.sleep(0.5)
            # 服务你的客人去
            for c in clients[:]:
                try: # 可能这个客户端还没有数据过来
                    # 开始通讯任务
                    data = c.recv(2048)
                    c.send(data.upper())
                except BlockingIOError as e:
                    print("这个客户端还不需要处理.....",)
    
                except ConnectionResetError:
                    # 断开后删除这个客户端
                    clients.remove(c)
            print("=======================",len(clients))

    多路复用
    假设原本有30个socket 需要我们自己来处理, 如果是非阻塞IO模型,相当于从头开始问道尾,如果没有需要处理的
    回过头来再次重复,

    多路复用解决问题的思路,找一个代理即select,将你的socket交给select来检测
    select 会返回 那些已经准备好的 可读或者可写的socket
    我们拿着这些准备好的socket 直接处理即可

    对比线程池
    避免了开启线程的资源消耗
    缺点:
    同时检测socket不能超过1024


    异步IO
    阻塞IO recv accept 会将当前线程阻塞住 同步
    非阻塞IO recv accept 不会阻塞当前线程 ,没有数据直接抛出异常
    分析 属于同步还是异步?
    recv (wait_data,copy_data) 设置为非阻塞之后 wait_data不会再阻塞
    但是copy_data 也是IO操作 还是会阻塞
    也属于同步

    多路复用 也属于同步IO


    同步 异步 任务的执行方式
    同步IO 执行IO任务的方式
    异步IO

    异步IO
    线程池中的submit 就是异步任务
    异步的特点就是 立马就会返回
    同步翻译为sync 异步async

     

  • 相关阅读:
    求秋名山老司机车速
    JSON详解
    mysql 建立索引的原则
    mysql 建立索引的原则
    jQuery的html(),text()和val()比较
    jQuery的html(),text()和val()比较
    CSS cursor 属性
    CSS cursor 属性
    判断是否为润年
    mysql 查看当前使用的配置文件my.cnf的方法
  • 原文地址:https://www.cnblogs.com/xinfan1/p/10999979.html
Copyright © 2020-2023  润新知