zoukankan      html  css  js  c++  java
  • python 协程

    引子---

    当我们了解进程、线程的概念后,知道进程:操作系统资源分配的最小单位,线程:CPU调度的最小单位.虽然两者不断的提高了CPU利用率,但操作系统创建和销毁进程和线程的还是消耗时间,还要管理他们之间的分时切换.例如核心矛盾:当一条线程陷入阻塞后,这一整条线程就不能再做其他事情了,开启和销毁多条线程以及CPU在多条线程之间切换依然依赖操作系统   单线程能否实现并发就比较重要了!因此引出协程的概念

    并发的本质---> 切换和状态保存

    示例代码1
    
    使用生成器函数 yield,实现并发的效果
    def func():
           for i in range(5):
                  yield i
    g = func()
    for i in g:
           print('生活需要勇敢面对')
    
    输出:
    
    生活需要勇敢面对
    生活需要勇敢面对
    生活需要勇敢面对
    生活需要勇敢面对
    生活需要勇敢面对

    # yiled可以保存状态,yield的状态保存与操作系统的保存线程状态很像,但是yield是代码级别控制的,更轻量级
     

    再看示例2

    import time
    def consumer():  #消费者
               while True:
                      res = yield
    
    def producer():  #生产者
           g = consumer()
           next(g)
           for i in range(10000):
                  g.send(i)
    start = time.time()
    producer()
    print( time.time()- start)
    
    输出:
    0.002000093460083008
    
    yield这种切换,就已经在线程中出现了多个任务,这多个任务的切换本质上就是协程. 其中consumer 是一个协程,producer也是一个协程. 单纯的切换还是会消耗时间,但是如果能够阻塞的时候切换,并且多个程序阻塞时间共享,协程能够非常大限度的提升效率
    简单的协程

    什么是协程?

      协程也叫纤程/轻型线程,基于单线程实现并发,对于操作系统来说,协程是不可见的,不需要操作系统调度,协程是程序级别的单位,由用户程序自己控制调度的.

    协程的效率问题

      协程的效率跟操作系统本身没有关系,和线程也没有关系,而是看程序的调度是否合理.

    协程指的只是在同一条线程上,能够互相切换的多个任务,遇到I/O就切换,实际上我们利用协程提高工作效率的一种方式!

    操作系统切换和程序切换对比

    • 线程是属于内核级别的,由操作系统控制调度,如果单线程I/O后执行时间过长,就会被迫交出CPU使用权限,切换到其他线程使用
    • 单线程内开启协程,一旦遇到I/O,就会从应用程序级别进行切换,以此来提升效率,但是非I/O操作的切换与效率无关.

      对比操作系统控制线程的切换,用户在单线程内控制协程的切换

    协程的优缺点

      优点: 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级,

          单线程内就可以使用并发的效果,最大限度的利用CPU

      缺点: 协程的本质是单线程下,无法利用多核,

          协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程.

    协程的特点:

      1.必须在单线程下实现并发

      2.一条协程在多个任务之间相互切换

      3.数据是安全的,不能利用多核

           4.能够规避一个线程上的I/O阻塞,提高了CPU的利用率

    Greenlet模块

    美 /'grinlɪt/  协程模块,多个任务之间来回切换

    import time
    from greenlet import greenlet  #  美 /'grinlɪt/
    def play():
           print('start play')
           g2.switch()  #开关 只负责切换,无法规避IO
           time.sleep(1)    #遇到阻塞后切换g2
           print('end play')
    def study():
           print('start study')
           time.sleep(1)  #遇到阻塞后再执行g1
           print('end study?')
           g1.switch()
    g1 = greenlet(play)
    g2 = greenlet(study)
    g1.switch()
    
    输出
    start play
    start study
    end study?
    end play
    #还是得等待2s

    Gevent模块

    gevent模块是基于greenlet模块实现的,多个任务交给gevent管理,遇到I/O就使用greenlet进行切换

    示例1:

    import gevent
    
    def play(): 协程1
           print('play start')
           gevent.sleep(1)
           print('end play')
    
    def eat():协程2
           print('start eat')
           gevent.sleep(1)
           print('end eat')
    
    g1 = gevent.spawn(play)  #遇到io立即切换
    g2 = gevent.spawn(eat)  #  /spɔn/ 'b'
    #主线程
    # gevent.sleep(2)   #如果无法确定协程阻塞时间,因此我们一般用join
    g1.join()   #精准的控制协程任务,一定是执行完毕后,join立即结束阻塞
    g2.join()
    gevent.joinable([g1,g2])  #简单写法
    
    输出: play start start eat end play end eat

    示例2:

    from gevent import monkey;monkey.patch_all()
    # monkey的存在就是把以下引用的模块中所有的io阻塞都打成一个包,然后gevent就可以识别这些阻塞时间啦
    import time,gevent
    def play():   # 协程1
        print('start play')
        time.sleep(1)   #不用gevent.sleep
        print('end play')
    def run():  # 协程2
        print('start run')
        time.sleep(1)     #monkey处理后time.sleep阻塞 可以被gevent识别
        print('end run')
    
    g1 = gevent.spawn(play)
    g2 = gevent.spawn(run)
    gevent.joinall([g1,g2])
    滑稽的monkey模块

    示例3

    #server端
    
    from gevent import monkey;monkey.patch_all()
    import socket
    import gevent
    
    def talk(conn):
        while True:
            msg = conn.recv(1024).decode()
            conn.send(msg.upper().encode())
    
    sk = socket.socket()
    sk.bind(('127.0.0.1',9000))
    sk.listen()
    
    while True:
        conn,addr = sk.accept()
        gevent.spawn(talk,conn)
    
    
    # client端
    import socket
    import threading
    def task():
        sk = socket.socket()
        sk.connect(('127.0.0.1',9000))
        while True:
            sk.send(b'hello')
            print(sk.recv(1024))
    for i in range(500):
        threading.Thread(target=task).start()
    并发socket

    鉴于一条线程能够支持起500个协程,比如我们4C的机器开5个进程,每条进程20条线程,并发50000条并发是完全没问题的!

     

    ...

    CrazyShenldon
  • 相关阅读:
    Springmvc全局异常处理
    SpringMVC异常处理一
    [GDB7] gdb 的学习
    《Python 第七章》更加抽象
    python问题:IndentationError:expected an indented block错误解决
    [C/C++] C++ 类的学习
    [GCC6] gcc 的学习
    [Python] 列表 list
    [python] 循环与轻量级 pass, del, eval
    《Python 第八章》异常
  • 原文地址:https://www.cnblogs.com/CrazySheldon1/p/10117281.html
Copyright © 2011-2022 走看看