- 协程
- greenlet模块 (gevent实现原理)
- gevent模块 (注册协程,实现异步编程)
- 协程的应用
eg:爬虫、socket聊天
协程
-
概念
- 能够在一个线程中实现并发效果。
- 能够规避一些任务中的IO操作,在检测到IO就切换到其他任务
-
对比进程和线程
- 进程和线程的任务切换由操作系统完成
- 协程任务之间的切换由程序(代码)完成,只有遇到协程模块能识别的IO操作的时候,程序才会进行任务切换,实现并发的效果
- 进程、线程、协程的开启数量:
cpu核心数:4
进程 开5个 (cpu数+1)
线程 开20个 (进程数4)
协程 开5w个 (进程数线程数*500)一个线程能开500个协程
greenlet模块
真正的协程模块就是使用greenlet完成切换的
# 多任务之间切换的方法 greenlet模块 (协程)
from greenlet import greenlet
def eat():
print('eating start')
g2.switch() # 切换到对应的方法,接着执行
print('eating end')
def play():
print('playing start')
g2 = greenlet(play)
g1 = greenlet(eat)
g1.switch()
gevent模块
- gevent 感知不到其他模块(time或其他阻塞)的阻塞。只能用gevent.sleep()
- 不过可以通过打补丁实现感知time 、 socket的阻塞 from gevent import monkey;monkey.patch_all()
此行代码需要放在文件的开头,在import time之前
gevent方法:
-
g1 = gevent.spawn(func)
将func注册到协程 spawn(func, *args, **kwargs) -
g1.join()
激活协程,等待g1结束 -
g1.value
拿到协程对象的返回值(就是协程执行的func的返回值) -
gevent.joinall(iterator)
将多个协程对象保存为一个可迭代对象,传入joinall方法,等待所有协程结束
import gevent
def eat():
print('eating start')
gevent.sleep(2) # gevent 感知不到其他模块(time或其他阻塞)的阻塞。只能用gevent.sleep()
# 不过可以通过打补丁实现感知time 、 socket的阻塞 from gevent import monkey;monkey.patch_all()
print('eating end')
def play():
print('playing start')
gevent.sleep(2)
print('playing end')
g1 = gevent.spawn(eat) # 将func注册在协程中
g2 = gevent.spawn(play)
g1.join() # 等待协程执行完毕
g2.join()
from gevent import monkey;monkey.patch_all()
import time
import gevent
def eat(i):
print('eating start')
time.sleep(2) # gevent 感知不到其他模块(time或其他阻塞)的阻塞。只能用gevent.sleep()
# 不过可以通过打补丁实现感知time 、 socket的阻塞 from gevent import monkey:monkey.patch_all()
print('eating end')
return '%send'%i
def play():
print('playing start')
time.sleep(2)
print('playing end')
g_list = []
ge_list = []
for i in range(10):
ge = gevent.spawn(play)
ge_list.append(ge)
for i in range(20):
g = gevent.spawn(eat, i)
g_list.append(g)
for ge in ge_list: # 等待所有协程执行完毕的 方法一
ge.join()
gevent.joinall(g_list) # 等待所有协程执行完毕的 方法二
for g in g_list:
print(g.value) # 获得func的返回值
协程的应用
爬虫
获取网页内容
方法一:requests
import requests
def get_content(url):
content = requests.get(url) # 通过request.get到的网页内容是无格式的
return content.content.decode('utf-8')
url = 'http://www.baidu.com'
print(get_content(url))
方法二:urlopen
from urllib.request import urlopen
def get_content(url):
content = urlopen(url) # urlopen到的网页内容是有格式的
return content.read().decode('utf-8')
url = 'http://www.baidu.com'
print(get_content(url))
协程来爬取网页
from gevent import monkey;monkey.patch_all()
import gevent
import ssl
from urllib.request import urlopen, Request
def get_content(url):
context = ssl._create_unverified_context()
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20100101 Firefox/23.0'}
get_url = Request(url=url, headers=headers)
content = urlopen(get_url, context=context) # urlopen到的网页内容是有格式的
text = content.read().decode('utf-8')
return len(text)
url_list = ['http://www.baidu.com',
'http://www.jianshu.com',
'http://www.csdn.com',
'http://www.cctv.com',
'http://www.sogou.com']
# 通过协程来并发获得网页内容的长度
g_list = []
for i in range(len(url_list)):
g = gevent.spawn(get_content, url_list[i])
g_list.append(g)
for g in g_list:g.join()
for g in g_list:print(g.value)
socket
server端
from gevent import monkey;monkey.patch_all()
import gevent
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()
def talk(conn):
conn.send(b'hello')
msg = conn.recv(1024).decode('utf-8')
print(msg)
conn.close()
while True:
conn, addr = sk.accept()
gevent.spawn(talk, conn)
sk.close()
client端
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 8080))
msg = sk.recv(1024).decode('utf-8')
print(msg)
send_msg = input('>>>')
sk.send(send_msg.encode('utf-8'))
sk.close()
网友评论