python高级编程学习——10—(协程、同步异步、生成器-send方法、yield完成多任务、yield from案例、进程,线程,协程对比总结、下载器案例实现)

1、同步、异步

同步:是指代码调用IO操作时,必须等待IO操作完成才返回的调用方式
异步:是指代码调用IO操作时,不必等IO操作完成就返回的调用方式

阻塞、非阻塞
阻塞:从调用者的角度出发,如果在调用的时候,被卡住,不能再继续向下运行,需要等待,就说是阻塞;
非阻塞:从调用者的角度出发, 如果在调用的时候,没有被卡住,能够继续向下运行,无需等待,就说是非阻塞;

生成器-send方法
send方法有一个参数,该参数指定的是上一次被挂起的yield语句的返回值

"""斐波那契数列"""
def create_num(num):
    a, b = 0, 1
    
    current_num = 0
    
    while current_num < num:
        yield a
        a, b = b, a+b
        current_num += 1
    
    return "abc"
  
    
g = create_num(5)
print(g)
# for i in g:          # 循环输出
#     print(i)

# print(next(g))   # 逐个取值
# print(next(g))
# print(next(g))
# print(next(g))
# print(next(g))

while True:
    try:
        ret = next(g)
        print(ret)
    except Exception as e:
        # print(e, '打印出')
        print(e.value, '打印出')    # return 的值输出:abc 打印出
        break

send方法

"""斐波那契数列"""
def create_num(num):
    a, b = 0, 1
    current_num = 0
    
    while current_num < num:
        result = yield a
        print("输出yield a的赋值result:", result)
        a, b = b, a+b
        current_num += 1
    
    return "abc"
 
    
g = create_num(5)

print(next(g))              # 如果需要使用send启动生成器,也可以先用next启动
# print(g.send(None))
print(g.send('123'))        # 如果需要使用send启动生成器,第一次必须要传入None才行
print(g.send('999'))

输出结果及原因如下图所示:(重要)
在这里插入图片描述
当不给yield a赋值输出的时候,send方法和next功能一样,而且send传参也没有效果

"""斐波那契数列"""
def create_num(num):
    a, b = 0, 1
    current_num = 0
    
    while current_num < num:
        # result = yield a
        yield a                                            # 当不给yield a赋值输出的时候,send方法和next功能一样,而且send传参也没有效果
        # print("输出yield a的赋值result:", result)
        a, b = b, a+b
        current_num += 1
    
    return "abc"
 
    
g = create_num(5)

print(next(g))              # 如果需要使用send启动生成器,也可以先用next启动
# print(g.send(None))
print(g.send('123'))        # 如果需要使用send启动生成器,第一次必须要传入None才行

print(g.send('999'))

在这里插入图片描述
关闭生成器

# 关闭生成器
g.close()

2、使用yield完成多任务

import time


def task1():
    while True:
        print("---1----")
        time.sleep(0.1)
        yield
        

def task2():
    while True:
        print("---2----")
        time.sleep(0.1)
        yield


def main():
    t1 = task1()
    t2 = task2()
    
    while True:
        next(t1)
        next(t2)
        
    
if __name__ == '__main__':
    main()

在这里插入图片描述

3、yield from介绍

python3.3新加了yield from语法

from itertools import chain


l = [1, 2, 3]

dic = {
    "name": "asd",
    "age": 18
}

for value in zip(l, dic):
    print(value)
"""输出为
(1, 'name')
(2, 'age')
"""


for value in chain(l, dic, range(5, 10)):
    print(value)
    
"""输出如下
1
2
3
name
age
5
6
7
8
9
"""

print(chain(l, dic, range(5, 10)))          # <itertools.chain object at 0x000001F0A7B0AF28>
print(list(chain(l, dic, range(5, 10))))    # [1, 2, 3, 'name', 'age', 5, 6, 7, 8, 9]


def my_chain(*args, **kwargs):           # *args, **kwargs代表不定长参数
    for my_iterable in args:
        # for value in my_iterable:
        #     yield value
        yield from my_iterable                            # yield from相当于上面两句,相当于一个for循环


for value in my_chain(l, dic, range(5, 10)):
    print(value)
"""输出如下
1
2
3
name
age
5
6
7
8
9
"""

def generator_1(l):
    yield l


def generator_2(l):
    yield from l


for i in generator_1(l):
    print(i)                     # [1, 2, 3]    因为传参的是列表
    
for i in generator_2(l):
    print(i)
"""
1
2
3
"""

yield from建立起调用方和子生成器的通道,子生成器完成具体任务

def generator_1():                        # 【子生成器】:yield from后的generator_1()生成器函数是子生成器
    total = 0
    while True:
        x = yield
        print('加', x)
        if not x:                         # 如果传参None,循环退出
            break
        total += x
    return total


def generator_2():           #【委托生成器】:generator_2()是程序中的委托生成器,它负责委托子生成器完成具体任务。
    while True:
        total = yield from generator_1()  # yield from建立起调用方和子生成器的通道
        print('加和总数是:', total)


def main():  # 调用方                【调用方】:main()是程序中的调用方,负责调用委托生成器。
    # g1 = generator_1()
    # g1.send(None)
    # g1.send(2)
    # g1.send(3)
    # g1.send(None)
    g2 = generator_2()
    g2.send(None)
    g2.send(2)
    g2.send(3)
    g2.send(None)


