多任务-协程

一、协程的概念

协程,又称微线程,纤程。英文名Coroutine。
协程是python中另外一种实现多任务的方式,只不过比线程占用更小的执行单元(理解为需要的资源)。
通俗的理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定。
在实现多任务时, 线程切换从系统层面远不止保存和恢复 CPU上下文这么简单。 操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作。 所以线程的切换非常耗性能。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住。

二、协程的实现方式

2.1 通过yield实现

# -*- coding:utf-8 -*-
import time

def work1():
    while True:
        print('--work1--')
        yield
        time.sleep(0.5)

def work2():
    while True:
        print('-work2')
        yield
        time.sleep(0.5)

def main():
    w1 = work1()
    w2 = work2()
    while True:
        next(w1)
        next(w2)

if __name__ == '__main__':
    main()

结果:
--work1--
-work2
--work1--
-work2
--work1--
-work2
--work1--
-work2
--work1--
-work2
--work1--
-work2
......

可以看到,任务1和任务2是交替执行的,实现了多任务。

2.2 通过greenlet实现

为了更好使用协程来完成多任务,python中的greenlet模块对其封装,从而使得切换任务变的更加简单。

使用如下命令安装greenlet模块:
sudo pip3 install greenlet

# -*- coding:utf-8 -*-

import time, random, greenlet

def test1(num):
    while True:
        print('A')
        print(num)
        gr2.switch(2)
        time.sleep(random.random())

def test2(num):
    while True:
        print('B')
        print(num)
        gr1.switch(1)
        time.sleep(random.random())

gr1 = greenlet.greenlet(test1)
gr2 = greenlet.greenlet(test2)

gr1.switch(1)

结果:
A
1
B
2
A
1
B
2
A
1
......

可以看到,同样实现了多任务,只不过是需要手动去切换任务。

2.3通过gevent实现

greenlet已经实现了协程,但是还需要人工切换,python还有一个比greenlet更强大的并且能够自动切换任务的模块gevent。

其原理是当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。

由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。

# -*- coding:utf-8 -*-
import gevent

def f(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        # 用来模拟一个耗时操作,注意不是time模块中的sleep
        gevent.sleep(1)

g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)

#把循环次数改为500000,让它们的运行时间长一点,然后在操作系统的进程管理器中看,线程数只有1个。
# g1 = gevent.spawn(f, 500000)
# g2 = gevent.spawn(f, 500000)
# g3 = gevent.spawn(f, 500000)

g1.join()
g2.join()
g3.join()

结果:
<Greenlet "Greenlet-0" at 0x10b8f0a48: f(5)> 0
<Greenlet "Greenlet-1" at 0x10b8f0d48: f(5)> 0
<Greenlet "Greenlet-2" at 0x10b8f0e48: f(5)> 0
<Greenlet "Greenlet-0" at 0x10b8f0a48: f(5)> 1
<Greenlet "Greenlet-1" at 0x10b8f0d48: f(5)> 1
<Greenlet "Greenlet-2" at 0x10b8f0e48: f(5)> 1
<Greenlet "Greenlet-0" at 0x10b8f0a48: f(5)> 2
<Greenlet "Greenlet-2" at 0x10b8f0e48: f(5)> 2
<Greenlet "Greenlet-1" at 0x10b8f0d48: f(5)> 2
<Greenlet "Greenlet-0" at 0x10b8f0a48: f(5)> 3
<Greenlet "Greenlet-1" at 0x10b8f0d48: f(5)> 3
<Greenlet "Greenlet-2" at 0x10b8f0e48: f(5)> 3
<Greenlet "Greenlet-0" at 0x10b8f0a48: f(5)> 4
<Greenlet "Greenlet-2" at 0x10b8f0e48: f(5)> 4
<Greenlet "Greenlet-1" at 0x10b8f0d48: f(5)> 4

# -*- coding:utf-8 -*-

from gevent import monkey; #monkey.patch_all() 这个方法需要放在最前面,不然会有警告
import gevent, random, time


#打补丁:有耗时操作时需要,自动检测耗时的操作,自动将time.sleep(random.random())转换为gevent.sleep(random.random())
# monkey.patch_all() # 将程序中用到的耗时操作的代码,换为gevent中自己实现的模块


def coroutine_work(coroutine_name):
    for i in range(10):
        print(coroutine_name, i)
        time.sleep(random.random())


gevent.joinall([
    gevent.spawn(coroutine_work, 'work1'),
    gevent.spawn(coroutine_work, 'work2')

])

结果:
work1 0
work1 1
work1 2
work1 3
work1 4
work1 5
work1 6
work1 7
work1 8
work1 9
work2 0
work2 1
work2 2
work2 3
work2 4
work2 5
work2 6
work2 7
work2 8
work2 9

三、进程、线程、协程对比

  • 进程是操作系统资源分配的单位;
  • 线程是CPU调度的单位;
  • 进程切换需要的资源最大,效率很低;
  • 线程切换需要的资源一般,效率一般(当然在不考虑GIL的情况下);
  • 协程切换任务资源很小,效率高;
  • 多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中,所以是并发。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容