线程:CPU基本执行单元,可以与同属一个进程的其他线程共享资源,线程是属于进程的。

进程:资源单元,进程一般由程序、数据集、进程控制块三部分组成。一个进程默认有一个主线程,

GIL:用于在进程中对所有线程加锁,同一时刻只有一个线程被cpu调度。GIL对IO请求影响小。

在编写爬虫时,性能的消耗主要在IO请求中

单线程模式下请求URL时必然会引起等待,从而使得请求整体变慢。

  1. import requests
  2.  
  3. def fetch_async(url):
  4.  
  5. response = requests.get(url)
  6. print(response.text)
  7. return response
  8.  
  9. if __name__ == '__main__':
  10.  
  11. url_list = ['http://www.github.com', 'http://www.bing.com']
  12.  
  13. for url in url_list:
  14. fetch_async(url)

同步执行

多线程方式运行:

  1. import requests
  2. from concurrent.futures import ThreadPoolExecutor
  3.  
  4. def fetch_async(url):
  5.  
  6. response = requests.get(url)
  7. return response
  8.  
  9. if __name__ == '__main__':
  10.  
  11. pool = ThreadPoolExecutor(5)
  12.  
  13. url_list = ['http://www.github.com', 'http://www.bing.com']
  14.  
  15. for url in url_list:
  16. pool.submit(fetch_async,url)
  17.  
  18. pool.shutdown()

多线程

  1. import requests
  2. from concurrent.futures import ThreadPoolExecutor
  3.  
  4. def fetch_async(url):
  5.  
  6. response = requests.get(url)
  7. return response
  8.  
  9. def callback(future):
  10. print(future.result())
  11.  
  12. if __name__ == '__main__':
  13.  
  14. pool = ThreadPoolExecutor(5)
  15.  
  16. url_list = ['http://www.github.com', 'http://www.bing.com']
  17.  
  18. for url in url_list:
  19. v = pool.submit(fetch_async,url)
  20. v.add_done_callback(callback)
  21.  
  22. pool.shutdown()

多线程+回调函数

多进程方式运行:

  1. import requests
  2. from concurrent.futures import ProcessPoolExecutor
  3.  
  4. def fetch_async(url):
  5.  
  6. response = requests.get(url)
  7. return response
  8.  
  9. if __name__ == '__main__':
  10.  
  11. pool = ProcessPoolExecutor(5)
  12.  
  13. url_list = ['http://www.github.com', 'http://www.bing.com']
  14.  
  15. for url in url_list:
  16. pool.submit(fetch_async,url)
  17.  
  18. pool.shutdown(wait=True)

多进程

  1. import requests
  2. from concurrent.futures import ProcessPoolExecutor
  3.  
  4. def fetch_async(url):
  5.  
  6. response = requests.get(url)
  7. return response
  8.  
  9. def callback(future):
  10. print(future.result())
  11.  
  12. if __name__ == '__main__':
  13.  
  14. pool = ProcessPoolExecutor(5)
  15.  
  16. url_list = ['http://www.github.com', 'http://www.bing.com']
  17.  
  18. for url in url_list:
  19. v = pool.submit(fetch_async,url)
  20. v.add_done_callback(callback)
  21.  
  22. pool.shutdown(wait=True)

多进程+回调函数

通过上述代码均可以完成对请求性能的提高,对于多线程和多进行的缺点是在IO阻塞时会造成了线程和进程的浪费,所以异步IO会是首选:

  1. import asyncio
  2.  
  3. @asyncio.coroutine
  4. def func1():
  5. print('before...func1......')
  6. yield from asyncio.sleep(5)
  7. print('end...func1......')
  8.  
  9. tasks = [func1(), func1()]
  10.  
  11. loop = asyncio.get_event_loop()
  12. loop.run_until_complete(asyncio.gather(*tasks))
  13. loop.close()

asyncio-示例1

