1. 程式人生 > 實用技巧 >18-程序&協程

18-程序&協程

程序

  • 概念

    • 程序是程式的一次執行過程, 正在進行的一個過程或者說一個任務,而負責執行任務的則是CPU.
  • 程序的生命週期

    • 當作業系統要完成某個任務時,它會建立一個程序。當程序完成任務之後,系統就會撤銷這個程序,收回它所佔用的資源。從建立到撤銷的時間段就是程序的生命週期
  • 並行與併發

    • 並行
      • 多個任務同時執行,只有具備多個cpu才能實現並行,含有幾個cpu,也就意味著在同一時刻可以執行幾個任務。
    • 併發
      • 是偽並行,即看起來是同時執行的,實際上是單個CPU在多道程式之間來回的進行切換。
  • 同步與非同步

    • 同步
      • 指一個程序在執行某個請求的時候,若該請求需要一段時間才能返回資訊,那麼這個程序將會一直等待下去,直到收到返回資訊才繼續執行下去。
    • 非同步
      • 指程序不需要一直等下去,而是繼續執行下面的操作,不管其他程序的狀態。當有訊息返回時系統會通知進行處理,這樣可以提高執行的效率。
  • 建立程序

    • import multiprocessing
      
      # 方法1
      import os
      
      def process1(*args):
          print('子程序:', args)
      
      def create_process1():
          p = multiprocessing.Process(target=process1, args=('故宮', '長城'))
          p.start()
          # p.join()
      
      # 方法2
      class MyProcess(multiprocessing.Process):
          def __init__(self):
              super().__init__()
          def run(self):
              print('子程序:', multiprocessing.current_process().name)
              print('程序id:', self.pid)
      
      def create_process2():
          p = MyProcess()
          p.start()
          print('p程序', p.pid)
          print('主程序', os.getpid())  # 程序id
      
      if __name__ == '__main__':
          # create_process1()
          create_process2()
      
  • 程序鎖

    • import multiprocessing
      import time
      
      # multiprocessing.Lock,將鎖作為變數傳入
      lock = multiprocessing.Lock()
      
      def f(i, lock):
          with lock:
              print(f'第{i}個程序加鎖')
              time.sleep(3)
              print(f'第{i}個程序解鎖')
      
      if __name__ == '__main__':
          # 程序鎖:瞭解
          for i in range(5):
              p = multiprocessing.Process(target=f, args=(i, lock))  # 不同的程序間記憶體獨立
              p.start()
      
  • 訊號量

    • import multiprocessing
      import time
      
      # 控制程序的最大併發數
      # 將訊號量作為變數傳入,因為程序之間記憶體是相互獨立的
      def f2(i, sem):
          with sem:
              print(f'子程序{i}開始執行')
              time.sleep(3)
              print(f'子程序{i}結束')
      
      if __name__ == '__main__':
          # 訊號量:控制最大的程序併發數
          sem = multiprocessing.Semaphore(3)
          for i in range(20):
              multiprocessing.Process(target=f2, args=(i, sem)).start()
      
  • 非同步變同步

    • .join()

