zoukankan      html  css  js  c++  java
  • 并发编程

    多道技术:

    空间复用:

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

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

    切换的两种情况: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

     

  • 相关阅读:
    事务传播机制,搞懂。
    洛谷 P1553 数字反转(升级版) 题解
    洛谷 P1200 [USACO1.1]你的飞碟在这儿Your Ride Is Here 题解
    洛谷 P1055 ISBN号码 题解
    洛谷 P2141 珠心算测验 题解
    洛谷 P1047 校门外的树 题解
    洛谷 P1980 计数问题 题解
    洛谷 P1008 三连击 题解
    HDU 1013 题解
    HDU 1012 题解
  • 原文地址:https://www.cnblogs.com/xinfan1/p/10999979.html
Copyright © 2011-2022 走看看