异步发送TCP请求:

  1. import asyncio
  2.  
  3. @asyncio.coroutine
  4. def fetch_async(host, url='/'):
  5. print(host, url)
  6. reader, writer = yield from asyncio.open_connection(host, 80)
  7.  
  8. request_header_content = """GET %s HTTP/1.0\r\nHost: %s\r\n\r\n""" % (url, host,)
  9. request_header_content = bytes(request_header_content, encoding='utf-8')
  10.  
  11. writer.write(request_header_content)
  12. yield from writer.drain()
  13. text = yield from reader.read()
  14. print(host, url, text)
  15. writer.close()
  16.  
  17. tasks = [
  18. fetch_async('www.cnblogs.com', '/alex/'),
  19. fetch_async('dig.chouti.com', '/pic/show?nid=4073644713430508&lid=10273091')
  20. ]
  21.  
  22. loop = asyncio.get_event_loop()
  23. results = loop.run_until_complete(asyncio.gather(*tasks))
  24. loop.close()

asyncio-示例2

  1. import asyncio
  2. from aiohttp import ClientSession
  3.  
  4. async def func1(url):
  5. print(url)
  6. async with ClientSession() as session:
  7. async with session.get(url) as response:
  8. response = await response.read()
  9. print('Response OK')
  10.  
  11. tasks = [
  12. asyncio.ensure_future(func1('https://www.cnblogs.com/')),
  13. asyncio.ensure_future(func1('https://www.baidu.com/')),
  14. asyncio.ensure_future(func1('https://www.python.org/')),
  15. ]
  16.  
  17. loop = asyncio.get_event_loop()
  18. loop.run_until_complete(asyncio.gather(*tasks))

asyncio + aiohttp

  1. import asyncio
  2. import requests
  3.  
  4. @asyncio.coroutine
  5. def fetch_async(func, *args):
  6. loop = asyncio.get_event_loop()
  7. future = loop.run_in_executor(None, func, *args)
  8. response = yield from future
  9. print(response.url, response.content)
  10.  
  11. tasks = [
  12. fetch_async(requests.get, 'http://www.cnblogs.com/alex/'),
  13. fetch_async(requests.get, 'http://dig.chouti.com/pic/show?nid=4073644713430508&lid=10273091')
  14. ]
  15.  
  16. loop = asyncio.get_event_loop()
  17. results = loop.run_until_complete(asyncio.gather(*tasks))
  18. loop.close()

asyncio + requests

  1. import gevent
  2.  
  3. import requests
  4. from gevent import monkey
  5.  
  6. monkey.patch_all()
  7.  
  8. def fetch_async(method, url, req_kwargs):
  9. print(method, url, req_kwargs)
  10. response = requests.request(method=method, url=url, **req_kwargs)
  11. print(response.url, response.content)
  12.  
  13. # ##### 发送请求 #####
  14. gevent.joinall([
  15. gevent.spawn(fetch_async, method='get', url='https://www.python.org/', req_kwargs={}),
  16. gevent.spawn(fetch_async, method='get', url='https://www.yahoo.com/', req_kwargs={}),
  17. gevent.spawn(fetch_async, method='get', url='https://github.com/', req_kwargs={}),
  18. ])
  19.  
  20. # ##### 发送请求(协程池控制最大协程数量) #####
  21. # from gevent.pool import Pool
  22. # pool = Pool(None)
  23. # gevent.joinall([
  24. # pool.spawn(fetch_async, method='get', url='https://www.python.org/', req_kwargs={}),
  25. # pool.spawn(fetch_async, method='get', url='https://www.yahoo.com/', req_kwargs={}),
  26. # pool.spawn(fetch_async, method='get', url='https://www.github.com/', req_kwargs={}),
  27. # ])

gevent + requests

  1. import grequests
  2.  
  3. request_list = [
  4. grequests.get('http://httpbin.org/delay/1', timeout=0.001),
  5. grequests.get('http://fakedomain/'),
  6. grequests.get('http://httpbin.org/status/500')
  7. ]
  8.  
  9. # ##### 执行并获取响应列表 #####
  10. # response_list = grequests.map(request_list)
  11. # print(response_list)
  12.  
  13. # ##### 执行并获取响应列表(处理异常) #####
  14. # def exception_handler(request, exception):
  15. # print(request,exception)
  16. # print("Request failed")
  17.  
  18. # response_list = grequests.map(request_list, exception_handler=exception_handler)
  19. # print(response_list)

