第六阶段:多任务

2.2多任务

2.2.1线程

2.2.1.1多任务介绍

  1. 相关概念介绍
    • 对于一个计算机而言,一个cpu一次只可以运行一个任务,对于单核的cpu的计算机而言,当有多个任务需要运行时,必须先执行一个任务,只有该任务停止后才能执行另一个任务,不能同时运行(计算机会随机执行一个任务,执行一段时间后就执行下一个任务,然后循环执行这些任务,由于计算机执行时间很短,比如说0.0000000001秒,所以在一秒内,一个cpu可以将这些任务执行无数次,在用户看来就相当于这些任务同时在运行而且每个任务都一直在运行且没有中断,在用户看来一个计算机可以运行多个任务,用户就认为一个cpu一次可以执行多个任务,其实这是假的多任务,称之为并发(任务数目大于cpu数量)),而对于多个cpu而言,如果任务的数目小于或者等于cpu数量,一个cpu就可以只运行一个任务(不用退出来运行其它任务),多个cpu就可以实现多个任务同时运行,在用户看来一个计算机可以运行多个任务,这种多任务是真的多任务,称为并行(任务数量小于等于cpu数目)

    • 在现实中一般计算机都是并发执行任务的

    • 当想要多个while True同时进行可以用多任务来实现

    • 多任务demo
      import time
      import threading

      """
      range()函数返回一个整数列表
      range(0,5)会产生一个列表[0, 1, 2, 3, 4]
      range(0,10,2)会产生一个列表[0,2,4,6,8],这里的2表示步长
      range(0,-5,-1)会产生一个列表[0,-1,-2,-3,-4]
      """
      def sing():
          for i in range(5):
              print("sing")
              time.sleep(1)  # time模块的sleep方法可以实现程序休息1秒在执行的功能
      
      
      def dance():
          for i in range(5):
              print("dance")
              time.sleep(1)
      
      
      def main():
          t1 = threading.Thread(target=sing)  # Thread是一个类,t1是一个对象,target用来指向要运行的函数
          t2 = threading.Thread(target=dance)
          t1.start()  # 子线程1开始执行
          t2.start()  # 子线程2开始执行
      
      if __name__ == "__main__":
          main()
      

2.2.1.2线程的概念

  1. 对上面demo的理解:线程可以简单的理解为一个python程序执行时,解释器会从上往下一行一行的执行代码,而这一行一行执行代码需要一个东西才能实现,这个东西就是线程,一个程序开始执行时就会产生一个主线程,主线程来一行一行从上往下执行代码;当执行到t1.start()时开始创建一个子线程1,子线程1会寻找要执行的函数(target指向的函数就是子线程要执行的函数)来执行,当主线程执行到t2.start()时有创建一个子线程2,子线程2寻找要执行的函数来执行,由于python解释器一次只能执行一个线程,如果没有设定线程休息,python解释器会随机调用线程来执行,当主线程执行完毕后会等待子线程执行完毕后才结束程序的执行,当子线程执行完毕如果主线程也执行完毕后会结束程序的执行。如果主线程结束而子线程没有结束程序会等所有的子线程执行完毕后才会结束整个程序的执行,如果设置了线程休息,则在休息的时间内python解释器会调用其它线程
  2. 注意事项:在没有设置线程休眠的情况下,线程的执行是没有先后顺序的,是由计算机随机调用的,至于出现了子线程先执行的错觉是因为子线程需要执行的时间太短的原因,具体可以看2.2.1.3节第二点注意中的代码实例

