Python 协程

本文探讨了Python中的协程实现并发的概念,强调只有在遇到I/O时切换才能提高效率。通过对比多线程和协程,指出单线程内的任务切换能减少阻塞时间,增加CPU利用率。Gevent作为协程库,结合Greenlet和I/O检测,可以在遇到阻塞时自动切换任务,从而提升程序执行效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 实现并发:开多进程和开多线程

  • 若只有一个线程,肯定无法实现并行,但是否可以实现并发?
    并发的实现手段:切换+保存状态,原来是由OS管控,没有I/O时操作系统也可能切换任务,因为要统筹全局。但是真正能提高效率的是有I/O的时候切换。

  • 在自己的线程中,有五个任务,进行切换+保存状态,也叫并发,但是是由程序员控制,OS管不着。即,单线程下实现并发,成为协程。

  • 协程不存在,只是人们规定的一种概念。

  • 协程对提升效率是否有意义?
    没有遇到I/O,单纯的切换反而会降低效率
    应该遇到I/O才切换,yield和send无法实现

  • 原先多线程,每个线程互不干涉,每个线程遇到I/O阻塞就得等待,多线程只是实现了任务切换,并没有解决I/O问题

  • 在协程中,可以实现比如五个任务,一个任务遇到I/O需要等待,就切换到另外一个任务,这样针对整个线程阻塞的时间就降低了,CPU给每个线程的运行时间是有限的,但是另外两个就绪和阻塞时间是此消彼长的,降低了阻塞时间,就绪时间就增加了,从而增大了可以再次抢到CPU的机会,提高了效率。并且单线程内的切换是代码级的,CPU管不着,所以切换速度比较快。且操作系统看不到一个线程中的任务切换。

  • 遇到I/O才切换的协程才有提高效率的意义,不遇到I/O就切换,也称作协程。单线程内实现并发,最大限度利用CPU

  • 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,单个线程开启协程。协程一旦出现阻塞会阻塞整个线程。

  • Greenlet比yield好一些,可以简单的实现多任务直接切换,但是仍然知识纯粹切换,遇到I/O仍然不能实现切换,没有解决遇到I/O自动切换来提升效率的问题。

  • Gevent
    假如有20个任务,通常会既有计算任务,也有I/O任务,完全可以在任务一遇到阻塞时利用阻塞的时间去执行任务2.。。。如此提高效率
    Gevent=Greenlet+检测I/O遇到I/O就切换

Greenlet9(无法实现遇到I/O切换)

from  greenlet import  greenlet
 
#模拟多个任务,吃一口饭,玩一次手机
def eat(name):
    print('%s eat 1' %name)
    g2.switch(name)
    print('%s eat 2' %name)
    g2.switch(name)
    
    
def play(name):
    print('%s play 1' %name)
    g1.switch(name)
    print('%s play 2' %name)
    
    

g1= greenlet(eat)   #在第一次启动任务时才传参数
g2= greenlet(play)


g1.switch('egon')   #启动任务,传参
结果:
egon eat 1
egon play 1
egon eat 2
egon play 2

gevent

#实现单线程多任务遇到I/O才切

#模拟多个任务,吃一口饭,玩一次手机
  #实用gevent管理这两个任务
import  gevent

def eat(name):
    print('%s eat 1' %name)
    gevent.sleep(3) #相当于time.sleep(3)   ,模拟I/O 操作  #单纯的写,gevent只能识别自己的I/O识别操作
    print('%s eat 2' %name)
  
    
def play(name):
    print('%s play 1' %name)
    gevent.sleep(2)
    print('%s play 2' %name)
    
    

g1=gevent.spawn(eat,'egon')
g2=gevent.spawn(play,'egon')  #参数可不同
#gevent.sleep(10)  #保证线程两个任务能够有时间运行起来,避免线程开启任务就立刻死掉,但是不知道应该睡几秒
g1.join()
g2.join()      #保证该线程等着上边两个任务运行完才死掉  这两句等价于 gevent.joinall([g1,g2])
#这两个是异步调用

结果:
egon eat 1
egon play 1
egon play 2
egon eat 2

#大概花了三秒,相比于串行的五秒提高了效率

但上边仅是gevent.sleep(5)自己模拟的I/O,可以识别,但没有什么用处,使用time.sleep(3)就无法识别了,一直在那等着

要想gevent能够识别所有的I/O操作,不局限于自己的
加上一个补丁


from gevent import monkey
monkey.patch_all()   #保证下边所有I/O都能被识别,打补丁
import  gevent
import  time

def eat(name):
    print('%s eat 1' %name)
    time.sleep(3) #模拟I/O 操作  ,属于其他I/O类
    print('%s eat 2' %name)
  
    
def play(name):
    print('%s play 1' %name)
    time.sleep(2)
    print('%s play 2' %name)
    
    

g1=gevent.spawn(eat,'egon')
g2=gevent.spawn(play,'egon')  #参数可不同

g1.join()
g2.join()   

结果:
egon eat 1
egon play 1
egon play 2
egon eat 2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值