python 3.x asyncio与ThreadPoolExecutor结合使用,完成阻塞IO的请求
asyncio与ThreadPoolExecutor结合使用,完成阻塞IO的请求
- 协程模式是单线程的,为什么又多出一个多线程呢???
- asyncio是异步IO编程的解决方案,异步IO是包含多线程,多进程以及协程.
- 所以asyncio即可完成多线程(多进程)也可以完成协程.
- 所以在协程里面也可以使用多线程.
- 什么时候在协程中使用多线程呢???
- 协程里面是不能加入阻塞IO的,但是某些时候(比如说某一个库或者说某一个接口,只能提供阻塞怎么办?实际很简单把它放在一个线程中去做即可)
- 在协程中集成阻塞IO,比如说MySQL库是阻塞的,PyMySQL和MySQLClient都是阻塞的,那在协程中如果去强行使用MySQL这三个库怎么办?就可以使用多线程
- 在asyncio中集成线程池
- run_in_executor(self, executor, func, *args):将某个阻塞IO的函数放到executor(ThreadPoolExecutor)中运行.
- executor: 线程池执行者
- func: 函数名
- *args: 函数的参数
- run_in_executor(self, executor, func, *args):将某个阻塞IO的函数放到executor(ThreadPoolExecutor)中运行.
run_in_executor(self, executor, func, *args)源码
1 def run_in_executor(self, executor, func, *args): 2 self._check_closed() 3 if self._debug: 4 self._check_callback(func, 'run_in_executor') 5 if executor is None: 6 executor = self._default_executor 7 8 # 1.如果executor为None的话,就会声明一个ThreadPoolExecutor() 9 if executor is None: 10 executor = concurrent.futures.ThreadPoolExecutor() 11 12 # 2.将executor赋值给_default_executor,所以说将executor放到self里面(实例变量)也就是loop里面,作为一个变量 13 self._default_executor = executor 14 15 # wrap_future()方法将executor.submit()执行后返回的future对象,封装成新的futures(也就是协程的futures) 16 return futures.wrap_future(executor.submit(func, *args), loop=self)
submit(self, fn, *args, **kwargs)源码
1 def submit(self, fn, *args, **kwargs): 2 with self._shutdown_lock: 3 if self._shutdown: 4 raise RuntimeError('cannot schedule new futures after shutdown') 5 6 f = _base.Future() 7 w = _WorkItem(f, fn, args, kwargs) 8 9 self._work_queue.put(w) 10 self._adjust_thread_count() 11 12 # 返回future对象. 13 return f
1 import asyncio 2 from concurrent.futures import ThreadPoolExecutor 3 import socket 4 # urlparse的作用是对url进行解析. 5 from urllib.parse import urlparse 6 import time 7 8 9 # get_url是一个阻塞的接口.将阻塞接口与asyncio结合起来. 10 def get_url(url): 11 new_url = urlparse(url) 12 host = new_url.netloc 13 path = new_url.path 14 if path == "": 15 path = "/" 16 17 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 18 client.connect((host, 80)) # 阻塞不会消耗cpu 19 client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(path, host).encode("utf8")) 20 21 data = b"" 22 while True: 23 re_data = client.recv(1024) 24 if re_data: 25 data += re_data 26 else: 27 break 28 29 data = data.decode("utf8") 30 html_data = data.split("\r\n\r\n")[1] 31 print(html_data) 32 client.close() 33 34 35 if __name__ == "__main__": 36 start_time = time.time() 37 loop = asyncio.get_event_loop() 38 executor = ThreadPoolExecutor(3) 39 # 要在asyncio中等待task的执行完成就要使用run_until_complete()方法,而这个方法接收一个list参数,所以需要声明一个list 40 tasks = [] 41 # 为了测试asyncio与ThreadPoolExecutor结合的性能,一次性放20个函数进来. 42 for i in range(1, 21): 43 # 获取某一个商品的详情的url,随机获取前20个商品详情的url 44 url = "http://shop.projectsedu.com/goods/{}/".format(i) 45 # 这个提交过程是快速返回的.是asyncio的task 46 # 通过run_in_executor()方法的源码得知,将线程的future执行了一个包装,包装成协程的future, 47 # 这样的话,就可以进行await,所以返回一个task. 48 # run_in_executor()方法运行原理还是在内部去声明一个线程池,然后这里面的阻塞代码放到线程池里面去运行. 49 # 与线程池是一样的没有多高的性能. 50 task = loop.run_in_executor(executor, get_url, url) 51 tasks.append(task) 52 # 很简单的将阻塞的url放到loop中,同时也不会影响协程的运行. 53 loop.run_until_complete(asyncio.gather(*tasks)) 54 55 print("code run finished use time: {}".format(time.time() - start_time)) 56 57 """ 58 输出结果: 59 code run finished use time: 8.041460037231445 60 61 通过运行的时间得出结论,这种性能无非还是将一些代码放到线程池中运行.避免不了线程池的一些问题. 62 在使用协程模式的时候,在协程里面不要去调用阻塞方式.但是一定要在协程中调用阻塞方式,就可以把阻塞方式拿到线程池中运行. 63 这就是即将要说明的如何将线程池与asyncio相结合.来模拟http请求. 64 """
********
posted on 2019-05-22 09:22 jaydenjune 阅读(1457) 评论(0) 收藏 举报
浙公网安备 33010602011771号