2.2.1.3查看正在运行的线程

  1. demo演练
    import time
    import threading

    """
    range()函数返回一个整数列表
    range(0,5)会产生一个列表[0, 1, 2, 3, 4]
    range(0,10,2)会产生一个列表[0,2,4,6,8],这里的2表示步长
    range(0,-5,-1)会产生一个列表[0,-1,-2,-3,-4]
    """
    def sing():
        for i in range(5):
            print("sing--%d" % i)
            time.sleep(1)  # time模块的sleep方法可以实现程序休息1秒在执行的功能
    
    
    def dance():
        for i in range(5):
            print("dance--%d" % i)
            time.sleep(1)
    
    
    def main():
        t1 = threading.Thread(target=sing)  # Thread是一个类,t1是一个对象,target用来指向要运行的函数
        t2 = threading.Thread(target=dance)  # 当线程指向的函数执行完毕后子线程就结束了
        t1.start()  # 子线程1开始执行
        print("111")
        t2.start()  # 子线程2开始执行
        print("222")
        while True:
            print(threading.enumerate())  # threading.enumerate()返回的是一个当前正在运行的线程列表,
            if len(threading.enumerate()) == 1:
                break
            time.sleep(1)
    """
    一般程序的enumerate()返回的是一个元祖(元祖中包含两个内容:分别为索引和值)
    for i in enumerate(range(0, 5)):
        print(i)
    而thread模块的enumerate()返回的是一个列表,可以来查看当前正在运行的线程
    """
    
    if __name__ == "__main__":
        main()
    
  2. 注意:只有调用start()方法时才开始创建线程;函数结束意味着一个线程结束;线程之间本质上是没有执行顺序的,但在python程序执行时,会产生一个错觉:会感觉当主进程和子进程都要运行时会优先执行子进程的程序再执行主进程的程序,这是因为子程序的内容需要执行的时间极短而被快速执行,如果子程序的执行内容需要时间很长,计算机就会随机执行主线程和子线程
    import time
    import threading

    扫描二维码关注公众号,回复: 11531251 查看本文章
    def test01(*temp_01):  # 传递的元祖内有几个数值,这里就需要写几个参数,或者直接写一个元祖参数亦可
        global gl_list
        for temp in range(temp_01[0]):  # 表示将索引为0的值传入
            gl_list += 1
        print("---test01---%d" % gl_list)
    
    def test02(temp_03):
        global gl_list
        for temp in range(temp_03):
            gl_list += 1
        print("---test02---%s" % gl_list)
    
    gl_list = 0
    
    
    def main():
        # args指定创建进程是传递什么参数给函数,args后面的参数必须是元祖类型的
        t1 = threading.Thread(target=test01, args=(100000000, 200))
        t2 = threading.Thread(target=test02, args=(100000000, ))
        t1.start()
        t2.start()
        print("---test---%d" % gl_list)
    
    
    if __name__ == "__main__":
        main()
    

2.2.1.4通过继承Thread类完成创建进程

import time
import threading
class MyThread(threading.Thread):
    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = "I'm " + self.name + ' @ ' + str(i)  # name属性中保存的是当前线程的名字
            print(msg)


if __name__ == "__main__":
    t = MyThread()
    t.start()  # 开启线程后会自动调用类中的run方法(必须是run方法,不能是其它方法名),没有run方法线程就不会使用,定义一个类对象只会创建一个进程

2.2.1.5多线程共享全局变量-args参数

  1. 代码演练
    import time
    import threading

    def test01(temp):
        temp.append(33)
        print("---test01---%s" % temp)
    
    def test02(temp):
        print("---test02---%s" % temp)
    
    gl_list = [11, 22]
    
    
    def main():
        # args指定创建进程是传递什么参数给函数,args后面的参数必须是元祖类型的
        t1 = threading.Thread(target=test01, args=(gl_list, ))
        t2 = threading.Thread(target=test02, args=(gl_list, ))
        t1.start()
        time.sleep(1)
        t2.start()
    
    
    if __name__ == "__main__":
        main()
    
  2. 总结:一般而言,在实际程序执行时,线程需要执行的时间越短该线程就越有可能最先被执行完毕;当两个线程都特别小时线程都迅速执行完毕,就会显示一种先创建的线程执行完毕后后创建的线程才执行的错觉;当一个线程特别小另一个线程特别大就会出现小的线程先执行完毕大的线程后执行完毕;当两个线程都特别大时,两个线程会交替执行直至都执行完毕(可以理解为计算机给每个线程都固定一个执行时间,当一个线程执行时间到了无论该线程是否执行完毕都停止执行让另一个线程执行,当计算机又执行之前未执行完毕的线程时会继续执行之前未执行的程序,反复如此,计算机会尽量保证每个程序的执行时间相同)

