栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

Python之进程,线程,协程

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Python之进程,线程,协程

目录

进程:

关于进程的理解:

进程的创建(进程间全局变量)

自定义进程:

进程池(阻塞式和非阻塞式-多进程):

队列:Queue

进程通信(queue):

线程:

对线程的简单理解:

线程优点:

关于线程的使用方法:

如何创建并使用线程?

线程(共享全局变量):

线程(锁问题):

多线程同步问题:

线程(死锁问题):

生产者和消费者问题(两线程间的通信):

协程:

协程的优点:

协程的实现(yield):

协程的实现(greenlet):

协程的实现(gevent+猴子补丁):

协程模拟下载多个任务:


进程:

关于进程的理解:

多任务概念:

        生活中,你可能一边听歌,一边写作业;一边上网,一边吃饭... 这些都是生活中的多任务场景。 电脑也可以执行多任务,比如你可以同时打开浏览器上网,听音乐,打开pycharm编写代码......

简单的说**多任务就是同一时间运行多个程序**

单核和多核:

--单核CPU实现多任务原理:操作系统轮流让各个任务交替进行,QQ执行2us,切换到微信,再执行2us,再切换到陌陌,执行2us...。 表面是看,每个任务反复执行下去,但是CPU调度执行速度太快了,导致我们感觉所以任务都在同时执行一样。

--多核CPU实现多任务原理:真正的并行执行多任务只能在多核CPU上实现,但是由于任务数量远远多于CPU的核心数量, 所以,操作系统也会自动把很多任务调度到每个核心上执行。

实现多任务的方式:

--多进程模式

--多线程模式

--协程

进程 > 线程 > 协程

大致上来说,进程是线程的容器,是程序的实体;进程是分配空间,线程是分配时间。

进程优点:

稳定性高,一个进程崩溃了不会影响其他进程

进程缺点:

1.创建进程开销巨大

2.操作系统能同时运行的进程数目有限

        在linux下可以使用fork()函数创建进程;在windows系统上可以引用multiprocessing模块,创建进程,我们可以使用multiprocessing模块中Process类创建新的进程。python里的fork()在os模块里。

进程的创建(进程间全局变量)

from multiprocessing import Process      #导入模块

process = Process(target = 函数,name = 进程名,args = (给函数传递的参数))

process.start()        #启动进程并执行任务

process.run()          #只是执行了任务没有启动进程

process.join()         #起到堵塞的作用,完成上面的任务后才可以往下进行

terminate()              #终止

os模块里的getpid()可以获取当前进程号。

###举例说明

from multiprocessing import Process  # 使用该模块创建进程
from time import sleep
import os

m = 1  # 不可变类型


def task1(s, name):
    global m
    while True:
        m += 1
        sleep(s)
        print('这是任务1...', os.getpid(), m)  # 两进程并不共享全局变量m,相当于每个进程单独拥有一份m


def task2(s, name):
    global m
    while True:
        m += 1
        sleep(s)
        print('这是任务2...', os.getpid(), m)


if __name__ == '__main__':
    number = 0

    print(os.getpid())
    # 子进程
    p = Process(target=task1, name='任务1', args=(1, 'aa'))
    p.start()
    print(p.name)  # 打印动作在主进程里
    p1 = Process(target=task2, name='任务2', args=(2, 'bb'))
    p1.start()
    print(p1.name)  # 打印动作在主进程里

输出:

7368
任务1
任务2
这是任务1... 15988 2
这是任务2... 1692 2
这是任务1... 15988 3
这是任务1... 15988 4
这是任务2... 1692 3
这是任务1... 15988 5
这是任务1... 15988 6
这是任务2... 1692 4
这是任务1... 15988 7
这是任务1... 15988 8
这是任务2... 1692 5
这是任务1... 15988 9
这是任务1... 15988 10
...
...
...

1.该输出条件没有限制,将一直输出。

2.主进程里声明了整型变量m,在子进程里的m仅共享初始化的m,值为1;而后对m的操作并不共享,说明在进程里的变量具有独立性。

自定义进程:

当父类进程里的内容不满足需求时,我们可以自定义进程。

