第六階段:多任務

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

    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的具體情況來確定是併發還是並行
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章