2.2.1.6同步、互斥解决资源竞争

  1. 资源竞争:当两个线程都对同一个全局变量进行操作时就会产生资源竞争(谁先调用谁后调用的问题)

  2. 多线程都会出现资源竞争问题

  3. 同步的概念:同步就是协同步调,按照事先预定的顺序执行

  4. 互斥锁:某个线程要更改共享数据时,先将其锁定保证其它线程不能修改该数据;当该线程执行完毕后将进行解锁,这是其它线程才可以进行修改全局变量(可以用排队上厕所来理解着个事情)

  5. 步骤:
    # 创建锁(默认不上锁)
    mutex = threading.Lock()
    # 锁定
    mutex.acquire()
    # 释放(解锁)
    mutex.release()

  6. 实例
    import time
    import threading

    def test01(*temp_01):  # 传递的元祖内有几个数值,这里就需要写几个参数,或者直接写一个元祖参数亦可
        global gl_list
        mutex.acquire() # 锁定,如果锁是解开当就立刻锁定,如果锁是锁定当就进行堵塞,锁一旦解开立刻解锁
        for temp in range(temp_01[0]):  # 表示将索引为0的值传入
            gl_list += 1
        mutex.release()
        print("---test01---%d" % gl_list)
    
    def test02(temp_03):
        global gl_list
        mutex.acquire()
        for temp in range(temp_03):
            gl_list += 1
        mutex.release()
        print("---test02---%s" % gl_list)
    
    gl_list = 0
    # 创建锁
    mutex = threading.Lock()
    
    def main():
        # args指定创建进程是传递什么参数给函数,args后面的参数必须是元祖类型的
        t1 = threading.Thread(target=test01, args=(100000000, 200))
        t2 = threading.Thread(target=test02, args=(100000000, ))
        t1.start()
        t2.start()
    
    
    if __name__ == "__main__":
        main()
    

2.2.1.7死锁、银行家算法

  1. 死锁:在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等带对方的资源,就会造成死锁(但在实际情况下死锁很少发生)

  2. 死锁的案例
    import threading
    import time

    class MyThread1(threading.Thread):
        def run(self):
            mutexA.acquire()
            print(self.name + "---do1---up---")
            time.sleep(1)
            mutexB.acquire()  # 此时要等待另一个进程解锁
            print(self.name + "---do1---down---")
            mutexB.release()
            mutexA.release()
    
    
    class MyThread2(threading.Thread):
        def run(self):
            mutexB.acquire()
            print(self.name + "---do2---up---")
            time.sleep(1)
            mutexA.acquire()  # 此时要等待另一个进程解锁,产生死锁
            print(self.name + "---do2---down---")
            mutexA.release()
            mutexB.release()
    
    mutexA = threading.Lock()
    mutexB = threading.Lock()
    
    
    def main():
        t1 = MyThread1()
        t2 = MyThread2()
        t1.start()
        t2.start()
    
    
    if __name__ == '__main__':
        main()
    
  3. 避免死锁的方法

    • 添加超时时间(当一个锁在规定的时间内不能上锁那我就不上锁,避免等待堵塞)
    • 程序设计时避免死锁(银行家算法:就是一个银行如何将用户存储的10万以20万进行放贷的方法)

2.2.1.8多任务版聊天器(收发同时进行)

  1. socket套接字是全双工,所以可以实现收发同时进行
  2. 源码
    • 主机1
      import socket
      import threading

      def recv_mag(udp_socket):
      	"""接受数据并显示"""
      	while  True:
      		recv_data = udp_socket.recvfrom(1024)
      		print(recv_data)
      
      
      def  send_msg(udp_socket, dest_ip, dest_port):
      	while True:
      		send_data = input("请输入要发送的数据:")
      		udp_socket.sendto(send_data.encode("utf-8"), (dest_ip, dest_port))
      
      
      def  main():
      	udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
      	udp_socket.bind(("", 7891))
      	dest_ip = input("请输入对方的ip:")
      	dest_port = int(input("请输入对方的port:"))
      	t_recv = threading.Thread(target=recv_mag, args=(udp_socket, ))
      	t_send = threading.Thread(target=send_msg, args=(udp_socket, dest_ip, dest_port))
      	t_recv.start()
      	t_send.start()
      	
      
      if __name__ == '__main__':
      	main()
      
    • 主机2
      import socket
      import threading

      def recv_mag(udp_socket):
      	"""接受数据并显示"""
      	while  True:
      		recv_data = udp_socket.recvfrom(1024)
      		print(recv_data)
      
      
      def  send_msg(udp_socket, dest_ip, dest_port):
      	while True:
      		send_data = input("请输入要发送的数据:")
      		udp_socket.sendto(send_data.encode("utf-8"), (dest_ip, dest_port))
      
      
      def  main():
      	udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
      	udp_socket.bind(("", 7890))
      	dest_ip = input("请输入对方的ip:")
      	dest_port = int(input("请输入对方的port:"))
      	t_recv = threading.Thread(target=recv_mag, args=(udp_socket, ))
      	t_send = threading.Thread(target=send_msg, args=(udp_socket, dest_ip, dest_port))
      	t_recv.start()
      	t_send.start()
      	
      
      if __name__ == '__main__':
      	main()
      