1.继承主进程(super的使用)

2.重写run()方法

3.创建进程对象并启动(start)进程,start的调用会默认执行自定义进程里的run()函数

###举例说明

from multiprocessing import Process
from time import sleep
class MyProcess(Process):  # 继承了主进程
    def __init__(self,name):
        super(MyProcess,self).__init__()
        self.name = name

    # 重写run方法
    def run(self):
        n = 1
        while True:
            sleep(1)
            print('{}____自定义进程:{}'.format(n,self.name))
            n += 1

if __name__ == '__main__':
    p = MyProcess('小明')
    p.start()
    p1 = MyProcess('小红')
    p1.start()

输出:

1____自定义进程:小明
1____自定义进程:小红
2____自定义进程:小明
2____自定义进程:小红
3____自定义进程:小明
3____自定义进程:小红
4____自定义进程:小明
4____自定义进程:小红
...
...
...

        以上代码仅简单作一个自定义进程,不做过多解释;其中输出没有设置限制条件,故会一直执行;要注意定义进程类时继承主进程,看情况写初始化函数 __init__(),需重写run()方法。

进程池(阻塞式和非阻塞式-多进程):

        当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态生成多个进程, 但是如果是成百甚至上千个目标时,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块里的(进程池)Pool方法。

        初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满 那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待, 直到池中有进程结束,才会创建新的进程来执行。

1.进程池中的进程在创建的时候就已经分配好了进程号,任务的迭代不影响进程号的改变。

2.进程池的存活依赖于主进程,同生共死。

非阻塞式:全部添加到队列中,立刻返回,并不等待其他进程执行完毕才结束, 但是回调函数是等待任务完成之后才会调用执行。

pool = Pool(5)    #创建了可容纳5个进程的进程池

pool.apply_async(func=函数名,args=(参数,),callback=回调函数名)    #当函数参数只有一个,在参数后面加逗号

非阻塞式标志是apply_async;callback是回调函数的意思,等待任务完成后执行。

###举例说明

import os
import time
import random
from multiprocessing import Pool
#非阻塞式进程

def task(task_name):

    print('开始做任务啦!',task_name)
    start = time.time() #算出当前时间
    #使用sleep()
    time.sleep(random.random()*2)
    end = time.time()
    return '完成任务{}  用时:{},进程ID:{}'.format(task_name,end-start,os.getpid())

container = []
def callback_func(n):
    container.append(n)

if __name__ == '__main__':
    #创建了容量为5的进程池,在创建的时候就已经分配好了进程号,任务的迭代不影响进程号的改变
    pool = Pool(5)   #进程池的存活依赖于主进程,同生共死
    tasks = ['LOL','洗衣服','吃饭','遛狗','听歌','睡觉']

    for i in tasks:
        #进程池里可装5个进程,但是有6个任务所以最后一个任务需要等其他进程结束才能开始,需要排队
        pool.apply_async(func=task,args=(i,),callback=callback_func)  #使用非阻塞模式(非同步);callback为回调,当func执行完毕后,return的东西会给到回调函数callback


    pool.close()  #添加任务结束;关闭进程池,不再接收新的请求。
    pool.join()   #堵住主进程,必须在完成任务后才能往下进行

    for i in container:
        print(i)
    print('OVER!')

输出:

开始做任务啦! LOL
开始做任务啦! 洗衣服
开始做任务啦! 吃饭
开始做任务啦! 遛狗
开始做任务啦! 听歌
开始做任务啦! 睡觉
完成任务LOL  用时:0.12997722625732422,进程ID:19996
完成任务遛狗  用时:0.11489439010620117,进程ID:18380
完成任务吃饭  用时:0.5681507587432861,进程ID:19520
完成任务洗衣服  用时:0.6339731216430664,进程ID:980
完成任务睡觉  用时:1.4290874004364014,进程ID:19996
完成任务听歌  用时:1.6031692028045654,进程ID:6704
OVER!

