多任务--进程和协程

什么是进程?

程序在没运行起来之前是死的,程序运行起来后就是进程,进程跟程序之间的区别就是进程拥有资源,例如登陆QQ之后,QQ可以调用声卡、摄像头等。

 

两个进程之间的通信,利用队列Queue,放到内存里,一个写,一个读。缺点就是队列只能在同一个程序或者电脑中运行,要在多台电脑之间进行,用到缓存redis。

import multiprocessing


def download_data(q):
    # 下载数据
    data = [11, 22, 33, 44]
    for i in data:
        q.put(i)


def analysis_data(q):
    while True:
        data = q.get()
        print(data)
        if q.empty():
            break


def main():
    q = multiprocessing.Queue(4)
    p1 = multiprocessing.Process(target=download_data, args=(q, ))
    p2 = multiprocessing.Process(target=analysis_data, args=(q, ))
    p1.start()
    p2.start()


if __name__ == "__main__":
    main()

 进程池

进程池里面有预先指定好的进程数,当很多任务一起过来时,如果进程被调用完,其他任务就一直等着,等待完成任务的进程来调用它们。好处就是减少了进程的创建和销毁所花的时间和资源。


 

协程

1、迭代器

在原来的基础上去得到一个新的东西,这就是迭代。

# 判断是否可迭代
>>> from collections import Iterable
>>> isinstance([11, 22, 33], Iterable)
True
from collections import Iterable


class Classmatename():
    def __init__(self):
        self.names = list()

    def add(self, name):
        self.names.append(name)

    def __iter__(self):
        """如果想要一个对象称为一个 可以迭代的对象,既可以使用for,那么必须实现__iter__方法"""
        pass

classmatename = Classmatename()

classmatename.add("张三")
classmatename.add("李四")
classmatename.add("王五")

print(isinstance(classmatename, Iterable)) # ------》 True

 但如何循环打印列表里面的值呢?

首先先想一下当我们循环打印列表的时候发生了什么,每次循环,就会打印一个值,下一次循环打印下一个值,由此我们应该可以猜想有一个东西,在记录着我们打印到哪了,这样才可以在下一次打印接下来的值。对于我们上面创建的类,现在它已经是可以迭代了,但是还没有一个东西来记录它应该打印什么,打印到了哪里。

for item in xxx_obj:
    pass

1、判断xxx_obj是否是可以迭代;
2、在第一步成立的前提下,调用iter函数,得到xxx_obj对象的__iter__方法的返回值;
3、__iter__方法的返回值是一个  迭代器

 什么是迭代器?一个对象里面有“__iter__”方法,我们称之为  可以迭代。如果"__iter__"方法返回的对象里面既有“__iter__”又有"__next__"方法,那么我们称这个对象为  迭代器

所以为了可以打印,这里需要两个条件:1、有iter值;2、iter值返回一个对象引用。这个对象里面除了iter方法外还有一个next方法。

因此大体过程就是for循环调用,首先判断这个对象是不是可迭代的(有"__iter__",是),接下来自动用iter方法调用"__iter__",获取返回的“对象引用”。接下来for循环调用这个对象里面的“__next__”方法,调一次,next方法返回什么,就输出什么给item打印。

from collections import Iterator

classmate_Iterator = iter(classmatename) # 用iter获取迭代器
# 判断是否是迭代器
print(isinstance(classmate_Iterator, Iterator))
import time


class Classmatename():
    def __init__(self):
        self.names = list()
        self.current_num = 0

    def add(self, name):
        self.names.append(name)

    def __iter__(self):
        """如果想要一个对象称为一个 可以迭代的对象,既可以使用for,那么必须实现__iter__方法"""
        # 创建实例对象
       return ClassIterator(self) # self指向这个类本身,然后传给类ClassIterator

class ClassIterator(): def __init__(self, obj): self.obj = obj # self.obj指向实例对象Classmatename self.current_num = 0 def __iter__(self): pass def __next__(self): if self.current_num < len(self.obj.names): ret = self.obj.names[self.current_num] self.current_num += 1 # 注意这里的self.current_num=0要放在__init__下,如果current_num=0放在__next__下,则for循环每次调用__next__时,current_num都会被重新记零 return ret else: raise StopIteration # 抛出异常,告诉for循环可以结束了

classmatename = Classmatename() classmatename.add("张三") classmatename.add("李四") classmatename.add("王五") for item in classmatename: print(item) time.sleep(1)

 另一种办法,写在一起

import time


class Classmatename():
    def __init__(self):
        self.names = list()
        self.current_num = 0

    def add(self, name):
        self.names.append(name)

    def __iter__(self):
        """如果想要一个对象称为一个 可以迭代的对象,既可以使用for,那么必须实现__iter__方法"""
        # 创建实例对象
        return self # 返回自身给for循环调用里面的__next__

    def __next__(self):
        if self.current_num < len(self.names):
            ret = self.names[self.current_num]
            self.current_num += 1
            return ret
        else:
            raise StopIteration # 抛出异常,告诉for循环可以结束了



classmatename = Classmatename()
classmatename.add("张三")
classmatename.add("李四")
classmatename.add("王五")


for item in classmatename:
    print(item)
    time.sleep(1)

 迭代器的优点

优先我们先来了解下Python2中的range和xrange区别

>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> xrange(10)
xrange(10)