2.2.2进程

2.2.2.1使用进程实现多任务

  1. 代码实现
    import time
    import multiprocessing

    def sing():
        for i in range(5):
            print("sing--%d" % i)
    
    def dance():
        for i in range(5):
            print("dance--%d" % i)
    
    
    def main():
        p1 = multiprocessing.Process(target=sing)
        p2 = multiprocessing.Process(target=dance)
        p1.start()
        for i in range(0, 5):
            print("main--%d" % i)
        p2.start()
    
    if __name__ == "__main__":
        main()
    
  2. 查看程序是否有多个进程在同时进行:新开一个界面,输入命令:ps -aux即可查看当前正在运行的进程(要保证在查看时进程仍然在执行)

  3. 一个主进程创建子进程后,子进程会拥有主进程中所有的代码和资源(子进程和主进程而言除了要执行的程序不同其它都一样,源代码都有),但是是通过写时拷贝的方式进行的,也就是说子进程要修改主代码时就复制一份,不修改主代码就不复制,因此进程耗费大量的资源

2.2.2.2进程和线程的区别

  1. 进程是一个程序运行需要所有资源的总称(包括代码)
  2. 一个进程中至少有一个执行的主线程(进程中代码的执行是靠线程执行的),进程是资源分配的单位
  3. 线程实现多任务是一个程序中有多个线程同时执行,每个线程执行不同的程序;而进程实现多任务是拥有多个进程,每个进程通过主线程实现不同的程序;
  4. 没有进程就没有线程
  5. 理解:可以将一条流水线(所有东西)视为一个进程,流水线的一名工人视为一个线程;一个流水线有多个工人工作就是用多线程实现多任务,多个流水线,每个流水线只有一个工人工作就是用进程实现多任务;最大程度上实现多任务是多个进程且每个进程多个线程(也就是多进程和多线程同时执行)
  6. 不同进程间是独立的,但可以实现通信;同一进程的不同线程间是共享全局变量的

2.2.2.3进程间的通信

2 .2.2.3.1通过队列实现进程间的通信(实现数据共享)

  1. 队列先进先出,栈先进后出

  2. 代码实例(将需要共享的数据放在队列中)
    import multiprocessing

    def download_from_web(q):
      """下载数据"""
        data = [11, 22, 33, 44]
        for temp in data:
            q.put(temp)
        print("结束")
    
    
    def analysis_data(q):
      """处理数据"""
        writting_analysis_data = list()  # 定义一个空列表,等价于[]
        while True:
            data = q.get()  # 通过get方法取出队列中的数据,先进先出
            writting_analysis_data.append(data)  # 将从队列中取出的数据存储到列表中
            if q.empty():  # 判断队列是否为空
                break
        print(writting_analysis_data)
    
        
    def main():
        # 创建一个队列
        q = multiprocessing.Queue()
        # 创建连个进程
        p1 = multiprocessing.Process(target=download_from_web, args=(q, ))
        p2 = multiprocessing.Process(target=analysis_data, args=(q, ))
        p1.start()
        p2.start()
    
    
    if __name__ == '__main__':
        main()
    