協程

  • 概念

    • # 首先我們得知道協程是啥?協程其實可以認為是比執行緒更小的執行單元。為啥說他是一個執行單元,因為他自帶CPU上下文。這樣只要在合適的時機,我們可以把一個協程切換到另一個協程,只要這個過程中儲存或恢復CPU上下文那麼程式還是可以執行的。
      
      # 通俗的理解:在一個執行緒中的某個函式,可以在任何地方儲存當前函式的一些臨時變數等資訊,然後切換到另外一個函式中執行,注意不是通過呼叫函式的方式做到的,並且切換的次數以及什麼時候再切換到原來的函式都由開發者自己確定。
      
    • 協程,又稱微執行緒,纖程。英文名Coroutine。

  • 協程與執行緒的區別

    • 1.最大的優勢就是協程極高的執行效率。

      • # 因為子程式切換不是執行緒切換,而是由程式自身控制,因此,沒有執行緒切換的開銷,和多執行緒比,執行緒數量越多,協程的效能優勢就越明顯。
        
    • 2.第二大優勢就是不需要多執行緒的鎖機制

      • # 因為只有一個執行緒,也不存在同時寫變數衝突,在協程中控制共享資源不加鎖,只需要判斷狀態就好了,所以執行效率比多執行緒高很多。
        
  • 利用多核CPU

    • 多程序+協程,既充分利用多核,又充分發揮協程的高效率,可獲得極高的效能。
  • 缺點

    • 它不能同時將CPU的多個核用上,只能使用一個核
  • 建立協程

    • greenlet + switch

      • # greenlet + switch
        # 通過time.sleep()自己控制
        from greenlet import greenlet
        import time
        
        def fn1():
            print('協程1')
            time.sleep(3)
            g2.switch()  # 切換到g2協程
            print('英國是個小地方')
            time.sleep(3)
            g2.switch()
        
        def fn2():
            print('協程2')
            time.sleep(3)
            g1.switch()
            print('李嘉誠買下了半個英國')
            time.sleep(3)
        
        if __name__ == '__main__':
            g1 = greenlet(fn1)
            g2 = greenlet(fn2)
            g1.switch()  # 切換到g1協程
        '''
        協程1
        協程2
        英國是個小地方
        李嘉誠買下了半個英國
        '''
        
    • gevent + sleep

      • import gevent
        
        def fn1():
            gevent.sleep(1)
            print('協程1')
        
            gevent.sleep(4)
            print('窗前明月光')
        
        def fn2():
            gevent.sleep(2)
            print('協程2')
        
            gevent.sleep(2)
            print('疑是地上霜')
        
        if __name__ == '__main__':
            g1 = gevent.spawn(fn1)  # 傳參,g1 = gevent.spawn(fn1, 1)
            g2 = gevent.spawn(fn2)
        
            gevent.joinall([g1, g2])
            # g1.join()
        '''
        協程1
        協程2
        疑是地上霜
        窗前明月光
        '''
        
    • gevent + monkey

      • import gevent
        import requests
        import time
        
        def fn(url):
            print('協程:', url)
            response = requests.get(url)
            print(url, len(response.text))
        
        if __name__ == '__main__':
            url_list = [
                'http://www.baidu.com',
                'http://www.qq.com',
                'http://www.ifeng.com',
            ]
        
            # 建立協程
            g_list = []
            for url in url_list:
                g = gevent.spawn(fn, url)
                # g.join()
                g_list.append(g)
        
            # 執行所有的協程
            gevent.joinall(g_list)
        '''
        發生阻塞,並沒有達到多協程的效果(多個任務來回切換)。
        協程: http://www.baidu.com
        http://www.baidu.com 2381
        協程: http://www.qq.com
        http://www.qq.com 98603
        協程: http://www.ifeng.com
        http://www.ifeng.com 223424
        '''
        
      • import gevent
        from gevent import monkey  # 匯入猴子補丁
        monkey.patch_all()  # 自動切換協程
        # 匯入包的時候,以上三句一定放在最上面
        import requests
        import time
        
        def fn(url):
            print('協程:', url)
            response = requests.get(url)
            print(url, len(response.text))
        
        if __name__ == '__main__':
            url_list = [
                'http://www.baidu.com',
                'http://www.qq.com',
                'http://www.ifeng.com',
            ]
        
            # 建立協程
            g_list = []
            for url in url_list:
                g = gevent.spawn(fn, url)
                # g.join()
                g_list.append(g)
        
            # 執行所有的協程
            gevent.joinall(g_list)
        '''
        猴子補丁:在執行時修改類或模組,而不改動原始碼。
        協程: http://www.baidu.com
        協程: http://www.qq.com
        協程: http://www.ifeng.com
        http://www.baidu.com 2381
        http://www.qq.com 98620
        http://www.ifeng.com 223428
        '''
        

高階函式

  • 向函式中傳入函式

  • sorted()

  • reversed()

  • map()

    • 對映

    • 運算後是map型別,要強轉

      • # map():可以對某列表中的每個元素進行統一的處理,得到新的列表
        list1 = [1, 2, 3, 4]
        # list2 = map(lambda x:x**2, list1)
        # list2 = [i**2 for i in list1]
        list2 = map(str, list1)  # ['1', '2', '3', '4']
        print(list2)  # <map object at 0x000001C641FA2308>
        print(list(list2))  # ['1', '2', '3', '4']
        
        list3 = [1, 2, 3, 4]
        list4 = [1, 2, 3, 4]
        list5 = map(lambda x, y: x+y, list3, list4)
        print(list(list5))  # [2, 4, 6, 8]
        
    • ​ reduce()

      • 累計運算

        • # reduce():累計運算
          from functools import reduce
          n = reduce(lambda x, y: x+y, range(1, 101))
          print(n)  #5050
          
    • filter()

      • 過濾

      • 運算後是filter型別,要強轉

        • # filter():過濾
          list6 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
          list7 = filter(lambda x: x % 3 == 0, list6)
          print(list7)  # <filter object at 0x000001A9C6F61908>
          print(list(list7))  # [3, 6, 9]
          

json

  • import json
    
    # json字串 => json物件 :json解析/json反序列化
    json_str = '{"name": "鹿晗", "age": 30}'
    json_obj = json.loads(json_str)
    print(json_obj)  # {'name': '鹿晗', 'age': 30}
    print(type(json_obj))  # <class 'dict'>
    
    # json物件 => json字串  :json序列化(使用更多)
    json_obj = {"name": "鹿晗", "age": 30}
    json_str = json.dumps(json_obj)
    print(json_str)  # '{"name": "\u9e7f\u6657", "age": 30}'
    print(type(json_str))  # <class 'str'>
    
  • json是一種資料格式

  • json的兩種存在形式

    • json字串
    • json物件
    • json解析/json反序列化
      • json字串 => json物件
      • json.loads()
    • json序列化
      • json物件 => json字串
      • json.dumps()

pickle模組

  • 作用

    • 1.將Python物件(列表,字典,元組)存入檔案中,做一個持久化儲存
    • 2.將存入檔案的內容取出
  • 存入

    • dump()
  • 取出

    • load()
  • import pickle
    
    # 存入檔案
    stars = ['鹿晗', '肖戰', '蔡徐坤', '王一博', '吳亦凡']
    fp = open('stars.txt', 'wb')
    pickle.dump(stars, fp)
    fp.close()
    
    # 從檔案中取出
    fp2 = open('stars.txt', 'rb')
    res = pickle.load(fp2)
    print(res)  # ['鹿晗', '肖戰', '蔡徐坤', '王一博', '吳亦凡']
    print(type(res))  # <class 'list'>