协程(十)

一、协程

1、利用yield实现多任务

def work1():
  for i in range(10):
    print('work1----{}'.format(i))
    yield i

def work2():
  for i in range(10):
    print('work2----{}'.format(i))
    yield i

g1 = work1()
g2 = work2()
while True:
  try:
    next(g1)
    next(g2)
  except StopIteration:
    pass

2、什么是协程

协程是python中另外一个实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源)。为什么说它是一个执行单元,因为它自带CPU上下文。这样只要在合适gr时机,我们可以把一个协程切换到另一个协程。只要这个过程中保存或恢复CPU上下文那么程序还是可以运行的

通俗的理解:在一个线程的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数记忆什么时候再切换到原来的函数都有开发者自己确定。

 

3、协程和线程差异

在实现多任务时,线程切换从系统层面远不止保存和恢复CPU上下文这么简单。操作系统为了程序运行的高效性每个线程都有自己花奴才能Cache等数据,操作系统还会帮你做这些数据的恢复操作。所以线程的切换比较耗性能。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住。

 

3、greelet

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

import time

def work1():
  for i in range(10):
    print('work1:', i)
    g2.switch()
    time.sleep(0.1)

def work2():
  for i in range(10):
    print('work2:', i)
    g1.switch()
    time.sleep(0.1)
g1 = greenlet(work1)
g2 = greenlet(work2)

g1.switch()

通过switch()方法切换协程

 

4、gevent

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

import  gevent

def work1():
  for i in range(10):
    print('work1:', i)
    gevent.sleep(0.1)#耗时操作,程序遇到了sleep就会切换至下一协程执行
    
def work2():
  for i in range(10):
    print('work2:', i)
    gevent.sleep(0.4)
    
#创建两个协程
g1 = gevent.spawn(work1)
g2 = gevent.spawn(work2)
# g1.join() #设置主线程等待子协程执行之后再往下执行
# # g2.join()

gevent.sleep(5)
for i in range(10):
  print('10-------', i)

程序补丁:money.patch_all()

打补丁作用,只有耗时的地方(大部分),自动切换任务,不局限于gevent.sleep()

import gevent
from gevent import monkey
monkey.patch_all()

二、进程、线程、协程对比

扩展一:进程池

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

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

  Pool常用方法:

    apply_async(func[, args[,kwds]]):使用非阻塞方式调用func(并行执行,阻塞方式必须等待上一个进程退出才能执行下一个进程),args为传递func的参数列表, kwds为传递给func的关键字参数列表;

    close():关闭Pool,使其不再接受新的任务

    join():主进程阻塞,等待子进程退出,必须在close之后使用

import os
import time
from multiprocessing import Pool
def work1():
  for i in range(10):
    print(F'work1---{i}--{os.getpid()}-')
    time.sleep(0.1)
    
def work2():
  for i in range(10):
    print(F'work2---{i}--{os.getpid()}-')
    time.sleep(0.1)
    
if __name__ == '__main__':
  po = Pool(5)
  for i in range(10):
    po.apply_async(work1)
    
  for i in range(10):
    po.apply_async(work2)
  
  po.close()
  po.join()

扩展二:进程池的Queue

  如果要使用Pool创建进程,就需要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue,否则会得到一条如下的错误信息:

扩展三:线程池

内置模块: from concurrent.futures import ThreadPoolExcutor

第三方模块:threadpool

 

进程线程协程比较:

1、进程是资源分配单位

2、线程是操作系统调度的单位

3、进程切换需要的资源最大,效率很低

4、线程切换需要的资源一般,效率一般(当然在不考虑GIL的情况下)

5、协程切换任务资源很小,效率高

6、多进程,多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中,所以是并发

7、python中的线程由于GIL锁的存在,并不能够实现并行

 

posted @ 2019-08-27 18:29  sinder2018  阅读(29)  评论(0)    收藏  举报