2.2.2.4进程池Pool

  1. 概述:设定一个具有固定进程数量的进程池(并不是进程越多越好,cpu处理进程的能力有限,过多的进程会导致CPU卡顿),将需要执行的任务放置到进程池中,当进程池中某个进程中的任务执行完毕后在将进程池外的其他的未被执行的任务放置到进程池中运行,来实现进程的重复利用

  2. 当需要处理的任务数量不确定时一般使用进程池来循环处理任务

  3. 进程池的创建
    from multiprocessing import Pool
    import os, time, random

    def worker(msg):
        t_start = time.time()  # 返回当前的时间戳
        print("%s开始执行,进程号为%d" % (msg, os.getpid()))  # 返回当前进程的进程号
        time.sleep(random.random() * 2)  # random.random()可以随机生成0~1之间的浮点数
        t_stop = time.time()
        print(msg, "执行完毕,耗时%.2f" % (t_stop - t_start))
    
    
    po = Pool(3)  # 创建一个进程池,里面最多可以存放三个进程(创建进程池的同时就创建了3个进程)
    for i in range(0,10):  # 生成10个任务
        po.apply_async(worker, (i, ))
        # apply_async(要调用的目标,(传递给目标的参数元祖, )),po.apply_async让任务存储到进程池的特定位置中,然后进程池执行特定位置的任务,没有被执行的任务会在特定位置等待
    print("---start---")
    po.close()  # 关闭进程池,表示进程池的特定位置不在接受任务,必须要关闭进程池否则程序无法关闭
    po.join()  # 等待进程池中所有的子进程执行完成,必须放在close语句之后
    print("---end---")
    
  4. 对于多进程执行多任务而言,主进程不会等子进程执行完毕才结束程序的执行,所以要保证子进程全部执行完毕就必须要有po.join()

2.2.2.5案例:文件夹拷贝器(多任务版)

  1. 代码实例
    import os
    import multiprocessing

    def copy_file(q, file_name, old_folder_name, new_folder_name):
        # print("===拷贝==%s从%s拷贝到%s" % (file_name, old_folder_name, new_folder_name))
        old_f = open(old_folder_name + "/" + file_name, "rb")
        content = old_f.read()
        old_f.close()
        new_f = open(new_folder_name + "/" +file_name, "wb")
        new_f.write(content)  # 这里注注意要下载的文件夹下的文件夹是写入的
        new_f.close()
        # 如果拷贝完文件就向队列中写入一个消息表明已经完成
        q.put(file_name)
    
    def main():
        # 获取用户要拷贝文件夹的名字
        old_folder_name = input("请输入要拷贝的文件夹名字:")
        # 创建一个新的文件夹
        try:  # 防止创建的文件夹存在报错
            new_folder_name = old_folder_name + "_other"
            os.mkdir(new_folder_name)  # 使用os模块中的mkdri来创建文件夹,文件夹存在就报错
        except:
            pass
        # 获取文件夹中所有待拷贝的文件的名字:listdir(文件夹的位置)会返回文件夹中所有文件的名字,文件夹也会被返回
        file_names = os.listdir(old_folder_name)  # os.listdir(文件的路径,直接写文件名则只能在当前文件所在的目录下进行查找)返回的是一个列表
        # print(file_names)
        # 创建进程池
        po = multiprocessing.Pool(5)
        # 创建队列:用来判断拷贝了多少文件
        q = multiprocessing.Manager().Queue()  # 在进程池中使用队列必须要用Manager()下的Queue()类来创建
        # 想进程池中添加拷贝文件的任务
        for file_name in file_names:
            po.apply_async(copy_file, (q, file_name, old_folder_name, new_folder_name))
        po.close()
        # po.join()  # 这里通过while循环判断是否拷贝完毕来结束主进程所以不需要等待子进程执行完毕
        all_file_num = len(file_names)
        # print("原文件共有%d个文件" % all_file_num)
        cp_file_num = 0  # 判定已经拷贝文件的数量
        temp = 1
        while True:
            # file_name_get = q.get()
            q.get()  # 当队列中没有数据时会堵塞
            # print("第%d个文件%s已经完成拷贝" % (temp, file_name_get))
            print("\r拷贝的进度为%.2f%%" % ((cp_file_num + 1) / all_file_num * 100),end="")  # \r的作用是返回当前鼠标所在位置的行首
    
            # temp += 1
            cp_file_num += 1
            if cp_file_num == all_file_num:  # 通过判断是否拷贝完毕来结束子进程
                print("")
                break
    
    
    if __name__ == '__main__':
        main()
    