代码解读:

        在主进程里我们申请了容量为5的进程池,利用for循环的方式给这些进程分配任务,一共有六个任务,所以最后一个任务需要先等待,等到有进程把一个任务执行完成,再去执行最后的任务。故在该输出中,进程19996存在两次,执行了两个任务。并且在非阻塞式下,可先将任务全部添加到等待队列中,分配进程资源执行任务,同时进行;callback为回调,当func执行完毕后,return的东西会给到回调函数callback。

阻塞式:

添加一个任务,然后再执行一个任务,如果一个任务不结束,其他任务进不来。

pool = Pool(max)  #创建进程池对象

pool.apply_async(func=函数名,args=(参数,))    #非阻塞的

pool.close()  #停止添加进程

pool.join()     #堵住主进程,必须在完成任务后才能往下进行

 

###举例说明###

import os
import time
import random
from multiprocessing import Pool
def task(task_name):

    print('开始做任务啦!',task_name)
    start = time.time()
    #使用sleep()
    time.sleep(random.random()*2)
    end = time.time()
    print('完成任务{}  用时:{},进程ID:{}'.format(task_name,end-start,os.getpid()))

if __name__ == '__main__':
    pool = Pool(5)
    tasks = ['LOL', '洗衣服', '吃饭', '遛狗', '听歌', '睡觉']

    for i in tasks:
        pool.apply(func=task, args=(i,))  # 使用阻塞模式,没有回调函数;一人任务结束后才加入下一个任务
    pool.close()
    pool.join()

输出:

开始做任务啦! LOL
完成任务LOL  用时:1.8951187133789062,进程ID:13080
开始做任务啦! 洗衣服
完成任务洗衣服  用时:0.6655244827270508,进程ID:19708
开始做任务啦! 吃饭
完成任务吃饭  用时:1.7410509586334229,进程ID:19284
开始做任务啦! 遛狗
完成任务遛狗  用时:1.5534477233886719,进程ID:5752
开始做任务啦! 听歌
完成任务听歌  用时:0.3398125171661377,进程ID:11576
开始做任务啦! 睡觉
完成任务睡觉  用时:1.365257740020752,进程ID:13080

        由于是阻塞式下,依次从进程池中取出进程来执行任务task(),第一个任务没有完成下,不能启动第二个任务,也无法分配进程。故使用阻塞式的意义不大。

队列:Queue

        在说进程间的通信之前,让我们先看看多进程模块下的Queue类吧!

put()    #向队列里放入数据,可设置超时时间进行等待

get()    #向队列里取出数据,可设置超时时间进行等待

put_nowait()   #向队列里放入数据,不等待

get_nowait()   #向队列里取出数据,不等待

empty()           #判断队列是否为空

q = Queue(5)  # 队列容量
# 向队列里添加元素
q.put('A')
q.put('B')
q.put('C')
q.put('D')
q.put('E')
print(q.qsize())
# timeout表示2秒后报异常
if q.full() == False:  # full判断队列是否为满  empty判断队列是否为空
    q.put('A', timeout=2)  # 自动阻塞,当加入时超过最大容量只能等待队列里的元素被取走
else:
    print('队列已满!')

# 获取队列的值
print(q.get())  # 当队列为空时还在取元素,会阻塞
print(q.get())
print(q.get())
print(q.get())
print(q.get())

输出:

5
队列已满!
A
B
C
D
E
get(),put()为阻塞式,队列为空或为满时等待
get_nowait,put_nowait 为非阻塞式,队列为空或为满时不等待,直接报异常

进程通信(queue):

q = Queue(5)   #创建一个容量为5的队列

p1 = Process(target = ,args = (q,))

        简单的理解 Queue 实现进程间通信的方式,就是使用了操作系统给开辟的一个队列空间, 各个进程可以把数据放到该队列中,当然也可以从队列中把自己需要的信息取走。

from multiprocessing import Process
from time import sleep


def download(q):
    images = ['glrl.jpg', 'boy.jpg', 'man.jpg']
    for i in images:
        print('正在下载{}'.format(i))
        sleep(1)
        q.put(i)


def getfile(q):
    while True:
        try:
            file = q.get(timeout=5)
            print('{}保存成功!'.format(file))
        except:
            print('全部保存成功!')
            break


