• 多任务--进程和协程


    什么是进程?

    程序在没运行起来之前是死的,程序运行起来后就是进程,进程跟程序之间的区别就是进程拥有资源,例如登陆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()

     

  • 相关阅读:
    vue项目使用async await 封装 axios
    vue实现预览功能(包括doc,pdf,图片,视频)
    vue中实现下载文件功能
    vue项目中加入拖放排序功能
    Vue项目中生成二维码
    position跟display、overflow、float这些特性相互叠加后会怎么样?
    localStorage使用注意
    webpack 使用总结
    cookie作用域
    语法糖的理解
  • 原文地址:https://www.cnblogs.com/linyuhong/p/10006944.html
Copyright © 2020-2023  润新知