2.2.3协程

2.2.3.1迭代器

  1. 迭代:如果给定一个list或tuple或其他可以遍历的类型,我们可以通过for循环来遍历这个list或tuple或其它类型,这种遍历我们称为迭代(Iteration)。

  2. 迭代的实现过程:当迭代的对象是一个类的对象时,迭代是通过以下步骤实现

    • 第一步:判断对象是否可以迭代(对象所在的类有__iter__(self)方法),有就可以迭代)(可以通过isinstance()方法快速判断对象是否可以迭代),有该方法并不表示迭代可以进行,只是表示该对象可以被迭代
      from collections import Iterable

      class Demo(object):
          pass
      
      
      demo = Demo()
      print("对象是否可以别迭代:", isinstance(demo, Iterable))  #判断对象是否和类Iterable有关联,有就可以迭代
      # print中使用,可以将,之间的直接内容输出,将()内的内容视为一个元祖,但这种输出之间默认会有空格
      
    • 第二步:系统会自动调用iter(所要迭代的对象)方法,这个方法会自动调用所要迭代的对象所在类的__inter__(self)方法,对象所在的类的__inter__(self)方法会得到一个返回值(比如返回的是另一个类的一个对象)

    • 第三步:返回值返回的是一个迭代器(返回值所在的类用__iter(self)方法和__next__(self)方法,则称这个类的对象是一个迭代器),迭代遍历的内容实际上是__next__(self)方法中的内容
      from collections import Iterable
      from collections import Iterator

      class Demo(object):
          def __iter__(self):
              return DemoOne()
      
      
      class DemoOne(object):
          def __iter__(self):
              pass
      
      
          def __next__(self):
              pass
      
      
      demo = Demo()
      print("对象是否可以别迭代:", isinstance(demo, Iterable))  #判断对象是否和类Iterable有关联,有就可以迭代
      temp = iter(demo)
      print("判断temp是否返回的是否是一个迭代器:", isinstance(temp, Iterator))  #判断对象是否和类Iterator有关联,有就是迭代器
      # print中使用,可以将,之间的直接内容输出,将()内的内容视为一个元祖,但这种输出之间默认会有空格
      
  3. 完整代码实例:
    from collections import Iterable
    from collections import Iterator
    import time
    class Demo(object):
    def init(self):
    self.names = list()

        def add(self, name):
            self.names.append(name)
    
    
        def __iter__(self):
            return DemoOne(self)  # 这里的self(这里写demo就直接指定哪个对象传递)表示当前对象,会传递给obj
    
    
    class DemoOne(object):
        def __init__(self, obj):
            self.obj = obj
            self.current_num = 0
    
    
        def __iter__(self):
            pass
    
    
        def __next__(self):
            if len(self.obj.names) > self.current_num:
                ret = self.obj.names[self.current_num]
                self.current_num += 1
                return ret
            else:
                raise StopIteration  # 结束迭代器
    
    
    
    demo = Demo()
    demo.add("xiaoming")
    demo.add("xiaoli")
    demo.add("xiaomei")
    # print("对象是否可以别迭代:", isinstance(demo, Iterable))  #判断对象是否和类Iterable有关联,有就可以迭代
    # temp = iter(demo)  iter(demo)等价于demo.__iter__()
    # print("判断temp是否返回的是否是一个迭代器:", isinstance(demo.__iter__(), Iterator))  #判断对象是否和类Iterator有关联,有就是迭代器
    # print中使用,可以将,之间的直接内容输出,将()内的内容视为一个元祖,但这种输出之间默认会有空格
    for temp in demo:  # 会反复迭代__next__中的返回值(使用for来调用迭代器)
        print(temp)
        time.sleep(1)
    
  4. 完整代码升级:
    from collections import Iterable
    from collections import Iterator
    import time
    class Demo(object):
    def init(self):
    self.names = list()
    self.current_num = 0

        def add(self, name):
            self.names.append(name)
    
    
        def __iter__(self):
            return self  # 返回对象自己,省略一个类的编写
    
    
        def __next__(self):
            if len(self.names) > self.current_num:
                ret = self.names[self.current_num]
                self.current_num += 1
                return ret
            else:
                raise StopIteration  # 结束迭代器
    
    
    demo = Demo()
    demo.add("xiaoming")
    demo.add("xiaoli")
    demo.add("xiaomei")
    for temp in demo:  # 会反复迭代__next__中的返回值
        print(temp)
        time.sleep(1)
    