# 开两个进程,执行下载任务,利用队列来对两进程之间实现通信
if __name__ == '__main__':
    q = Queue(5)
    p1 = Process(target=download, args=(q,))  # target为待执行任务;注意只写函数名不加括号
    p2 = Process(target=getfile, args=(q,))  # 利用args传入队列对象

    p1.start()
    p1.join()  # 将任务全部执行完毕后才可执行下一步
    p2.start()
    p2.join()

    print('!!!!!!')

输出:

正在下载glrl.jpg
正在下载boy.jpg
正在下载man.jpg
glrl.jpg保存成功!
boy.jpg保存成功!
man.jpg保存成功!
全部保存成功!
!!!!!!

        注意:利用args传入队列对象,对象做参数。

线程:

#进程:Process:进程做任务速度更快

#线程:Thread:线程适合做耗时操作

对线程的简单理解:

        对于经常使用电脑的我们,不难发现这样的一个现象,电脑管家里可以,一边杀毒,一边清理电脑垃圾,一边卸载软件。其实这其中就有线程的功劳,我们可以将电脑管家看作一个进程,它做的这些许多任务我们看作线程,这就是一个多线程的例子。

        线程多为耗时操作,也称轻量级进程,是程序执行流的最小单元。

        线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可以与同属一个进程的其他线程共享进程所拥有的全部资源。

        一个线程可以创建或撤销另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪,阻塞,和运行三种基本状态。 每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。

        多线程(multithreading):是指从软件或者硬件上实现多个线程并发执行的技术,可以在单个程序中利用多个线程完成不同的工作。

线程优点:

1.使用线程可以把占据长时间的程序中的任务放到后台去处理。

2.用户界面可以更加吸引人,比如用户点击了一个按钮触发某些事件的处理,可弹出一个进度条显示处理的速度。

3.程序执行的速度可能加快。

关于线程的使用方法:

thread 和 threading 提供对线程的支持。

thread提供了低级别的,原始的线程以及简单的锁;threading 模块提供其他的方法。

threading.currentThread() #返回当前的线程变量

threading.enumerate() #返回一个包含正在运行的线程list

threading.activeCount() #返回正在运行的线程数量。与len(threading.enumerate())结果相同

如何创建并使用线程?

t1 = threading.Thread(target=函数名,name='线程名',args=(函数参数,))

t1.start()

###使用线程模仿听歌和下载功能###

import threading
from time import sleep


def download(n):
    images = ['glrl.jpg','boy.jpg','man.jpg']
    for i in images:
        print('正在下载{}'.format(i))
        sleep(n)
        print('{}下载成功!'.format(i))
def listenMusic():
    musics = ['大碗宽面','烤面筋','我们不一样']
    for music in musics:
        print('正在听歌{}'.format(music))


if __name__ == '__main__':
    t1 = threading.Thread(target=download,name='jsy',args=(1,))
    t1.start()
    t2 = threading.Thread(target=listenMusic,name='J')
    t2.start()

输出:

正在下载glrl.jpg
正在听歌大碗宽面
正在听歌烤面筋
正在听歌我们不一样
glrl.jpg下载成功!
正在下载boy.jpg
boy.jpg下载成功!
正在下载man.jpg
man.jpg下载成功!

        利用多个线程在同一时间完成不同的任务。

线程(共享全局变量):

        线程可以共享全局变量。 利用这一特性,线程可以用来做买火车票的程序。

###举例说明###

import threading
ticket = 1000


def run1():
    global ticket
    for i in range(100):
        ticket -= 1


def run2():
    global ticket
    for i in range(200):
        ticket += 1


if __name__ == '__main__':
    # 创建线程
    th1 = threading.Thread(target=run1(), name='th1')
    th2 = threading.Thread(target=run1(), name='th2')
    th3 = threading.Thread(target=run1(), name='th3')
    th4 = threading.Thread(target=run2(), name='th4')
    th1.start()
    th2.start()
    th3.start()
    th4.start()

    th1.join()
    th2.join()
    th3.join()
    th4.join()
    print('ticket:', ticket)  # 两线程共享变量ticket

输出:
 

