V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
zl2003cn
V2EX  ›  Python

Python 中的 asyncio+aiohttp 为什么还要创建线程

  •  
  •   zl2003cn · 2017-02-21 11:11:43 +08:00 · 5082 次点击
    这是一个创建于 2857 天前的主题,其中的信息可能已经有所发展或是发生改变。

    asyncio 不是用协程吗?我理解协程就应该是一个进程只创建一个线程,由这个线程自行调度协程,而使用 asyncio+aiohttp 时创建了 25 个线程, gevent+requests 创建了 15 个线程,这又是怎么理解?协程不就是为了节省线程切换带来的性能损耗吗?

    第 1 条附言  ·  2017-02-21 13:46:12 +08:00

    modified fetch function with semaphore

    import random
    import asyncio
    from aiohttp import ClientSession
    
    async def fetch(url, session):
        async with session.get(url) as response:
            delay = response.headers.get("DELAY")
            date = response.headers.get("DATE")
            print("{}:{} with delay {}".format(date, response.url, delay))
            return await response.read()
    
    
    async def bound_fetch(sem, url, session):
        # Getter function with semaphore.
        async with sem:
            await fetch(url, session)
    

    我在Windows上跑,在资源管理器看到创建了25个线程 async def run(r): url = "http://localhost:8080/{}" tasks = [] # create instance of Semaphore sem = asyncio.Semaphore(1000)

        # Create client session that will ensure we dont open new connection
        # per each request.
        async with ClientSession() as session:
            for i in range(r):
                # pass Semaphore and session to every GET request
                task = asyncio.ensure_future(bound_fetch(sem, url.format(i), session))
                tasks.append(task)
    
            responses = asyncio.gather(*tasks)
            await responses
    
    number = 10000
    loop = asyncio.get_event_loop()
    
    future = asyncio.ensure_future(run(number))
    loop.run_until_complete(future)
    
    第 2 条附言  ·  2017-02-21 13:47:39 +08:00

    modified fetch function with semaphore

    import random
    import asyncio
    from aiohttp import ClientSession
    
    async def fetch(url, session):
        async with session.get(url) as response:
            delay = response.headers.get("DELAY")
            date = response.headers.get("DATE")
            print("{}:{} with delay {}".format(date, response.url, delay))
            return await response.read()
    
    
    async def bound_fetch(sem, url, session):
        # Getter function with semaphore.
        async with sem:
            await fetch(url, session)
    
    
    async def run(r):
        url = "http://localhost:8080/{}"
        tasks = []
        # create instance of Semaphore
        sem = asyncio.Semaphore(1000)
    
        # Create client session that will ensure we dont open new connection
        # per each request.
        async with ClientSession() as session:
            for i in range(r):
                # pass Semaphore and session to every GET request
                task = asyncio.ensure_future(bound_fetch(sem, url.format(i), session))
                tasks.append(task)
    
            responses = asyncio.gather(*tasks)
            await responses
    
    number = 10000
    loop = asyncio.get_event_loop()
    
    future = asyncio.ensure_future(run(number))
    loop.run_until_complete(future)
    

    我在Windows的资源管理器看的线程数,刚才中间怎么打断了。。。

    第 3 条附言  ·  2017-02-21 14:31:32 +08:00

    gevent:

    import gevent.monkey
    gevent.monkey.patch_socket()
    
    import gevent
    import urllib.request as req
    import json
    
    def fetch(pid):
        response = req.urlopen('http://localhost:8080/')
        result = response.read()
        #json_result = json.loads(result)
        #datetime = json_result['datetime']
    
        print('Process %s' % (pid))
        return result
    
    def asynchronous():
        threads = []
        for i in range(1,800):
            threads.append(gevent.spawn(fetch, i))
        gevent.joinall(threads)
    
    print('Asynchronous:')
    asynchronous()
    

    gevent打了monkey patch也是创建15线程呀。。

    第 4 条附言  ·  2017-02-21 19:03:24 +08:00

    自己结帖,测试数据为:Windows下用asyncio跑一个run_forever()要开5个线程,用aiohttp发起请求最大创建25个线程,使用基于线程池的DNS resolver的gevent发起请求创建15个线程。而Linux(Ubuntu)下用asyncio跑空的事件循环只用1个线程,用aiohttp发起请求也只用1个线程,使用基于线程池DNS resolver的gevent创建10个线程,而在设置export GEVENT_RESOLVER=ares后也是只开一个线程,在Linux下的行为符合我对基于协程的异步IO的理解,而Windows下ayncio库为什么要创建这么多线程还是不理解,google也搜不到什么信息,望有大神告知。

    第 5 条附言  ·  2017-02-21 19:09:16 +08:00

    不对,刚再测了一下aiohttp发起请求还是要创建5个线程的,但也比Windows下创建少的多。。

    14 条回复    2017-02-27 11:12:25 +08:00
    chy373180
        1
    chy373180  
       2017-02-21 11:27:52 +08:00
    你从哪里看到有 25 个线程?代码贴下?
    wwqgtxx
        2
    wwqgtxx  
       2017-02-21 12:16:50 +08:00
    别的不说,“ gevent+requests 创建了 15 个线程”那只能说明你根本没用对 gevent , monkey patch 肯定没有在代码的第一行就打上
    zl2003cn
        3
    zl2003cn  
    OP
       2017-02-21 13:48:14 +08:00
    @wwqgtxx 我用的 grequests 这个库,不知道是不是这个库也没打
    zl2003cn
        4
    zl2003cn  
    OP
       2017-02-21 13:52:50 +08:00
    @wwqgtxx 这个库打了 monkey patch 的,难道是 Windows 的锅?
    zl2003cn
        5
    zl2003cn  
    OP
       2017-02-21 13:53:49 +08:00
    wwqgtxx
        6
    wwqgtxx  
       2017-02-21 14:01:31 +08:00
    @zl2003cn 不管别的类库打没打,你应该在你程序的第一行(!!!不是在你的 main 函数中,是程序的第一行,第一行,除了注释的第一行!!!)打上 monkey patch
    Zzzzzzzzz
        7
    Zzzzzzzzz  
       2017-02-21 14:09:21 +08:00
    gevent 那个正常, 它的 dns resolver 默认是线程池实现, 可以通过设置 GEVENT_RESOLVER=ares 改成 c-ares 的实现
    zl2003cn
        8
    zl2003cn  
    OP
       2017-02-21 14:32:05 +08:00
    @wwqgtxx 用 urllib 的版本有打 monkey patch ,更新了代码~
    wwqgtxx
        9
    wwqgtxx  
       2017-02-21 14:39:50 +08:00
    7 楼的解释是正确的
    DNS queries performed through threadpool (default) or through c-ares (enabled via GEVENT_RESOLVER=ares env var).
    zl2003cn
        10
    zl2003cn  
    OP
       2017-02-21 14:42:57 +08:00
    @Zzzzzzzzz 好的,我换 Ubuntu 试试 c-ares
    dant
        11
    dant  
       2017-02-21 19:30:52 +08:00 via Android
    线程池招你惹你了(
    另外,如果你用的是基于 IOCP 的 ProactorEventLoop ,那么线程数量可能会更多。
    最后,线程要用 Process Explorer 看。
    zl2003cn
        12
    zl2003cn  
    OP
       2017-02-21 21:51:03 +08:00
    @dant 没招我惹我:)只是想知道为什么要建立线程池,其对性能有哪些影响
    pynix
        13
    pynix  
       2017-02-21 22:02:34 +08:00
    你使用的可能是个假 aio
    zungmou
        14
    zungmou  
       2017-02-27 11:12:25 +08:00
    不开多线程怎么异步呀?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1012 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 20:22 · PVG 04:22 · LAX 12:22 · JFK 15:22
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.