2.2.3.2迭代器的应用

  1. 迭代器可以不占用很大的内存而生成一些需要的数据(例如要在某个时刻要使用一些数据但是现在不用,有两种方式:一种是将这些数据放置到一个列表中等待需要使用的时候在调用(需要占用大量内容),另一种方式是将生成数据的方法保存到程序中等待需要使用的时候调用该方法即可(占用内存小))

  2. 强制数据转化也是一种迭代器的应用(例如一个元祖转化为列表:先产生一个新的空列表,使用迭代器将元祖中的元素逐个取出然后添加到列表中)

  3. 斐波那契数列生成

    class Num(object):
        def __init__(self):
            self.a = 0
            self.b = 1
            self.temp = 0
    
    
        def __iter__(self):
            return self
    
    
        def __next__(self):
            if count > self.temp:
                self.a , self.b = self.b , self.a + self.b
                self.temp += 1
                return self.a
            else:
                raise StopIteration
    
    
    
    num = Num()
    count = int(input("请输入要打印的斐波那契数列的个数:"))
    print("斐波那契数列为:", end="")
    for temp in num:
        print(temp, end=" ")
    

2.2.3.3生成器

  1. 生成器是一种特殊的迭代器:可以让一个类似函数的函数在想要的位置停止执行以及什么时候开始继续执行,生成器没有__inter__和__next__

  2. 生成器创建的方式一:
    num = [x * 2 for x in range(10)]
    print(num)
    num_01 = (x * 2 for x in range(10)) # 改为大括号就是生成器
    for num_01 in num_01:
    print(num_01, end=" ")

  3. 生成器创建的方式二(使用next开启生成器):
    def create_num(all_num):
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
    yield a # 如果一个函数中有yield语句,那么这个就不在是函数而是一个生成器的模版
    # 当生成器到yield时,生成器的程序会暂停,将yield后面的内容传递给i,当在次通过next执行生成器时在从yield处再次执行
    a , b = b, a + b
    current_num += 1

    # 如果在调用函数的时候发现这个函数中有yield那么此时不是调用函数而是创建一个生成器对象
    temp = create_num(10)
    # for num in temp:
    #     print(num, end=" ")
    while True:
        try:  # 这里不添加try异常处理当生成器执行完毕后循环也会被强制退出,但是会有一个停止迭代器当信息输出在面板
            i = next(temp)  # 通过next()函数执行生成器
            print(i, end=" ")
        except Exception as ret:
            break
    
  4. 生成器创建的方式二(使用send开启生成器):

    def create_num(all_num):
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
    ret = yield a # 如果一个函数中有yield语句,那么这个就不在是函数而是一个生成器的模版
    # 当生成器到yield时,生成器的程序会暂停,将yield后面的内容传递给i,当在次通过next执行生成器时在从yield处再次执行
    print(ret)
    a , b = b, a + b
    current_num += 1

    如果在调用函数的时候发现这个函数中有yield那么此时不是调用函数而是创建一个生成器对象

    temp = create_num(10)

    for num in temp:

    print(num, end=" ")

    while True:
    try: # 这里不添加try异常处理当生成器执行完毕后循环也会被强制退出,但是会有一个停止迭代器当信息输出在面板
    i = temp.send(None) # 通过send开启生成器,send函数可以传递参数,而next函数不能传递参数,参数的内容会被传递给ret
    # 当send第一次执行时,会执行ret = yield a等号右边的部分,将a传递给i,然后生成器程序暂停,当第二次执行send时,会从ret = yield a等号左边开始执行并将参数传递给ret
    # 如果一个生成器第一次被send函数开启时,参数必须为None,因为没有生成器中没有变量接受传递的参数,否则会报错
    # send()必须要跟一个参数,但在生成器内部可以不写接收变量,也就是说ret = yield a中的ret = 内容可以省略
    print(i, end=" ")
    except Exception as ret:
    break