grequests

  1. from twisted.web.client import getPage
  2. from twisted.internet import reactor
  3.  
  4. REV_COUNTER = 0
  5. REQ_COUNTER = 0
  6.  
  7. def callback(contents):
  8. print(contents,)
  9.  
  10. global REV_COUNTER
  11. REV_COUNTER += 1
  12. if REV_COUNTER == REQ_COUNTER:
  13. reactor.stop()
  14.  
  15. url_list = ['http://www.bing.com', 'http://www.baidu.com', ]
  16. REQ_COUNTER = len(url_list)
  17. for url in url_list:
  18. print(url)
  19. deferred = getPage(bytes(url, encoding='utf8'))
  20. deferred.addCallback(callback)
  21. reactor.run()

twisted-示例1

  1. from twisted.internet import reactor
  2. from twisted.web.client import getPage
  3. import urllib.parse
  4.  
  5. def one_done(arg):
  6. print(arg)
  7. reactor.stop()
  8.  
  9. post_data = urllib.parse.urlencode({'check_data': 'adf'})
  10. post_data = bytes(post_data, encoding='utf8')
  11. headers = {b'Content-Type': b'application/x-www-form-urlencoded'}
  12. response = getPage(bytes('http://dig.chouti.com/login', encoding='utf8'),
  13. method=bytes('POST', encoding='utf8'),
  14. postdata=post_data,
  15. cookies={},
  16. headers=headers)
  17. response.addBoth(one_done)
  18.  
  19. reactor.run()

twisted-示例2

  1. from tornado.httpclient import AsyncHTTPClient
  2. from tornado.httpclient import HTTPRequest
  3. from tornado import ioloop
  4.  
  5. def handle_response(response):
  6. """
  7. 处理返回值内容(需要维护计数器,来停止IO循环),调用 ioloop.IOLoop.current().stop()
  8. :param response:
  9. :return:
  10. """
  11. if response.error:
  12. print("Error:", response.error)
  13. else:
  14. print(response.body)
  15.  
  16. def func():
  17. url_list = [
  18. 'http://www.baidu.com',
  19. 'http://www.bing.com',
  20. ]
  21. for url in url_list:
  22. print(url)
  23. http_client = AsyncHTTPClient()
  24. http_client.fetch(HTTPRequest(url), handle_response)
  25.  
  26. ioloop.IOLoop.current().add_callback(func)
  27. ioloop.IOLoop.current().start()

tornado

