python --- 协程编程(第三方库gevent的使用)

简介: 1. 什么是协程?  协程(coroutine),又称微线程。协程不是线程也不是进程,它的上下文关系切换不是由CPU控制,一个协程由当前任务切换到其他任务由当前任务来控制。一个线程可以包含多个协程,对于CPU而言,不存在协程这个概念,它是一种轻量级用户态线程(即只针对用户而言)。

1. 什么是协程?

  协程(coroutine),又称微线程。协程不是线程也不是进程,它的上下文关系切换不是由CPU控制,一个协程由当前任务切换到其他任务由当前任务来控制。一个线程可以包含多个协程,对于CPU而言,不存在协程这个概念,它是一种轻量级用户态线程(即只针对用户而言)。协程拥有自己的寄存器上下文和栈,协程调度切换到其他协程时,将寄存器上下文和栈保存,在切回到当前协程的时候,恢复先前保存的寄存器上下文和栈。

2. 在编程中为什么要使用协程?

  使用协程的好处:(1)CPU无需负担上下文的开销;(2)不需加锁(多个线程操作数据时得加锁);(3)由程序员切换控制流,方便编程;(4)高并发、高扩展、低成本(一个CPU支持上万的协程都不是问题)。

   当然,任何事物有优点必有缺点。协程得缺点:(1)协程自己无法利用CPU多核资源(除非与多进程或者多线程配合);(2)遇到阻塞操作会使整个程序阻塞。

 

例一(使用yield实现在任务间的切换):

 1 import time
 2 
 3 def func1(name):
 4     print("----func1 start...----")
 5     for i in range(6):
 6         temp = yield      #每次遇到yield,func1在此处阻塞,直到temp接收到func2中con.send()传来的值
 7         print("%s in the func1" % (str(temp)))
 8         time.sleep(1)
 9 
10 
11 def func2():
12     print("----func2 start...----")
13     con.__next__()     #此处开始真正的func1的调用
14     for i in range(5):
15         con.send(i+1)
16         print("%s in the func2" % i)
17 
18 
19 if __name__ == '__main__':
20     con = func1(1)     #在有yield的函数中此处不是真正的函数调用,打印con便可知道
21     # print(con)
22     p = func2()
使用yield进行任务切换

  注:例一严格来说不能算是协程,只是实现了两个任务之间的切换。

 

3. 既然例一不能算多协程,难么在python中应该如何使用协程?

  greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator(例一中的con=func1(1)就是做这个操作)。

 

例二:

 1 import greenlet
 2 
 3 def func1():
 4     for i in range(1,6):
 5         print(i)
 6         g2.switch()   #切换到g2
 7 
 8 def func2():
 9     words = ['a', 'b', 'c', 'd', 'e']
10     for w in words:
11         print(w)
12         g1.switch()    #切换到g1
13 
14 g1 = greenlet.greenlet(func1)
15 g2 = greenlet.greenlet(func2)
16 g1.switch()   #切换到g1
使用greenlent模块实现任务切换

  注:使用greenlent可以很简单的进行多任务之间的切换,但是程序运行最耗时的便是I/O操作,要使用协程实现高并发,应当是一旦遇到I/O操作就切换到其他任务,等I/O操作完成后在切回到当前任务(这个过程应当是自动的)。

 

4. 那么在python中,如何让任务遇到I/O操作就切换?

  我们使用第三方库gevent来实现。

  gevent的官方定义:gevent is a coroutine -based Python networking library that uses greenlet to provide a high-level synchronous API on top of the libev event loop.

 

例三(gevent的简单使用):

 1 import gevent
 2 import time
 3 
 4 def func1():
 5     print("func1: start.....")
 6     # Put the current greenlet to sleep for at least *seconds*.(模拟I/O操作,任务在此处自动切换)
 7     gevent.sleep(3)
 8     print("func1: end")
 9 
10 def func2():
11     print("func2: start.....")
12     gevent.sleep(0.5)
13     print("func2: end")
14 
15 start_time = time.time()
16 # joinall(greenlets, timeout=None, raise_error=False, count=None)
17 # Wait for the ``greenlets`` to finish.
18 # :return: A sequence of the greenlets that finished before the timeout (if any)expired.
19 gevent.joinall([gevent.spawn(func1),
20                 gevent.spawn(func2)])
21 # spawn(cls, *args, **kwargs)
22 # Create a new :class:`Greenlet` object and schedule it to run ``function(*args, **kwargs)``.
23 # This can be used as ``gevent.spawn`` or ``Greenlet.spawn``.
24 
25 print("cost:", time.time()-start_time)
26 # 通过计算程序运行的时间可以发现程序确实是以单线程达模拟出了多任务并行的操作。
gevent的简单使用

 

例四(gevent和urllib配合同时下载多个网页):

 1 import urllib.request
 2 import gevent,time
 3 import gevent.monkey
 4 
 5 def func(url="", filename=""):
 6     print("Download:%s" % url)
 7     result = urllib.request.urlopen(url)       #请求打开一个网页
 8     data = result.read()     #读取内容
 9     with open(filename, 'wb') as fp:    #写入文档