ticket: 900

        起初,总票数为1000,经过4个线程的手之后,1000-100-100-100+200=900。由于各线程共享全局变量ticket,所以才能统一对一个变量进行操作,这一点要优于进程。

线程(锁问题):

需要注意的是:

        1.python底层只要用线程就会默认加锁—GIL全局解释器锁。

        2.加锁实现线程同步,保证了数据安全,但是速度降低;当数据改变量过大时,锁会自动释放,数据不安全,所以线程并不安全,此时需要手动加锁。

###举例说明###(没有手动加锁)

import threading

n = 0


def task1():
    global n
    for i in range(10000000):
        n += 1
    print('n的值:', n)


def task2():
    global n
    for i in range(10000000):
        n += 1
    print('n的值:', n)


if __name__ == '__main__':
    th1 = threading.Thread(target=task1, name='th1')
    th2 = threading.Thread(target=task2, name='th2')
    th1.start()
    th2.start()
    th1.join()
    th2.join()
    print('最后打印{}'.format(n))

输出:

n的值: 12319874
n的值: 12652163
最后打印12652163

        注意:此时由于for循环次数太多,数据量改变太大,系统默认加上的锁已然自动释放;这种清况下需要手动加锁。

###举例说明###(手动加锁后)

import threading
n = 0


def task1():
    global n
    with lock:
        for i in range(10000000):
            n += 1
    print('n的值:', n)


def task2():
    global n
    with lock:
        for i in range(10000000):
            n += 1
    print('n的值:', n)


if __name__ == '__main__':
    lock = threading.Lock()
    th1 = threading.Thread(target=task1, name='th1')
    th2 = threading.Thread(target=task2, name='th2')
    th1.start()
    th2.start()
    th1.join()
    th2.join()
    print('最后打印{}'.format(n))

输出:

n的值: 10000000
n的值: 20000000
最后打印20000000

        使用上下文的方式加锁,with lock ,更加简便,不用去管获取锁和释放锁的问题。而且在加上锁之后,结果也是正确的。

多线程同步问题:

同步:一个一个的完成,一个做完,另一个才能进来,效率就会降低,而安全性增高。

共享数据:如果多个线程同时对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。

 

        多线程的优势在于可以同时运行多个任务(至少感觉像是这样)。 但是当线程需要共享数据时,可能存在数据不同步问题, 为了避免这种情况,引入了锁的概念。

        使用Thread对象的Lock和Block可以实现简单的线程同步,这两个对象都有acquire方法和release方法, 对于那些需要每次只允许一个线程操作的数据,可以将其放到acquire和release方法之间,锁只要不释放,其他的线程都无法进入运行状态。

lock = thread.Lock()

lock.acquire()      #请求得到锁

lock.release()      #释放锁

###举例说明###

---> 可见上文的锁问题案例
 

线程(死锁问题):

        开发过程中使用线程,在线程间共享多个资源的时候, 如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁;尽管死锁很少发生,但一旦发生就会造成应用的停止响应,程序不做任何事情。

避免死锁的方法:

1.重构代码

2.在acquire里加  timeout=

 

###举例说明###

from threading import Lock, Thread
import time

lockA = Lock()
lockB = Lock()


class MyThread(Thread):

    def run(self):  # start()
        if lockA.acquire():  # 如果成功获取到A锁则返回Ture
            print(self.name + '获取了A锁')
            time.sleep(1)  # 线程1在休息的时候B锁就被线程2获取了,所以无法获取B锁
            if lockB.acquire(timeout=2):  # 如果2秒得不到B锁,两秒后释放A锁
                print(self.name + '又获取了B锁')
                lockB.release()
            lockA.release()


class MyThread1(Thread):
    def run(self):  # start()
        if lockB.acquire():  # 如果成功获取到B锁则返回Ture
            print(self.name + '获取了B锁')
            time.sleep(1)
            if lockA.acquire():
                print(self.name + '又获取了A锁')
                lockA.release()
            lockB.release()


if __name__ == '__main__':
    t1 = MyThread()
    t2 = MyThread1()

    t1.start()# 默认执行自定义线程里的 run()方法
    t2.start()

输出:

Thread-1获取了A锁
Thread-2获取了B锁
Thread-2又获取了A锁

        自定义了两个线程:MyThread和MyThread1;先执行t1里的run方法,t1获得了A锁,t1休眠1秒钟,这一秒钟里t2执行run方法,并获取了B锁,然后t2也休眠1秒钟;等到t1醒来时再想获取B锁无法获取,需要等待,等2秒后没有获得B锁,将A锁释放;而t2,对A锁一直处于等待状态,故t1一释放A锁,t2就拿到A锁,程序向下进行,成功避免了死锁。

生产者和消费者问题(两线程间的通信):

         Python的queue模块中提供了同步的,线程安全的队列类,包括FIFO(先入先出)队列Queue, LIFO(后入先出-栈)队列LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语 (可以理解为原子操作,即要么不做,要么就做完),能够在多线程中直接使用, 可以使用队列来实现线程间的同步。

 

 

###举例说明###

import threading
import queue
import random
import time


def produce(q):
    i = 0
    while i < 10:
        num = random.randint(1, 100)
        q.put('生产者产生数据:%d' % num)
        print('生产者产生数据:%d' % num)
        time.sleep(1)
        i += 1
    q.put(None)
    #完成任务
    q.task_done()

def consume(q):
    while True:
        item = q.get()
        if item is None:
            break
        print('消费者获取到:%s' %item)
        time.sleep(2)
    #完成任务
    q.task_done()


if __name__ == '__main__':
    q = queue.Queue(10)
    arr = []

    # 创建生产者
    tp = threading.Thread(target=produce, args=(q,))
    tp.start()
    # 创建消费者
    tc = threading.Thread(target=consume, args=(q,))
    tc.start()

    tp.join()
    tc.join()
    print('END!')

输出:

生产者产生数据:95
消费者获取到:生产者产生数据:95
生产者产生数据:26
消费者获取到:生产者产生数据:26
生产者产生数据:25
生产者产生数据:43
消费者获取到:生产者产生数据:25
生产者产生数据:41
生产者产生数据:34
消费者获取到:生产者产生数据:43
生产者产生数据:73
生产者产生数据:69
消费者获取到:生产者产生数据:41
生产者产生数据:68
生产者产生数据:74
消费者获取到:生产者产生数据:34
消费者获取到:生产者产生数据:73
消费者获取到:生产者产生数据:69
消费者获取到:生产者产生数据:68
消费者获取到:生产者产生数据:74
END!

        生产者生产10组数据,消费者拿出10组数据;由于是两线程对同一组数据进行操作,可认为实现了线程间的通信。

 

协程:

进程 > 线程 > 协程

协程理解:相当于电脑管家(进程)里的杀毒功能(线程)中同时查(协程)C,D,E盘中的文件,协程是运行在单线程间的"并发",协程相比多线程一大优势就是省去了多线程之间的切换开销,获得了更大的运行效率。

协程使用:只要出现耗时操作就会切换动作,争取时刻资源都处于利用状态,高效利用CPU

耗时操作:网络操作,网络下载(爬虫),I/O操作(文件读写),堵塞

协程的优点:

        1.执行效率极高,因为子程序切换(函数)不是线程切换,由程序自身控制,没有切换线程的开销。所以与多线程相比,线程的数量越多,协程性能的优势越明显。

        2.不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在控制共享资源时也不需要加锁,因此执行效率高很多。

协程的实现(yield):

Process---Thread---生成器完成(yield)

###举例说明###

def task1():
    for i in range(3):
        print('A' + str(i))
        yield
        time.sleep(1)


def task2():
    for i in range(3):
        print('B' + str(i))
        yield
        time.sleep(2)


if __name__ == '__main__':
    g1 = task1()
    g2 = task2()
    while True:
        try:
            next(g1)
            next(g2)
        except:
            break

输出:

A0
B0
A1
B1
A2
B2

        利用yield实现暂时阻塞,再next()一个一个取出结果。

协程的实现(greenlet):

使用greenlet完成协程任务 可以更好得进行任务切换。

###举例说明###

import time
from greenlet import greenlet


