关于io 密集型 和 cpu密集型
https://blog.csdn.net/youanyyou/article/details/78990156
协程:Coroutine
一句话说就是一种用户态的轻量级线程。实现了单线程下并发的效果。
优点:
1、无需线程上下文的切换,还是单线程,只是函数之间的切换。
2、无需原子操作锁定及同步的开销。因为协程就是单线程。不需要锁,数据同步等的需要。
3、方便切换控制流,简化编程模型
4、高并发+高扩展+低成本:一个cpu支持上万的协程都是没有问题,所以适合高并发处理。
缺点:
1、无法利用多核资源,需要和进程配合才能运行在多cpu上。
2、协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
协程,又称微线程,纤程。协程是一种用户态的轻量级线程。
协程拥有自己的寄存器和栈。协程调度切换时,将寄存器上下文和栈保存在其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。
因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置
- 实例一:利用yield实现单线程下并发
import time
import queue
def consumer(name):
print("--->starting eating baozi...")
while True:
new_baozi = yield #
print("[%s] is eating baozi %s" % (name, new_baozi))
# time.sleep(1)
def producer():
r = con.__next__() #开始执行
r = con2.__next__()
n = 0
while n < 5:
n += 1
con.send(n) #唤醒生成器的同时并且传值
con2.send(n)
time.sleep(1)
print("\033[32;1m[producer]\033[0m is making baozi %s" % n)
if __name__ == '__main__':
con = consumer("c1") #只是生成器,next执行
con2 = consumer("c2")
producer()
上面这个程序是我们自己写的一个类似于协程的例子,它没有实现遇到io自动切换。那么,怎么样的程序才算协程呢?
1、必须在只有一个单线程里实现并发
2、修改共享数据不需要加锁
3、用户程序里自己保存多个控制流的上下栈
4、一个协程遇到io操作自动切换到其他协程(那么整个程序就只有cpu在运算)
- 实例二:greenlet实现遇到io手动切换
from greenlet import greenlet
def test1():
print(12)
gr2.switch() #手动切换
print(34)
gr2.switch()
def test2():
print(56)
gr1.switch()
print(78)
gr1 = greenlet(test1) #启动一个协程
gr2 = greenlet(test2)
gr1.switch() #先执行gr1
F:\anaconda\python.exe F:/web/s14/进程、线程/noke.py
12
56
34
78
- 实例三:gevent对greenlet封装实现了自动切换.
注意:整体是按2s运行的程序,但是有50处io,49个1s,1个2s,就只需要2s。
import gevent
def func1():
print('\033[31;1m李闯在跟海涛搞...\033[0m')
gevent.sleep(2) #模拟io
print('\033[31;1m李闯又回去跟继续跟海涛搞...\033[0m')
def func2():
print('\033[32;1m李闯切换到了跟海龙搞...\033[0m')
gevent.sleep(1) #停顿一秒
print('\033[32;1m李闯搞完了海涛,回来继续跟海龙搞...\033[0m')
def func3():
print("running func3")
gevent.sleep(0)
print("again")
gevent.joinall([
gevent.spawn(func1), #生成一个协程
gevent.spawn(func2),
gevent.spawn(func3),
])
F:\anaconda\python.exe F:/web/s14/进程、线程/noke.py
李闯在跟海涛搞...
李闯切换到了跟海龙搞...
running func3
again
李闯搞完了海涛,回来继续跟海龙搞...
李闯又回去跟继续跟海涛搞...
- 实例四:通过gevent实现协程并发爬取网页
from urllib import request
import gevent,time
from gevent import monkey
monkey.patch_all() #把当前程序的所有的io操作给我单独的做上标记 打补丁,因为gevent不知道urllib进行了io操作
#相当于sleep一下,阻塞,
def f(url):
print('GET: %s' % url)
resp = request.urlopen(url)
data = resp.read()
print('%d bytes received from %s.' % (len(data), url))
urls = [
'https://edu.hellobi.com/course/explore?c2=37&c3=26',
'https://www.baidu.com',
'https://www.cnblogs.com/alex3714/articles/5248247.html'
]
time_start = time.time()
for url in urls:
f(url)
print("同步cost",time.time() - time_start)
async_time_start = time.time() #async是异步的缩写
gevent.joinall([
gevent.spawn(f,'https://edu.hellobi.com/course/explore?c2=37&c3=26' ),
gevent.spawn(f,'https://www.baidu.com' ),
gevent.spawn(f, 'https://www.cnblogs.com/alex3714/articles/5248247.html'),
])
print("异步cost",time.time() - async_time_start)
F:\anaconda\python.exe F:/web/s14/进程、线程/noke.py
GET: https://edu.hellobi.com/course/explore?c2=37&c3=26
76534 bytes received from https://edu.hellobi.com/course/explore?c2=37&c3=26.
GET: https://www.baidu.com
227 bytes received from https://www.baidu.com.
GET: https://www.cnblogs.com/alex3714/articles/5248247.html
93323 bytes received from https://www.cnblogs.com/alex3714/articles/5248247.html.
同步cost 1.8301048278808594
GET: https://edu.hellobi.com/course/explore?c2=37&c3=26
GET: https://www.baidu.com
GET: https://www.cnblogs.com/alex3714/articles/5248247.html
227 bytes received from https://www.baidu.com.
93323 bytes received from https://www.cnblogs.com/alex3714/articles/5248247.html.
76534 bytes received from https://edu.hellobi.com/course/explore?c2=37&c3=26.
异步cost 0.6120350360870361
- 实例五:通过gevent实现单线程下多socket并发
服务端
import sys
import socket
import time
import gevent
from gevent import socket, monkey
monkey.patch_all()
def server(port):
s = socket.socket()
s.bind(('0.0.0.0', port))
s.listen(500)
while True:
cli, addr = s.accept()
gevent.spawn(handle_request, cli)
def handle_request(conn):
try:
while True:
data = conn.recv(1024)
print("recv:", data)
conn.send(data)
if not data:
conn.shutdown(socket.SHUT_WR)
except Exception as ex:
print(ex)
finally:
conn.close()
if __name__ == '__main__':
server(8001)
客户端
import socket
HOST = 'localhost' # The remote host
PORT = 8001 # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
while True:
msg = bytes(input(">>:"), encoding="utf8")
s.sendall(msg)
data = s.recv(1024)
# print(data)
print('Received', repr(data)) #repr格式化输出
s.close()