异步IO

Posted My deram

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了异步IO相关的知识,希望对你有一定的参考价值。

先恶补一下知识点:

上下文切换:当CPU从执行一个线程切换到执行另外一个线程的时候,它需要先存储当前线程的本地的数据,程序指针等,然后载入另一个线程的本地数据,程序指针等,最后才开始执行。这种切换称为“上下文切换”(“context switch”)

      CPU会在一个上下文中执行一个线程,然后切换到另外一个上下文中执行另外一个线程,上下文切换并不廉价。如果没有必要,应该减少上下文切换的发生

 

协程

协程:微线程, 协程是一种用户的轻量级线程

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

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

 

协程的好处

1.无需线程上下文切换,

2.无需原子操作锁定及同步开销  ,  什么是原子操作?  :是指不会被线程调度打断的操作;这种操作一旦开始,就运行到结束,中间不会有任何  context switch(切换到另一个线程,)

                         原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。视作整体是原子性的核心

3.方便切换控制流,简化编程模型

4.高并发 + 高扩展 + 低成本 : 一个CPU支持上万的协程都不是问题,所以很适合用于高并发处理

坏处-----:

1.无法利用多核资源,协程的本质是个单线程,它不能同时将单个CPU的多个核用上, 协程需要配合进程才能在多CPU上,  适用于CPU密集型应用

2.进程阻塞 (Blocking)  操作 如IO操作时,会阻塞掉整个程序

----什么条件符合才能称之为协程?

  A.必须在只有一个单线程里实现并发

  B.修改共享数据不需要加锁

  C.用户程序里自己保持多个控制流的上下文栈

  D.一个协程遇到IO操作自动切换到其他协程!!!!!!

重点来了。。。。。 大量的模块知识点---我希望我以后还能记起来----汗颜!

Greenlet模块

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

from greenlet import greenlet

def test1():
    print(test1:我是1)
    gr2.switch()    #切换到test2
    print(test1:我是1.1)
    gr2.switch()
def test2():
    print(test2:我是2)
    gr1.switch()  #切换到test1
    print(test2:我是2.2)


gr1=greenlet(test1)
gr2=greenlet(test2)
gr1.switch()      #先切换到test1
>>
test1:我是1
test2:我是2
test1:我是1.1
test2:我是2.2

swich()  就是切换,   按执行顺序--  但是遇到IO操作 好像并没有自动切换

Gevent模块

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

 这里使用gevent.sleep 来获取控制权    

import gevent

def func1():
    print(\033[31;1m我是func1\033[0m)
    gevent.sleep(3)
    print(\033[31;1m我是func1.1--我上面有3秒\033[0m)

def func2():
    print(\033[32;1m我是func2.\033[0m)
    gevent.sleep(2)
    print(\033[32;1m我是func2.1 我上面有2秒\033[0m)

def func3():
    print(\033[32;1m我是func3.\033[0m)
    gevent.sleep(2)
    print(\033[32;1m我是func3.1我上面有2秒\033[0m)

gevent.joinall([gevent.spawn(func1),
                gevent.spawn(func2),
                gevent.spawn(func3),])

这里会按照sleep 设置来执行      一定会先打印出func2-->func3-->func1  

同步和异步的性能区别

import gevent


def task(pid):
    """
    Some non-deterministic task
    """
    gevent.sleep(0.5)
    print(Task %s done % pid)


def synchronous():
    for i in range(1, 10):
        task(i)


def asynchronous():
    #threads = [gevent.spawn(task, i) for i in range(10)]
    threads=[]
    for i in range(10):
        threads.append(gevent.spawn(task,i))
    gevent.joinall(threads)


print(Synchronous:)
synchronous()

print(Asynchronous:)
asynchronous()

Synchrounous:定义了同步的函数:定义一个for循环。依次把内容传输给task函数,然后打印执行结果-----

Aynchrounous:定义了异步的函数: 这里用到了一个gevent.spawn方法,就是产生的意思. gevent.joinall 也就是等待所以操作都执行完毕

                gevent.spawn 可以调用函数

可是我们一般也不会这么用。去故意的设置一个gevent.sleep来切换  ,下面就来在实际场景中应用

遇到IO阻塞,自动切换任务

这里就用到了简单的网页爬虫环境中,

from gevent import monkey
import gevent
import time
from urllib.request import urlopen
monkey.patch_all()
#对比得出 协程 运行出的更快
#IO阻塞 自动切换任务。。
def say(url):
    print(get url,url)
    resp = urlopen(url)
    data = resp.read()
    print(len(data),url)
t1_start = time.time()
say(http://www.xiaohuar.com/)
say(http://www.oldboyedu.com/)
print("普通--time cost",time.time() - t1_start)

t2_stat = time.time()
gevent.joinall(
    [gevent.spawn(say,http://www.xiaohuar.com/),
     gevent.spawn(say,http://www.oldboyedu.com/),
     gevent.spawn(say,http://weibo.com/MMbdzx?from=myfollow_all&is_all=1#_rnd1482040021384)]
)
print("gevent---time cost",time.time() - t2_stat)

由于切换时再IO操作时自动完成,所以需要gevent修改py自带的标准库,这一过程在启动时通过monkey patch完成  -- (不懂是啥原理)

对比2次运行完毕的时间,很明显的看到gevent在处理上,更加有优势,

 到了这里简单的就算完了。。。来进入总结概念的部分-----最煎熬---

sellect、poll、epoll三者的区别

 

以上是关于异步IO的主要内容,如果未能解决你的问题,请参考以下文章

异步 IO 的整洁代码

Python异步IO

Cypress.io 如何处理异步代码

如何用python实现异步io

非阻塞IO可以等同异步IO嘛?

ES7-Es8 js代码片段