以上均是Python内置以及第三方模块提供异步IO请求模块,使用简便大大提高效率,而对于异步IO请求的本质则是【非阻塞Socket】+【IO多路复用】:

  1. import select
  2. import socket
  3. import time
  4.  
  5. class AsyncTimeoutException(TimeoutError):
  6. """
  7. 请求超时异常类
  8. """
  9.  
  10. def __init__(self, msg):
  11. self.msg = msg
  12. super(AsyncTimeoutException, self).__init__(msg)
  13.  
  14. class HttpContext(object):
  15. """封装请求和相应的基本数据"""
  16.  
  17. def __init__(self, sock, host, port, method, url, data, callback, timeout=5):
  18. """
  19. sock: 请求的客户端socket对象
  20. host: 请求的主机名
  21. port: 请求的端口
  22. port: 请求的端口
  23. method: 请求方式
  24. url: 请求的URL
  25. data: 请求时请求体中的数据
  26. callback: 请求完成后的回调函数
  27. timeout: 请求的超时时间
  28. """
  29. self.sock = sock
  30. self.callback = callback
  31. self.host = host
  32. self.port = port
  33. self.method = method
  34. self.url = url
  35. self.data = data
  36.  
  37. self.timeout = timeout
  38.  
  39. self.__start_time = time.time()
  40. self.__buffer = []
  41.  
  42. def is_timeout(self):
  43. """当前请求是否已经超时"""
  44. current_time = time.time()
  45. if (self.__start_time + self.timeout) < current_time:
  46. return True
  47.  
  48. def fileno(self):
  49. """请求sockect对象的文件描述符,用于select监听"""
  50. return self.sock.fileno()
  51.  
  52. def write(self, data):
  53. """在buffer中写入响应内容"""
  54. self.__buffer.append(data)
  55.  
  56. def finish(self, exc=None):
  57. """在buffer中写入响应内容完成,执行请求的回调函数"""
  58. if not exc:
  59. response = b''.join(self.__buffer)
  60. self.callback(self, response, exc)
  61. else:
  62. self.callback(self, None, exc)
  63.  
  64. def send_request_data(self):
  65. content = """%s %s HTTP/1.0\r\nHost: %s\r\n\r\n%s""" % (
  66. self.method.upper(), self.url, self.host, self.data,)
  67.  
  68. return content.encode(encoding='utf8')
  69.  
  70. class AsyncRequest(object):
  71. def __init__(self):
  72. self.fds = []
  73. self.connections = []
  74.  
  75. def add_request(self, host, port, method, url, data, callback, timeout):
  76. """创建一个要请求"""
  77. client = socket.socket()
  78. client.setblocking(False)
  79. try:
  80. client.connect((host, port))
  81. except BlockingIOError as e:
  82. pass
  83. # print('已经向远程发送连接的请求')
  84. req = HttpContext(client, host, port, method, url, data, callback, timeout)
  85. self.connections.append(req)
  86. self.fds.append(req)
  87.  
  88. def check_conn_timeout(self):
  89. """检查所有的请求,是否有已经连接超时,如果有则终止"""
  90. timeout_list = []
  91. for context in self.connections:
  92. if context.is_timeout():
  93. timeout_list.append(context)
  94. for context in timeout_list:
  95. context.finish(AsyncTimeoutException('请求超时'))
  96. self.fds.remove(context)
  97. self.connections.remove(context)
  98.  
  99. def running(self):
  100. """事件循环,用于检测请求的socket是否已经就绪,从而执行相关操作"""
  101. while True:
  102. r, w, e = select.select(self.fds, self.connections, self.fds, 0.05)
  103.  
  104. if not self.fds:
  105. return
  106.  
  107. for context in r:
  108. sock = context.sock
  109. while True:
  110. try:
  111. data = sock.recv(8096)
  112. if not data:
  113. self.fds.remove(context)
  114. context.finish()
  115. break
  116. else:
  117. context.write(data)
  118. except BlockingIOError as e:
  119. break
  120. except TimeoutError as e:
  121. self.fds.remove(context)
  122. self.connections.remove(context)
  123. context.finish(e)
  124. break
  125.  
  126. for context in w:
  127. # 已经连接成功远程服务器,开始向远程发送请求数据
  128. if context in self.fds:
  129. data = context.send_request_data()
  130. context.sock.sendall(data)
  131. self.connections.remove(context)
  132.  
  133. self.check_conn_timeout()
  134.  
  135. if __name__ == '__main__':
  136. def callback_func(context, response, ex):
  137. """
  138. :param context: HttpContext对象,内部封装了请求相关信息
  139. :param response: 请求响应内容
  140. :param ex: 是否出现异常(如果有异常则值为异常对象;否则值为None)
  141. :return:
  142. """
  143. print(context, response, ex)
  144.  
  145. obj = AsyncRequest()
  146. url_list = [
  147. {'host': 'www.google.com', 'port': 80, 'method': 'GET', 'url': '/', 'data': '', 'timeout': 5,
  148. 'callback': callback_func},
  149. {'host': 'www.baidu.com', 'port': 80, 'method': 'GET', 'url': '/', 'data': '', 'timeout': 5,
  150. 'callback': callback_func},
  151. {'host': 'www.bing.com', 'port': 80, 'method': 'GET', 'url': '/', 'data': '', 'timeout': 5,
  152. 'callback': callback_func},
  153. ]
  154. for item in url_list:
  155. print(item)
  156. obj.add_request(**item)
  157.  
  158. obj.running()

自定义异步IO