2.2.3.4使用yield实现多任务

  1. 使用yield可以暂停生成器的进行

  2. 暂用资源对比:协程<线程<进程

  3. 代码实例
    import time

    def task_1():
        while True:
            print("---1---")
            time.sleep(1)
            yield
    
    
    def task_2():
        while True:
            print("---2---")
            time.sleep(1)
            yield
    
    
    def main():
        t1 = task_1()
        t2 = task_2()
        while True:
            t1.send(None)
            t2.send(None)
    
    
    if __name__ == "__main__":
        main()
    

2.2.3.5greenlet,gevent完成多任务

  1. 使用greenlet完成多任务(不经常用)
    from greenlet import greenlet
    import time

    def test1():
        while True:
            print("---A---")
            gr2.switch()  #  切换到gr2协程
            time.sleep(1)
    
    
    def test2():
        while True:
            print("---B---")
            gr1.switch()  #  切换到gr1协程
            time.sleep(1)
    
    
    gr1 = greenlet(test1)  # 创建一个greenlet协程对象
    gr2 = greenlet(test2)
    gr1.switch()  # switch方法开启一个协程
    
  2. 使用gevent完成多任务
    import gevent
    import time
    from gevent import monkey

    # monkey.patch_all()  # 这个方法可以将程序中所有与延时有关的操作转换为gevent下的延时
    
    def f(n):
        for i in range(n):
            print("------", i)
            # gevent.sleep(1)  # 这里必须用gevent的sleep函数
            time.sleep(1)  # 这里普通的延时不会导致协程之间的切换,必须是gevent的延时
    
    
    g1 = gevent.spawn(f, 5)  # 创建一个gevent形式的协程对象,f为指定的要执行的协程,5是要传递的参数,创建时不会被执行
    g2 = gevent.spawn(f, 5)
    g3 = gevent.spawn(f, 5)
    g1.join()  # 等待g1执行完毕,会产生堵塞,如果有延时就自动执行其它协程,自动在各协程之间切换
    # 这里只要有一个join就可以开启所有协程的运行
    
  3. 使用gevent完成多任务升级(常用)
    import gevent
    import time
    from gevent import monkey

    monkey.patch_all()
    
    
    def test_01(i):
        for temp in range(i):
            print("---test01---%d---" % temp)
            time.sleep(1)
    
    
    def test_02(i):
        for temp in range(i):
            print("---test02---%d---" % temp)
    
    
    gevent.joinall([  # 将所有要执行的协程放置到一个joinall方法中
        gevent.spawn(test_01, 4),
        gevent.spawn(test_02, 5)
    ])
    

2.2.3.6图片下载器

  1. 在使用协程时一般不使用yield和greenlet来实现多任务,一般都使用gevent来实现多任务

  2. 代码示例:
    import urllib.request
    import gevent
    from gevent import monkey

    monkey.patch_all()
    
    
    def download(img_name, img_url):
        req = urllib.request.urlopen(img_url)  # 需要下载图片的网址
        img_content = req.read()  # 将图片读到img_content中,就是下载
        with open(img_name, "wb") as f:
            f.write(img_content)
    
    
    def main():
        gevent.joinall([
            gevent.spawn(download, "1.jpg", "http://t8.baidu.com/it/u=2247852322,986532796&fm=79&app=86&size=h300&n=0&g=4n&f=jpeg?sec=1583035193&t=b2dfc63a6a52f689ca46e7f23ef188fe"),
            gevent.spawn(download, "2.jpg", "http://t8.baidu.com/it/u=1484500186,1503043093&fm=79&app=86&size=h300&n=0&g=4n&f=jpeg?sec=1583035193&t=79d80a43ff55ece563e6d1fce28e763b")
        ])
    
    if __name__ == '__main__':
        main()
    

2.2.4进程、线程、协程对比

  1. 进程是资源分配的单位,进程耗费的资源最多,一般不会用多进程实现多任务
  2. 协程在一个线程中进行,所以是并发的,协程是效率最高的
  3. 多线程、多进程是根据计算机cpu的具体情况来确定是并发还是并行

猜你喜欢

转载自blog.csdn.net/IT_SoftEngineer/article/details/104457716
今日推荐