由上图可知,range和xrange的区别就是range会直接返回生成的结果,而xrange返回的是生成这个结果的方式,什么时候需要调用里面的值,它才会去生成,所以xrange占用的内存空间要小很多。(这个问题在Python3已经解决了,Python3的range相当于Python2里面的xrange)

迭代器也是一样,生成的是调用值的方式,什么时候要调用值,才去生成,因此占用很少的内存空间。

# 用迭代器的方法生成斐波那契数列
class Fibonacci(object):
    def __init__(self, all_num):
        self.all_num = all_num
        self.current_num = 0
        self.a = 0
        self.b = 1

    def __iter__(self):
        return self

    def __next__(self):
        if self.current_num < self.all_num:
            ret = self.a
            self.a, self.b = self.b, self.a+self.b
            self.current_num += 1
            return ret
        else:
            raise StopIteration

fibo = Fibonacci(10)
for num in fibo:
    print(num)

a = (11, 22, 33)
list(a)      # 首先生成一个空列表,然后循环调用a里面的迭代器,把值一个个放到列表中去
[11, 22, 33] 

生成器(是一种特殊的迭代器)

生成生成器的第一种方式

>>> nums1 = [x*2 for x in range(10)]
>>> nums1
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
>>> nums2 = (x*2 for x in range(10))
>>> nums2
<generator object <genexpr> at 0x00AF5180>

其中,num2就是生成器。可以通过for循环遍历里面的值。num1和num2的区别就是前面说的,num2不占用空间,值在需要时才会被生成。

生成生成器的第二种方式

# 用普通函数生成斐波那契数列
def create_num(all_num):
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        print(a)
        a, b = b, a+b
        current_num += 1

         
create_num(10)

用生成器的方法生成斐波那契数列,添加yield

# 生成器
def create_num(all_num):
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        yield a  # 如果一个函数中有yield语句,那么这个就不再是函数,而是一个生成器的模板
a, b
= b, a+b current_num += 1 # 如果在调用create_num的时候,发现这个函数有yield,那么不在是调用函数,而是创建一个生成器对象 obj = create_num(10)
for num in obj: # for循环调用过程。首先创建obj,当开始执行for循环时,程序开始向下走,
  print(num)  # 当到达yield a的时候停止,把a的值传给num,打印出来。然后for继续调用,于是从停止的位置也就是yield那里继续向下执行,而不会从函数的开头重新运行了。

 用 "ret = next(obj)" 可以一次调用一个值用来验证下。

def create_num(all_num):
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        yield a
        a, b = b, a+b
        current_num += 1
    return ".....ok....."

obj = create_num(10)
while True:
    try:
        ret = next(obj)
        print(ret)
    except Exception as ret:
        print(ret.value) # 这个value就是return返回的值
        break

 第二中调用生成器的方法:send() (一般不用做第一次启动,如果非要,send()里面只能传递None)

def create_num(all_num):
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        ret = yield a
        print(">>>>>>>>ret>>>>>>>", ret)
        a, b = b, a+b
        current_num += 1


obj = create_num(10)
ret = next(obj)
print(ret)
ret = obj.send("hahaha") # 首先程序运行到"yield a"时停止,把“a=0”传给ret打印出来。接下来运行"send("hahaha")”,从“ret=yield a“开始,此时"yield a"并没有返回值给等号左边的ret,那么就把"hahaha"
print(ret)               # 传给ret,由"print(">>>>>>>>>ret>>>>>>>>>", ret)"打印出来。再继续执行接下来的步骤。
# 结果返回
0
>>ret>>>>>>> hahaha
1

 总结:迭代器特点是占用内存空间小,什么时候用,什么时候生成;

  生成器有迭代器的特点,而且它最大的特点就是可以执行到一半时暂停,返回结果,然后再继续在原来基础上继续执行。

 用yield实现多任务

import time


def task_1():
    while True:
        print("--------1---------")
        time.sleep(0.1)
        yield


def task_2():
    while True:
        print("--------2---------")
        time.sleep(0.1)
        yield


def main():
    t1 = task_1()
    t2 = task_2()
    while True:
        next(t1)
        next(t2)


if __name__ == "__main__":
    main()

 

协程(单进程单线程)最大的意义,把原本等待的时间利用起来去做别的事情。

协程依赖于线程,线程依赖于进程。

有时程序写了很多行了,用的是time.sleep(),这时不想用gevent里面的gevent.sleep()方法,可以给程序打补丁

 

小案例,用gevent实现图片下载

import urllib.request
import gevent
from gevent import monkey


monkey.patch_all()


def download_img(file_name, url):
    img = urllib.request.urlopen(url)
    img_content = img.read()
    with open(file_name, "wb") as f:   # 这里没有用time模块是因为网络下载过程中本来就会延时,相当于我们前面实例的time.sleep()
        f.write(img_content)


def main():
    gevent.joinall([
        gevent.spawn(download_img, "1.jpg", "https://rpic.douyucdn.cn/live-cover/appCovers/2018/08/31/3279944_20180831104533_small.jpg"),
        gevent.spawn(download_img, "2.jpg", "https://rpic.douyucdn.cn/live-cover/appCovers/2018/11/14/910907_20181114154402_small.jpg")
    ])


if __name__ == "__main__":
    main()

 

原文地址:https://www.cnblogs.com/linyuhong/p/10006944.html