【Python之路】异步IO的更多相关文章

  1. [译]Python中的异步IO:一个完整的演练

    原文:Async IO in Python: A Complete Walkthrough 原文作者: Brad Solomon 原文发布时间:2019年1月16日 翻译:Tacey Wong 翻译时 ...

  2. 【Python】【异步IO】

    # [[异步IO]] # [协程] '''协程,又称微线程,纤程.英文名Coroutine. 协程的概念很早就提出来了,但直到最近几年才在某些语言(如Lua)中得到广泛应用. 子程序,或者称为函数,在 ...

  3. Python之路,Day9 , IO多路复用(番外篇)

    同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案是不同的.所以先限定一下本文的上下文. 本文讨论的背景是Linux环境下的network IO. ...

  4. python 并发编程 异步IO模型

    异步IO(Asynchronous I/O) Linux下的asynchronous IO其实用得不多,从内核2.6版本才开始引入.先看一下它的流程: 用户进程发起read操作之后,立刻就可以开始去做 ...

  5. Python 协程/异步IO/Select\Poll\Epoll异步IO与事件驱动

    1 Gevent 协程 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到 ...

  6. Python 事件驱动与异步IO

    一.事件驱动编程是一种编程范式,这里程序的执行流由外部事件来决定.它的特点是包含一个事件循环,当外部事件发生时使用回调机制来出发相应的处理.另外两种常见的编程范式是(单线程)同步以及多线程编程. 1. ...

  7. python之路之io多路复用

    1.实现io多路复用利用select s1同时接受三个客户端(开启了三个服务器端口) #!/usr/bin/env python # -*- coding: utf-8 -*- import sock ...

  8. Python(3)---从迭代器到异步IO

    whenif 关注 2017.02.13 23:48* 字数 1750 阅读 250评论 0喜欢 8 目录 1. 迭代(iteration)与迭代器(iterator) 1.1 构建简单迭代器 1.2 ...

  9. python异步IO编程(二)

    python异步IO编程(二) 目录 开门见山 Async IO设计模式 事件循环 asyncio 中的其他顶层函数 开门见山 下面我们用两个简单的例子来让你对异步IO有所了解 import asyn ...

  10. 【译】深入理解python3.4中Asyncio库与Node.js的异步IO机制

    转载自http://xidui.github.io/2015/10/29/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3python3-4-Asyncio%E5%BA%93% ...

随机推荐

  1. 19牛客暑期多校 round1 A 有关笛卡尔树的结论

    题目传送门//res tp nowcoder 分析 定理:B1~B2当且仅当B1与B2有同构的笛卡尔树. (B₁~B₂ iff B₁ and B₂ have isomorphic Cartesian ...

  2. selenium的使用与chromedriver的下载配置

    Selenium是一个web自动化测试工具,最初是为网站自动化测试而开发的,Selenium可以直接运行在浏览器上,它支持所有主流的浏览器,可以接受指令,让浏览器自动加载页面,获得需要的数据,甚至页面 ...

  3. curl post请求封装

    /* POST /servlet/ICBCCMPAPIReqServlet?userID=jyi.y.1001&PackageID=201807311347539185&SendTim ...

  4. rbac权限控制组件实现控制的基本原理图

    今天先整理一个rbac的权限控制的原理图上来 代码 后面就不透漏了,但是实现的方法有很多种,我这个只是其中一种的一部分!

  5. 运用加密技术保护Java源代码(转)

    出处:运用加密技术保护Java源代码 为什么要加密? 对于传统的C或C++之类的语言来说,要在Web上保护源代码是很容易的,只要不发布它就可以.遗憾的是,Java程序的源代码很容易被别人偷看.只要有一 ...

  6. 怎样使用构造函数: Vue()?

    1. 新建一个 .html 文件 => 引入一个在线的 vue 库 => 写一个带 id 的 html 标签 => 写一个 script 标签, 这里的 vApp 是 Vue() 这 ...

  7. mybatis+oracle批量插入报不符合协议和sql未正确结束

    在Java中循环save,需要加useGeneratedKeys="false",否则报错不符合协议 mybatis批量插入,也需要在insert里加入 useGeneratedK ...

  8. 阿里云语音合成(汉语英语)带UI界面的小程序(python)

    一,项目说明 将汉文转汉语.英文转英语,同时又有逗号<###English###>,<,,,>和句号<...>标志符用于文件处理.其中英文包含在### 英文 ### ...

  9. Asp.Net Core 中间件

    什么是中间件(Middleware)? 中间件是组装到应用程序管道中以处理请求和响应的软件. 每个组件: 选择是否将请求传递给管道中的下一个组件. 可以在调用管道中的下一个组件之前和之后执行工作. 请 ...

  10. RAII惯用法:C++资源管理的利器(转)

    RAII惯用法:C++资源管理的利器 RAII是指C++语言中的一个惯用法(idiom),它是“Resource Acquisition Is Initialization”的首字母缩写.中文可将其翻 ...