10         fp.write(data)
11     print("Finish:%s" % url)
12 
13 if __name__ == "__main__":
14     # Do all of the default monkey patching (calls every other applicablefunction in this module).
15     # 相当与做一个标记,做完此操作gevent就可以检测到此程序中所有的I/O操作
16     gevent.monkey.patch_all()
17 
18     async_time = time.time()
19     gevent.joinall([
20         gevent.spawn(func, "http://www.cnblogs.com/God-Li/p/7774497.html", "7774497.html"),
21         gevent.spawn(func, "http://www.gevent.org/", "gevent.html"),
22         gevent.spawn(func, "https://www.python.org/", "python.html"),
23     ])
24     print("async download cost:", time.time()-async_time)
25 
26     start_time = time.time()
27     func("http://www.cnblogs.com/God-Li/p/7774497.html", "7774497.html")
28     func("http://www.gevent.org/", "gevent.html")
29     func("https://www.python.org/", "python.html")
30     print("download cost:", time.time()-start_time)
gevent和urllib配合同时下载多个网页

  注:对上例代码稍加改造,加上对html源码的解析功能,就可以实现一个简单的多并发爬虫。

 

python --- 网络编程Socket中例二的socket_server2使用gevent改造就可以使其成为一个大并发的socket server。

例五(使用gevent实现并发的socket server):

 1 #服务端
 2 import socket
 3 import gevent
 4 import gevent.monkey
 5 
 6 gevent.monkey.patch_all()
 7 
 8 def request_handler(conn):
 9 
10     '''
11     Wait for an incoming connection.  Return a new socket
12     representing the connection, and the address of the client.
13     '''
14     while True:
15         # print("ok")
16         data = conn.recv(1024)         #接收信息,写明要接收信息的最大容量,单位为字节
17         print("server recv:", data)
18         conn.send(data.upper())       #对收到的信息处理,返回到客户端
19 
20 
21 
22 if __name__ == "__main__":
23     address = ("localhost", 6666)  # 写明服务端要监听的地址,和端口号
24     server = socket.socket()  # 生成一个socket对象
25     server.bind(address)  # 用socket对象绑定要监听的地址和端口
26     server.listen()  # 开始监听
27 
28     while True:
29         conn, addr = server.accept()  # 等带新连接接入服务端,返回一个新的socket对象和地址,地址格式同前面格式
30         gevent.spawn(request_handler, conn)
31 
32     server.close()  # 关闭服务端
socket_server2的并发实现

   注:可使用python --- 网络编程Socket中例二的socket_client2进行测试。

目录
相关文章
|
1天前
|
JSON 数据格式 开发者
pip和requests在Python编程中各自扮演着不同的角色
`pip`是Python的包管理器,用于安装、升级和管理PyPI上的包;`requests`是一个HTTP库,简化了HTTP通信,支持各种HTTP请求类型及数据交互。两者在Python环境中分别负责包管理和网络请求。
12 5
|
1天前
|
调度 Python
探索Python中的异步编程:从回调到协程
本文将介绍Python中的异步编程技术,从最初的回调函数到现代的协程模型。通过对比传统的同步编程方式和异步编程的优劣势,我们深入探讨了Python中异步编程的实现原理,以及如何利用asyncio库和async/await关键字来构建高效的异步应用程序。最后,我们还将讨论一些异步编程的最佳实践和常见问题的解决方法。
|
3天前
|
存储 Python 容器
Python高级编程
Python集合包括可变的set和不可变的frozenset,用于存储无序、不重复的哈希元素。创建集合可使用{}或set(),如`my_set = {1, 2, 3, 4, 5}`。通过add()添加元素,remove()或discard()删除元素,如`my_set.remove(3)`。
|
4天前
|
Python
Python中的协程:异步编程的利器
Python中的协程:异步编程的利器
13 1
|
4天前
|
测试技术 Python
Python模块化方式编程实践
Python模块化编程提升代码质量,包括:定义专注单一任务的模块;使用`import`导入模块;封装函数和类,明确命名便于重用;避免全局变量降低耦合;使用文档字符串增强可读性;为每个模块写单元测试确保正确性;重用模块作为库;定期维护更新以适应Python新版本。遵循这些实践,可提高代码可读性、重用性和可维护性。
24 2
|
10天前
|
测试技术 调度 索引
python编程中常见的问题
【4月更文挑战第23天】
31 2
|
10天前
|
网络协议 算法 网络架构
Python网络编程之udp编程、黏包以及解决方案、tcpserver
Python网络编程之udp编程、黏包以及解决方案、tcpserver
|
11天前
|
机器学习/深度学习 数据挖掘 算法框架/工具
Python:编程的艺术与魅力
Python:编程的艺术与魅力
24 3
|
11天前
|
缓存 安全 Linux
深入探索Python中的协程
深入探索Python中的协程
|
11天前
|
机器学习/深度学习 数据可视化 数据挖掘
实用技巧:提高 Python 编程效率的五个方法
本文介绍了五个提高 Python 编程效率的实用技巧,包括使用虚拟环境管理依赖、掌握列表推导式、使用生成器提升性能、利用装饰器简化代码结构以及使用 Jupyter Notebook 进行交互式开发。通过掌握这些技巧,可以让你的 Python 编程更加高效。