if __name__ == '__main__':
    main()

4、协程

协程,又称微线程
协程是python个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源)

Python中的协程大概经历了如下三个阶段:
1、最初的生成器变形yield/send
2、yield from
3、在最近的Python3.5版本中引入async/await关键字

使用greenlet完成多任务
安装模块:pip3 install greenlet

from greenlet import greenlet
import time

# 协程利用程序IO中的延迟,或者运行IO所需的时间,来切换任务  (这里代码中的延迟0.5s代表程序IO过程中所需要的时间)
def demo1():
    while True:
        print("demo1")
        gr2.switch()                           # 在延迟0.5s之间,切换到demo2的任务
        time.sleep(0.5)
        

def demo2():
    while True:
        print("demo2")
        gr1.switch()                          # 在延迟0.5s之间,切换到demo1的任务    互相切换
        time.sleep(0.5)
        

gr1 = greenlet(demo1)
# print(greenlet.__doc__)   # 打印类的说明文档

gr2 = greenlet(demo2)

gr1.switch()                                # 先执行demo1的任务,进行一个任务

使用gevent完成多任务
安装模块:pip3 install gevent

import gevent
import time
from gevent import monkey   # monkey补丁

#将程序中的耗时操作,换成gevent模块能够识别的形式
monkey.patch_all()


def f1(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(0.5)
        # gevent.sleep(0.5)
        

def f2(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(0.5)                 # time模块在gevent中无法识别,需要换成gevent的时间模块或者打补丁monkey.patch_all()
        # gevent.sleep(0.5)


def f3(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(0.5)
        # gevent.sleep(0.5)
        
        
g1 = gevent.spawn(f1, 5)
# time.sleep(1)                          # time模块在gevent中识别不了,需要替换
gevent.sleep(1)

g2 = gevent.spawn(f2, 5)
g3 = gevent.spawn(f3, 5)

g1.join()           # 这样是顺序执行
g2.join()
g3.join()
"""
<Greenlet at 0x17621773e48: f1(5)> 0
<Greenlet at 0x17621773e48: f1(5)> 1
<Greenlet at 0x1762196b048: f2(5)> 0
<Greenlet at 0x1762196b148: f3(5)> 0
<Greenlet at 0x17621773e48: f1(5)> 2
<Greenlet at 0x1762196b048: f2(5)> 1
<Greenlet at 0x1762196b148: f3(5)> 1
<Greenlet at 0x17621773e48: f1(5)> 3
<Greenlet at 0x1762196b048: f2(5)> 2
<Greenlet at 0x1762196b148: f3(5)> 2
<Greenlet at 0x17621773e48: f1(5)> 4
<Greenlet at 0x1762196b048: f2(5)> 3
<Greenlet at 0x1762196b148: f3(5)> 3
<Greenlet at 0x1762196b048: f2(5)> 4
<Greenlet at 0x1762196b148: f3(5)> 4
"""

gevent模块实现简单下载器案例

import gevent
from gevent import monkey          # 补丁


monkey.patch_all()
import requests        # urllib 模块进行封装后的模块   需要将requests模块放在补丁模块下面,就不会出现下面的警告,相当于补丁补入requests模块中
"""
MonkeyPatchWarning: Monkey-patching ssl after ssl has already been imported may lead to errors, including RecursionError on Python 3.6. It may also silently lead to incorrect behaviour on Python 3.7. Please monkey-patch earlier. See https://github.com/gevent/gevent/issues/1016. Modules that had direct imports (NOT patched): ['urllib3.contrib.pyopenssl (G:\\python3.6.5\\lib\\site-packages\\urllib3\\contrib\\pyopenssl.py)', 'urllib3.util (G:\\python3.6.5\\lib\\site-packages\\urllib3\\util\\__init__.py)'].
  monkey.patch_all()
"""


def download(url):
    print("get: %s" % url)
    res = requests.get(url)
    data = res.text                # 网址信息
    print(len(data), url)
    
    
# g1 = gevent.spawn(download, "https://www.baidu.com/")
# g2 = gevent.spawn(download, "https://www.meijutt.tv/")
# g3 = gevent.spawn(download, "https://www.kancloud.cn/ju7ran/gaoji/1474878")
# g4 = gevent.spawn(download, "https://www.csdn.net/")
# g1.join()
# g2.join()
# g3.join()
# g4.join()


gevent.joinall([                                                    # joinall函数完成上面的效果
    gevent.spawn(download, "https://www.baidu.com/"),
    gevent.spawn(download, "https://www.meijutt.tv/"),
    gevent.spawn(download, "https://www.kancloud.cn/ju7ran/gaoji/1474878"),
    gevent.spawn(download, "https://www.csdn.net/")
])

5、进程、线程、协程对比

简单总结

  • 进程是资源分配的单位
  • 线程是操作系统调度的单位
  • 进程切换需要的资源很最大,效率很低
  • 线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)
  • 协程切换任务资源很小,效率高
  • 多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中,所以是并发

进程可以理解为打开一个QQ进行联网,多线程代表这个QQ中的不同聊天窗口,协程代表如果某一个聊天窗口中一份传输信息内容比较大,可以先处理这个聊天窗口中的信息内容小的部分,合理优化资源。

发布了50 篇原创文章 · 获赞 9 · 访问量 2074

猜你喜欢

转载自blog.csdn.net/weixin_42118531/article/details/104031274