协程:
-
协程的概念:
协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。
是操作系统不可见的
单个线程并发的处理多个任务,程序控制协程的切换+保持状态
-
特点:
- 由于多线程本身就不能利用多核(协程也不能)
- 所以即便是开启了多个线程也只能轮流在一个cpu上执行
- 协程如果把所有的任务得到IO操作都规避掉,只剩下需要使用cpu的操作
- 就意味着协程就可以做到提高cpu利用率的效果
- 必须在只有一个单线程里实现并发
- 修改共享数据不需加锁
- 用户程序里自己保存多个控制流的上下文栈(保持状态)
- 一个协程遇到IO操作自动切换到其他协程
-
多线程和协程
-
线程:切换操作系统,开销大,操作系统不可控,给操作系统的压力大
- 操作系统对IO操作的感知更加灵敏
-
协程:切换需要python代码,开销小,用户操作可控,运行速度快,完全不会增加操作系统的压力
- 用户级别能够对IO操作的感知比较低
-
10个任务,并发的执行这10个任务:
并发的本质:切换 + 保持状态
1.方式一:开启多个进程并发执行,操作系统切换 + 保持状态
2.方式二:开启多个线程并发执行,操作系统切换 + 保持状态
3.方式三:开启协程并发的执行,自己的程序把控着cpu在10个任务之间来回切换 + 保持状态
-
-
协程的优缺点:
优点:
- 协程的切换开销小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
- 单线程内就可以实现并发的效果,最大限度的利用CPU
缺点:
- 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
- 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
-
协程的运用:
-
模拟协程
import time def gen(): while 1: yield 1 time.sleep(2) print(2) def func(): obj = gen() for i in range(5): print(next(obj)) func()
-
greenlet
from greenlet import greenlet import time def func1(): print(1111) g2.switch() # 切换到func2任务 time.sleep(2) # 睡眠2秒 print(2222) g2.switch() # 切换到func2任务 def func2(): print(3333) g1.switch() # 切换到func1任务 print(4444) g1 = greenlet(func1) g2 = greenlet(func2) g1.switch() # 切换到func1任务,如果需要传参在switch中写入参数
单纯的切换(在没有IO的情况下或者没有重复开辟内存空间的操作),反而会降低程序的执行速度
greenlet只是一种切换,它并没有在遇到IO的时候自动的跳转,而是人为的在会遇到IO的地方进程一个切换。
-
gevent
import gevent from threading import currentThread def func1(): print(111) print(currentThread().name) gevent.sleep(1) print(222) def func2(name): print(333) print(currentThread().name) gevent.sleep(1) print(name) print(444) g1 = gevent.spawn(func1,) g2 = gevent.spawn(func2,'小猿') print(f'主:{currentThread().name}') gevent.joinall([g1,g2])
最终版:
import gevent import time from gevent import monkey monkey.patch_all() # 打补丁:将下面的所有的任务的阻塞都打上标记 def func1(): print(111) time.sleep(1) print(222) def func2(): print(333) time.sleep(1) print(444) g1 = gevent.spawn(func1,) g2 = gevent.spawn(func2,) gevent.joinall([g1,g2])
工作中:
一般在工作中我们都是进程+线程+协程的方式来实现并发,以达到最好的并发效果,如果是4核的cpu,一般起5个进程,每个进程中20个线程(5倍cpu数量),每个线程可以起500个协程,大规模爬取页面的时候,等待网络延迟的时间的时候,我们就可以用协程去实现并发。 并发数量 = 5 * 20 * 500 = 50000个并发,这是一般一个4cpu的机器最大的并发数。nginx在负载均衡的时候最大承载量就是5w个单线程里的这20个任务的代码通常会既有计算操作又有阻塞操作,我们完全可以在执行任务1时遇到阻塞,就利用阻塞的时间去执行任务2。。。。如此,才能提高效率,这就用到了Gevent模块。 -
asynico模块
实现高并发的一个模块
import asyncio # 起一个任务 async def demo(): # 协程方法 print('start') await asyncio.sleep(1) # 阻塞 print('end') loop = asyncio.get_event_loop() # 创建一个事件循环 loop.run_until_complete(demo()) # 把demo任务丢到事件循环中去执行 启动多个任务,并且没有返回值 async def demo(): # 协程方法 print('start') await asyncio.sleep(1) # 阻塞 print('end') loop = asyncio.get_event_loop() # 创建一个事件循环 wait_obj = asyncio.wait([demo(),demo(),demo()]) loop.run_until_complete(wait_obj) 启动多个任务并且有返回值 async def demo(): # 协程方法 print('start') await asyncio.sleep(1) # 阻塞 print('end') return 123 loop = asyncio.get_event_loop() t1 = loop.create_task(demo()) t2 = loop.create_task(demo()) tasks = [t1,t2] wait_obj = asyncio.wait([t1,t2]) loop.run_until_complete(wait_obj) for t in tasks: print(t.result()) 谁先回来先取谁的结果 import asyncio async def demo(i): # 协程方法 print('start') await asyncio.sleep(10-i) # 阻塞 print('end') return i,123 async def main(): task_l = [] for i in range(10): task = asyncio.ensure_future(demo(i)) task_l.append(task) for ret in asyncio.as_completed(task_l): res = await ret print(res) loop = asyncio.get_event_loop() loop.run_until_complete(main()) import asyncio async def get_url(): reader,writer = await asyncio.open_connection('www.baidu.com',80) writer.write(b'GET / HTTP/1.1\r\nHOST:www.baidu.com\r\nConnection:close\r\n\r\n') all_lines = [] async for line in reader: data = line.decode() all_lines.append(data) html = '\n'.join(all_lines) return html async def main(): tasks = [] for url in range(20): tasks.append(asyncio.ensure_future(get_url())) for res in asyncio.as_completed(tasks): result = await res print(result) if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(main()) # 处理一个任务 python原生的底层的协程模块 爬虫 webserver框架 题高网络编程的效率和并发效果 语法 await 阻塞 协程函数这里要切换出去,还能保证一会儿再切回 await 必须写在async函数里,async函数是协程函数 loop 事件循环 所有的协程的执行 调度 都离不开这个loop
-
-
线程queue
多线程抢占资源: 只能让其串行.
-
互斥锁.
-
队列.
import queue # 队列 q = queue.Queue(4) q.put('小猿') q.put('大猿') q.put('帅气的大猿') q.put('漂亮的小猿') print(q.get()) print(q.get()) print(q.get()) print(q.get()) 结果: 小猿 大猿 帅气的大猿 漂亮的小猿
-
堆栈
import queue # 堆栈 q = queue.LifoQueue(4) q.put('小猿') q.put('大猿') q.put('大猿很帅') q.put('小猿很漂亮') print(q.get()) print(q.get()) print(q.get()) print(q.get()) 结果: 小猿很漂亮 大猿很帅 大猿 小猿
-
优先级队列
import queue q = queue.PriorityQueue(4) q.put((1,'大猿')) q.put((2,'小猿')) q.put((-2,'小明')) q.put((-1,'小华')) print(q.get()) print(q.get()) print(q.get()) print(q.get()) 结果: (-2, '小明') (-1, '小华') (1, '大猿') (2, '小猿')
-
-
事件event
版本一:
如果程序中的其他线程需要通过判断某个线程的状态来确定自己下一步的操作
from threading import Thread,currentThread import time flag = False def check(): print(f'{currentThread().name} 检测服务器是否开启...') time.sleep(3) global flag flag = True print('服务器已经开启...') def connect(): while 1: print(f'{currentThread().name} 等待连接...') time.sleep(0.5) if flag: print(f'{currentThread().name} 链接成功...') break t1 = Thread(target=check,) t2 = Thread(target=connect,) t1.start() t2.start()
开启两个线程,一个线程运行到中间的某个阶段,触发另个线程执行.两个线程增加了耦合性.
event:
from threading import Thread,currentThread,Event import time event = Event() def check(): print(f'{currentThread().name} 检测服务器是否开启...') time.sleep(4) event.set() print('服务器已经开启...') def connect(): count = 1 while not event.is_set(): if count == 4: print('连接次数过多,已断开') break event.wait(1) print(f'{currentThread().name} 尝试连接{count}次') count += 1 else: print(f'{currentThread().name} 连接成功') t1 = Thread(target=check,) t2 = Thread(target=connect,) t1.start() t2.start()