协程

协程的初识

协程本质上就是一个线程 一个线程实现并发.如果协程中处理的所有任务都遇到了阻塞 协程就会停止 只有阻塞完成会切回来 进程间是由操作系统调控cpu 而协程是由我们自己书写的程序调控的

单个cpu: 10个任务,让你给我并发的执行这个10个任务:

  1. 方式一:开启多进程并发执行, 操作系统切换+保持状态.
  2. 方式二:开启多线程并发执行,操作系统切换+保持状态.
  3. 方式三:开启协程并发的执行, 自己的程序 把控着cpu 在3个任务之间来回切换+保持状态.

协程他切换速度非常快,蒙蔽操作系统的眼睛,让操作系统认为cpu一直在运行你这一个线程(协程.)

单核心下处理多任务最好的方式

协程 开销小. 运行速度快. 协程会长期霸占cpu只执行我程序里面的所有任务.

并发的本质:就是切换+保持状态.

协程处理IO密集型, 计算密集型,还是串行好.

什么是协程? 单个线程并发的处理多个任务. 程序控制协程的切换+保持状态.

协程的特点:

  1. 必须在只有一个单线程里实现并发
  2. 修改共享数据不需加锁
  3. 用户程序里自己保存多个控制流的上下文栈(保持状态)
  4. 附加:一个协程遇到IO操作自动切换到其它协程

工作中:

​ 一般在工作中我们都是进程+线程+协程的方式来实现并发,以达到最好的并发效果,如果是4核的cpu,一般起5个进程,每个进程中20个线程(5倍cpu数量),每个线程可以起500个协程,大规模爬取页面的时候,等待网络延迟的时间的时候,我们就可以用协程去实现并发。 并发数量 = 5 * 20 * 500 = 50000个并发,这是一般一个4cpu的机器最大的并发数。nginx在负载均衡的时候最大承载量就是5w个

  单线程里的这20个任务的代码通常会既有计算操作又有阻塞操作,我们完全可以在执行任务1时遇到阻塞,就利用阻塞的时间去执行任务2。。。。如此,才能提高效率,这就用到了Gevent模块。

推导过程

切换 + 保持状态

# 切换 + 保持状态
def gen():
    for i in range(10,1,-1):
        yield i

def func():
    obj = gen()
    for i in range(5):
        print(next(obj))
func()

利用greenlet 切换 +保持状态

用greenlet模块可以非常简单地实现这20个任务直接的切换

真正的协程模块就是使用greenlet完成的切换

# 切换 +保持状态(遇到IO不会主动切换)
# switch()代表切换
from greenlet import greenlet
import time
def eat(name):
    print('%s eat 1' %name)  # 2
    g2.switch('taibai')  # 3
    time.sleep(3)
    print('%s eat 2' %name)  # 6
    g2.switch()  # 7

def play(name):
    print('%s play 1' %name)  # 4
    g1.switch()  # 5 切换
    print('%s play 2' %name)  # 8

g1=greenlet(eat)
g2=greenlet(play)

最终版本

必须要jion 不然线程结束了
# g1.join()
# g2.join()
gevent.joinall([g1,g2])#与上面2个合并效果相同


# 最终版本:
import gevent## 切换 +保持状态(遇到IO不会主动切换)

from gevent import monkey
monkey.patch_all()  # 打补丁: 将下面的所有的任务的阻塞都打上标记 遇到就切换

def eat(name):
    print('%s eat 1' %name)
    time.sleep(2)
    print('%s eat 2' %name)

def play(name):
    print('%s play 1' %name)
    time.sleep(1)
    print('%s play 2' %name)

g1 = gevent.spawn(eat,'egon')#固定写法
g2 = gevent.spawn(play,name='egon')

# g1.join()
# g2.join()
gevent.joinall([g1,g2])
posted @ 2020-03-01 10:18  一起奥利给  阅读(134)  评论(0编辑  收藏  举报