def a():  # 任务a
    for i in range(3):
        print('A' + str(i))
        gb.switch()
        time.sleep(0.1)


def b():  # 任务b
    for i in range(3):
        print('B' + str(i))
        gc.switch()
        time.sleep(0.1)


def c():  # 任务c
    for i in range(3):
        print('C' + str(i))
        ga.switch()
        time.sleep(0.1)


if __name__ == '__main__':
    # 通过手动控制switch,完成ABC依次打印
    ga = greenlet(a)
    gb = greenlet(b)
    gc = greenlet(c)
    ga.switch()

输出:
 

A0
B0
C0
A1
B1
C1
A2
B2
C2

        通过设置ga,gb,gc,并对它们进行switch控制,高效实现任务的切换。

协程的实现(gevent+猴子补丁):

geeenlet已经实现了协程,但是这个人工切换,有些麻烦。 python还有一个比greenlet更强大的并且能够自动切换任务的模块gevent。其原理是当一个greenlet遇到IO(指的是input output输入输出,比如网络,文件操作等)操作时, 比如访问网络,就自动切换到其他的greenlet,等到IO完成,在适当的时候切换回来继续执行。 由于IO操作非常耗时,经常使程序处于等待状态,有了gevent我们自动切换协程, 就保证总有greenlet在执行,而不是等待IO。

猴子补丁(Monkey pitch):实现了自动切换。

###举例说明###(不使用猴子补丁)

import gevent
import time
from gevent import monkey


def a():  # 任务a
    for i in range(3):
        print('A' + str(i))
        time.sleep(0.1)


def b():  # 任务b
    for i in range(3):
        print('B' + str(i))
        time.sleep(0.1)


def c():  # 任务c
    for i in range(3):
        print('C' + str(i))
        time.sleep(0.1)


if __name__ == '__main__':
    g1 = gevent.spawn(a)
    g2 = gevent.spawn(b)
    g3 = gevent.spawn(c)

    g1.join()
    g2.join()
    g3.join()
    print('Over !')

输出:
 

A0
A1
A2
B0
B1
B2
C0
C1
C2
Over !

        不使用猴子补丁时,程序中任务a,b,c按顺序执行,一个执行完才能执行下一个(即便出现了耗时操作sleep),此时不能称为是协程的应用。

###举例说明###(使用了猴子补丁)

import gevent
import time
from gevent import monkey

monkey.patch_all()  # 猴子补丁


def a():  # 任务a
    for i in range(3):
        print('A' + str(i))
        time.sleep(0.1)


def b():  # 任务b
    for i in range(3):
        print('B' + str(i))
        time.sleep(0.1)


def c():  # 任务c
    for i in range(3):
        print('C' + str(i))
        time.sleep(0.1)


if __name__ == '__main__':
    g1 = gevent.spawn(a)
    g2 = gevent.spawn(b)
    g3 = gevent.spawn(c)

    g1.join()
    g2.join()
    g3.join()
    print('Over !')

输出:

A0
B0
C0
A1
B1
C1
A2
B2
C2
Over !

        当我们使用了猴子补丁后,任务a,b,c交替执行。所以猴子补丁的作用就是当 gevent遇到延时 时切换到其他任务。

协程模拟下载多个任务:
# 案例
from gevent import monkey

monkey.patch_all()  # 此处的猴子补丁需要在导入requests包之前完成补丁操作
import requests
import gevent
import urllib.request


def download(url):
    # response = requests.get(url)
    response = urllib.request.urlopen(url)
    content = response.read()
    # content = response.text
    print('下载了{}的数据,长度:{}'.format(url, len(content)))


if __name__ == '__main__':
    urls = ['http://www.163.com', 'http://www.qq.com', 'http://www.baidu.com']
    g1 = gevent.spawn(download, urls[0])
    g2 = gevent.spawn(download, urls[1])
    g3 = gevent.spawn(download, urls[2])

    gevent.joinall((g1, g2, g3))

输出:

下载了http://www.baidu.com的数据,长度:321105
下载了http://www.163.com的数据,长度:613708
下载了http://www.qq.com的数据,长度:23948

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/295875.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号