3-协程

协程概念

子程序/函数:在所有语言中都是层级调用,比如A调用B,在B执行的过程中又可以调用C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。是通过栈实现的,一个线程就是执行一个子程序,子程序调用总是一个入口,一次返回,调用的顺序是明确的

概述:看上去也是子程序,但执行过程中,在子程序的内部可中断,然后转而执行别的子程序,不是函数调用。有点类似于CPU中断

'''
def C():
    print("C--start")
    print("C--end")
def B():
    print("B--start")
    C()
    print("B--end")
def A():
    print("A--start")
    B()
    print("A--end")

A()
'''


def A():
    print(1)
    print(2)
    print(3)
def B():
    print("x")
    print("y")
    print("z")
'''
1
2
x
y
z
3
执行出这个结果
但是A中是没有B的调用
看起来A、B执行过程有点像线程,但协程的特点在于是一个线程执行


与线程相比,协程的执行效率极高,因为只有一个线程,也不存在同时写变量的冲突,在协程中共享资源不加锁,只需要判断状态
'''

自己写生成器

为了理解协程,首先理解生成器

class My_gen(object):
    def __init__(self):
        self.number = 0
        pass

    def __next__(self):
        self.number += 1
        if self.number < 10:
            return self.number
        else:
            raise StopIteration()


def f():
    number = 0
    while number < 10:
        number +=1
        yield number


g = My_gen()
print(type(g))
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(next(g))

协程原理

'''
Python对协程的支持是通过generator实现的

'''
def run():
    print(1)
    yield 10
    print(2)
    yield 20
    print(3)
    yield 30

#协程的最简单风格,控制函数的阶段执行,节约线程或者进程的切换
#返回值是一个生成器
m = run()
print(next(m))
print(next(m))
print(next(m))

向yield发送消息

import threading


def f():
    while True:
        x = yield
        print(x)


def func(g):
    next(g)
    g.send(10)
    next(g)
    g.send(12)


if __name__ == '__main__':
    g = f()
    print(threading.enumerate())
    func(g)

输出结果:    
[<_MainThread(MainThread, started 1856)>]
10
None
12    

数据传输

def run():
    #空变量,存储的作用data始终为空
    data = ""
    r = yield data
    #r = a
    print(1, r, data)
    r = yield "aa"
    #r = b
    print(2, r, data)
    r = yield "bb"
    #r = c
    print(3, r, data)
    r = yield "cc"


m = run() 
#启动m
m.send(None)   # ''
print(m.send("a")) # 'aa'
print(m.send("b")) # 'bb'
print(m.send("c"))  # 'cc'
print("******")

输出结果:
1 a 
aa
2 b 
bb
3 c 
cc
******

生产者与消费者

def product(c):
    c.send(None)
    for i in range(5):
        print("生产者产生数据%d"%i)
        r = c.send(str(i))
        print("消费者消费了数据%s"%r)
    c.close()
def customer():
    data = ""
    while True:
        n = yield data
        if not n:
            return
        print("消费者消费了%s"%n)
        data = "200"
c = customer()
product(c)

买汉堡

import time


def buy():
    """
    购买
    :return:
    """
    result = '666'
    while True:
        n = yield result
        if not n:
            return None
        time.sleep(1)
        result = '{n}购买成功'.format(n=n)


def make(buy):
    next(buy)
    n = 0
    while n < 5:
        n = n + 1
        r = buy.send(n)
        print(r)
    buy.close()


if __name__ == '__main__':
    buy = buy()
    make(buy)

异步协程

import asyncio


# 创建一个类, 做汉堡的一个类
class Hamburger:

    @classmethod
    def make(cls, num, *args, **kwargs):
        """
        创建指定个数的对象
        """
        hamburgers = []
        for i in range(num):
            hamburgers.append(cls.__new__(cls))
        return hamburgers


# 创建实例,一个实例就相当于一个汉堡
hamburgers = Hamburger.make(5)
print(hamburgers)


# 开始 已经做好了5个汉堡

async def make_hamburger(n):
    # 统计做的汉堡的个数
    count = 0
    while True:
        if len(hamburgers) == 0:
            # 如果没有汉堡, 根据请求做汉堡
            await ask_for_hamburger()
        # 取出一个汉堡给顾客
        hamburger = hamburgers.pop()
        yield hamburger
        count += 1
        if count == n:
            break


async def ask_for_hamburger():
    await asyncio.sleep(4)
    hamburgers.extend(Hamburger.make(3))


async def buy_hamburgers():
    bucket = []
    async for hamburger in make_hamburger(12):
        bucket.append(hamburger)
        print('买到第{0}个汉堡'.format(hamburger))


# 事件循环
loop = asyncio.get_event_loop()
loop.run_until_complete(buy_hamburgers())
loop.close()

使用异步协程实现批量下载

"""
- aiohttp  :  异步请求库
- asuyncio :  异步协成库
- re   :   正则表达式
"""
import re
import asyncio
import aiohttp
import async_timeout

start_url = "http://www.geyanw.com"
crawled_urls = []  # 已经爬过的URL
ALLOW_HOST = "geyanw.com"


async def start_reqeust():
    """
    根据start_url获取网页的内容,从start_url网页中
    提取新的url,并把url加到队列中.
    :return:
    """
    # 以异步的方法获得网页内容
    content = await get_content(start_url)
    if content is not None:
        urls = parse_html(content)
        for url in urls:
            q.put_nowait(url)


async def get_content(url):
    """
    根据url获取网页内容
    :param url:网页地址
    :return:
    """
    async with aiohttp.ClientSession() as session:
        try:
            with async_timeout.timeout(5):
                async with session.get(url) as response:
                    print(response)
                    if response.status == 200:
                        print("{url}下载成功".format(url=url))
                        html = await response.read()
                        return html
                    else:
                        return None

        except Exception as e:
            print(e)
        return None


def parse_html(html):
    """
    从html中提取新的url
    :param html:网页源码
    :return:网页源码中的所有url  -->list
    """
    pattern = re.compile(r'href="(/[a-z0-9-/]+(.html)?)"')
    urls = pattern.findall(html.decode('GBK'))
    return [start_url + url[0] for url in urls]


async def work(q):
    """
    从队列q中取出url,以异步的方式发送一个get请求,
    从返回的response中获取网页源码,从网页源码中提取url,
    如果url是新的没有爬过的有效url,把url添加到队列中.

    :param q:
    :return:
    """
    while not q.empty():
        # 从队列q中取出url
        url = await q.get()
        if url not in crawled_urls:
            # 以异步的方式发送一个get请求,
            content = await get_content(url)
            if content is not None:
                urls = parse_html(content)
                for url in urls:
                    # 如果url是新的没有爬过的有效url,把url添加到队列中.
                    if url not in crawled_urls and ALLOW_HOST in url:
                        q.put_nowait(url)
            else:
                print("{url}请求失败!".format(url=url))


if __name__ == '__main__':
    q = asyncio.Queue()
    loop = asyncio.get_event_loop()
    init_req = start_reqeust()
    task = asyncio.ensure_future(init_req)
    loop.run_until_complete(task)
    works = []
    # 创建多个协程
    for i in range(50):
        # 创建一个协程
        c = asyncio.ensure_future(work(q))
        # 把协程对象添加到列表
        works.append(c)

    loop.run_until_complete(asyncio.wait(works))
    loop.close()

    print(crawled_urls)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容