1. 程式人生 > 實用技巧 >python知識點總結

python知識點總結

目錄


1.Python記憶體管理

1.Python如何記憶體管理

  1. 引用計數:當一個Python物件被引用時其引用計數增加1,當其不再被一個變數引用時則計數減1,當引用計數等於0時物件被刪除。弱引用不會增加引用計數。

  2. 垃圾回收:python會檢查引用計數為0的物件,清除其在記憶體佔的空間;迴圈引用物件則用一個迴圈垃圾回收器來回收

  3. 記憶體池機制:在Python中,許多時候申請的記憶體都是小塊的記憶體,這些小塊記憶體在申請後,很快又會被釋放,由於這些記憶體的申請並不是為了建立物件,所以並沒有物件一級的記憶體池機制。這就意味著Python在執行期間會大量地執行malloc(分配記憶體)和free(釋放記憶體)的操作,頻繁地在使用者態和核心態之間進行切換,這將嚴重影響Python的執行效率。為了加速Python的執行效率,Python引入了一個記憶體池機制,用於管理對小塊記憶體的申請和釋放。

    a) Python提供了對記憶體的垃圾收集機制,但是它將不用的記憶體放到記憶體池而不是返回給作業系統。 b) Python中所有小於256個位元組的物件都使用pymalloc實現的分配器,而大的物件則使用系統的 malloc。另外Python物件,如整數,浮點數和List,都有其獨立的私有記憶體池,物件間不共享他們的記憶體池。也就是說如果你分配又釋放了大量的整數,用於快取這些整數的記憶體就不能再分配給浮點數。

2.調優手段

  1. 手動垃圾回收

  2. 調高垃圾回收閾值

  3. 避免迴圈引用

3.記憶體洩露是什麼?如何避免?

  1. 記憶體洩漏指由於疏忽或錯誤造成程式未能釋放已經不再使用的記憶體。記憶體洩漏並非指記憶體在物理上的消失,而是應用程式分配某段記憶體後,由於設計錯誤,導致在釋放該段記憶體之前就失去了對該段記憶體的控制,從而造成了記憶體的浪費,通過Python擴充套件模組gc 來檢視不能回收的物件的詳細資訊。

  2. 防止記憶體洩露:__del__()函式的物件間的迴圈引用是導致記憶體洩露的主凶。不使用一個物件時使用: del object 來刪除一個物件的引用計數就可以有效防止記憶體洩露問題。

  3. 判斷是否記憶體洩露:通過 sys.getrefcount(obj) 來獲取物件的引用計數,並根據返回值是否為0來判斷.

4.哪些操作會導致Python記憶體溢位,怎麼處理?

  1. 記憶體溢位原因:
    1.記憶體中載入的資料量過於龐大,如一次從資料庫取出過多資料; 一般比如資料查詢未做分頁處理
    2.集合類中有對物件的引用,使用完後未清空,使得JVM不能回收
    3.程式碼中存在死迴圈或迴圈產生過多重複的物件實體
    4.使用的第三方軟體中的BUG; 一般引用第三方jar包過多會出現此類問題
    5.啟動引數記憶體值設定的過小 這種可能性很小伺服器引數設定一般會出現這類問題畢竟都是開發
  2. 記憶體溢位的解決方案:
    第一步,修改JVM啟動引數,直接堆記憶體。(-Xms,-Xmx引數一定不要忘記加。)
    第二步,檢查錯誤日誌,檢視“OutOfMemory”錯誤前是否有其 它異常或錯誤。
    第三步,對程式碼進行走查和分析,找出可能發生記憶體溢位的位置。

2.Python的垃圾回收機制

  1. 引用計數

    PyObject是每個物件必有的內容,其中ob_refcnt就是做為引用計數。當一個物件有新的引用時,它的ob_refcnt就會增加,當引用它的物件被刪除,它的ob_refcnt就會減少.引用計數為0時,該物件生命就結束了。

    優點:

    1. 簡單
    2. 實時性

    缺點:

    1. 維護引用計數消耗資源
    2. 迴圈引用
  2. 標記清除機制

    基本思路是先按需分配,等到沒有空閒記憶體的時候從暫存器和程式棧上的引用出發,遍歷以物件為節點、以引用為邊構成的圖,把所有可以訪問到的物件打上標記,然後清掃一遍記憶體空間,把所有沒標記的物件釋放。

  3. 分代技術

​ 分代回收的整體思想是:將系統中的所有記憶體塊根據其存活時間劃分為不同的集合,每個集合就成為一個“代”,垃圾收集頻率隨著“代”的存活時間的增大而減小,存活時間通常利用經過幾次垃圾回收來度量。

​ Python預設定義了三代物件集合,索引數越大,物件存活時間越長。

​ 舉例: 當某些記憶體塊M經過了3次垃圾收集的清洗之後還存活時,我們就將記憶體塊M劃到一個集合A中去,而新分配的記憶體都劃分到集合B中去。當垃圾收集開始工作時,大多數情況都只對集合B進行垃圾回收,而對集合A進行垃圾回收要隔相當長一段時間後才進行,這就使得垃圾收集機制需要處理的記憶體少了,效率自然就提高了。在這個過程中,集合B中的某些記憶體塊由於存活時間長而會被轉移到集合A中,當然,集合A中實際上也存在一些垃圾,這些垃圾的回收會因為這種分代的機制而被延遲。

3.程序,執行緒,協程

程序裡有執行緒,執行緒裡有協程

1.程序(並行)

1.定義:

  • 程序是系統進行資源分配和排程的一個獨立單位(不共享全域性變數),程序需要相應的系統資源:記憶體、時間片、pid,開銷大

  • 多程序適合在CPU密集操作(cpu操作指令比較多,如位多的的浮點運算)

  • 程序是並行

併發和並行的區別?

  • 併發:併發的實質是一個物理CPU(也可以多個物理CPU) 在若干道程式之間多路複用,併發性是對有限物理資源強制行使多使用者共享以提高效率,不會在同一時刻同時執行,存在交替執行的情況。
  • 並行:指兩個或兩個以上事件或活動在同一時刻發生。在多道程式環境下,並行性使多個程式同一時刻可在不同CPU上同時執行

2.組成

  1. 程式:我們編寫的程式用來描述程序要完成哪些功能以及如何完成

  2. 資料集:資料集則是程式在執行過程中所需要使用的資源

  3. 程序控制塊:程序控制塊用來記錄程序的外部特徵,描述程序的執行變化過程,系統可以利用它來控制和管理程序,它是系統感知程序存在的唯一標誌

  • 闡釋:程序與程序之間都佔用的是獨立的記憶體塊,它們彼此之間的資料也是獨立的
  • 優點:同時利用多個CPU,能夠同時進行多個操作
  • 缺點:耗費資源(需要重新開闢記憶體空間)

3.multiprocess模組介紹

1.管理程序模組:

  • Process(用於建立程序模組)
  • Pool(用於建立管理程序池)
1.程序Process
  • Process([group [, target [, name [, args [, kwargs]]]]])

    1. 需要使用關鍵字的方式來指定引數
    2. args指定的為傳給target函式的位置引數,是一個元組形式,必須有逗號
    • 引數介紹

      1. group引數未使用,值始終為None

      2. target表示呼叫物件,即子程序要執行的任務

      3. args表示呼叫物件的引數元組,args=(1,2,'egon',)

      4. kwargs表示呼叫物件的字典,kwargs={'name':'egon','age':18}

      5. name為子程序的名稱

    • 方法介紹:

      1. p.start():啟動程序,並呼叫該子程序中的p.run()
      2. p.run():程序啟動時執行的方法,正是它去呼叫target指定的函式,我們自定義類的類中一定要實現該方法
      3. p.terminate():強制終止程序p,不會進行任何清理操作,如果p建立了子程序,該子程序就成了殭屍程序,使用該方法需要特別小心這種情況。如果p還儲存了一個鎖那麼也將不會被釋放,進而導致死鎖
      4. p.is_alive():如果p仍然執行,返回True
      5. p.join([timeout]):阻塞當前上下文環境的程序,直到呼叫此方法的程序終止或到達指定的timeout(可選引數),需要強調的是,p.join只能join住start開啟的程序,而不能join住run開啟的程序
    • 屬性介紹:

      1. p.daemon:預設值為False,如果設為True,代表p為後臺執行的守護程序。設定為True後,p不能建立自己的新程序,必須在p.start()之前設定,主程序建立守護程序,守護程序會隨著主程序的結束而結束。守護程序內無法再開啟子程序,否則丟擲異常:AssertionError: daemonic processes are not allowed to have children

      2. p.name:程序的名稱

      3. p.pid:程序的pid

      4. p.exitcode:程序在執行時為None、如果為–N,表示被訊號N結束(瞭解即可)

      5. p.authkey:程序的身份驗證鍵,預設是由os.urandom()隨機生成的32字元的字串。這個鍵的用途是為涉及網路連線的底層程序間通訊提供安全性(瞭解即可)

      6. 在Windows作業系統中由於沒有fork(linux作業系統中建立程序的機制),因此用process()直接建立子程序會無限遞迴建立子。所以必須把建立子程序的部分使用if name ==‘main’ 判斷保護起來,import 的時候 ,就不會遞迴運行了

    • 多程序開發中join和deamon的區別
      1. join:當子執行緒呼叫join時,主執行緒會被阻塞,當子執行緒結束後,主執行緒才能繼續執行。

      2. deamon:當子程序被設定為守護程序時,主程序結束,不管子程序是否執行完畢,都會隨著主程序的結束而結束。

    • Process模組建立並開啟子程序的兩種方式:

      1. 通過呼叫process模組的方式來建立程序

        from multiprocessing import Process
        import time
        def start(name):
            time.sleep(1)
            print('hello', name)
        
        if __name__ == '__main__':
            p = Process(target=start, args=('zhangsan',))
            p1 = Process(target=start, args=('lisi',))
            p.start()
            p1.start()
            p.join()#hello zhangsan hello lisi
        
      2. 通過繼承process類,重寫__init__方法和run方法的方式。如果需要傳參,必須寫入到__init__方法裡面,且必須加上super().init();因為父類Process裡面也有__init__方法。

        from multiprocessing import Process
        import time
        class MyProcess(Process):
            def __init__(self,name):
                super().__init__()
                self.name = name
            def run(name):
                time.sleep(1)
                print('hello', name)
        
        if __name__ == '__main__':
            p = MyProcess('zhangsan')
            p1 = MyProcess('lisi',)
            p.start()
            p1.start()
            p.join()
            #hello <MyProcess name='zhangsan' parent=25347 started>
            # hello <MyProcess name='lisi' parent=25347 started>
        
2.程序池Pool
  • 建立程序池:

    1. Pool([numprocess [,initializer [, initargs[maxtasksperchild,]]]]):
    2. 程序池內部維護一個程序序列,當使用時,則去程序池中獲取一個程序,如果程序池序列中沒有可供使用的程序,那麼程式就會等待,直到程序池中有可用程序為止。我們可以用Pool類建立一個程序池, 展開提交的任務給程序池.
    • 引數介紹:

      1. numprocess:要建立的程序數,如果省略,將預設為cpu_count()的值,可os.cpu_count()檢視
      2. initializer:是每個工作程序啟動時要執行的可呼叫物件,預設為None
      3. initargs:是要傳給initializer的引數組
      4. maxtasksperchild:工作程序退出之前可以完成的任務數,完成後用一個新的工作程序來替代原程序,來讓閒置的資源被釋放。maxtasksperchild預設是None,意味著只要Pool存在工作程序就會一直存活。
    • 程序池的方法:

      1. Pool.apply(func[, args[, kwds]]) (同步程序池,阻塞,序列):使用arg和kwds引數呼叫func函式,結果返回前會一直阻塞,由於這個原因,apply_async()更適合併發執行,另外,func函式僅被pool中的一個程序執行。

        from multiprocessing import Pool
        import os,time
        def task(n):
            print('[%s] is running'%os.getpid())
            time.sleep(2)
            print('[%s] is done'%os.getpid())
            return n**2
        
        if __name__ == '__main__':
            p = Pool(4) #建立4個程序
            for i in range(1,3):#開2個任務
                res = p.apply(task,args=(i,))  #同步的,等著一個執行完才執行另一個
                print('本次任務的結束:%s'%res)
            p.close()#禁止往程序池內再新增任務
            p.join() #在等程序池
            print('主')
        
        '''
        [26252] is running
        [26252] is done
        本次任務的結束:1
        [26251] is running
        [26251] is done
        本次任務的結束:4
        主'''
        
      2. Pool.apply_async(func[, args[, kwds[, callback[, error_callback]]]])(非同步程序池,非阻塞,並行) : 是apply()的一個變體,會返回一個結果物件。如果callback被指定,那麼callback可以接收一個引數然後被呼叫,當結果準備好回撥時會呼叫callback,呼叫失敗時,則用error_callback替換callback。 Callbacks應被立即完成,否則處理結果的執行緒會被阻塞。

        from  multiprocessing import Process,Pool
        import time
        
        def Foo(i):
            time.sleep(1)
            return i+100
        
        def Bar(arg):
            print('number::',arg)
        
        if __name__ == "__main__":
            pool = Pool(3)#定義一個程序池,裡面有3個程序
            for i in range(4):
                #callback是回撥函式,就是在執行完Foo方法後會自動執行Bar函式,並且自動把Foo函式的返回值作為引數傳入Bar函式
                pool.apply_async(func=Foo, args=(i,),callback=Bar)
        
            pool.close()#關閉程序池
            #time.sleep(2)     # 達到3程序數後等待2秒再繼續執行
            #pool.terminate()  # 達到3程序數立刻關閉執行緒池
            pool.join()#程序池中程序執行完畢後再關閉,(必須先close在join)
        
        '''
        number:: 100
        number:: 101
        number:: 102
        number:: 103
        '''
        
      3. close() : 阻止更多的任務提交到pool,待任務完成後,工作程序會退出。

      4. terminate() : 不管任務是否完成,立即停止工作程序。在對pool物件程序垃圾回收的時候,會立即呼叫terminate()。

      5. join() : 主程序等待所有子程序執行完畢,在呼叫join()前,必須呼叫close() or terminate()。這樣是因為被終止的程序需要被父程序呼叫wait(join等價與wait),否則程序會成為殭屍程序。

2.程序之間通訊,資源共享

  • Queue,pipe

    • 會阻塞程序,多執行緒安全

    • queue.Queue()  #先進先出
      queue.LifoQueue() #後進先出
      queue.PriorityQueue() #優先順序佇列
      queue.deque() #雙線佇列
      
    • 佇列都是在記憶體中操作,程序退出,佇列清空

    • #佇列
      import time
      from multiprocessing import Process, Queue
      def start(q):
          q.put( 'hello')
      
      if __name__ == '__main__':
          q = Queue()
          p = Process(target=start, args=(q,))
          p.start()
          print(q.get())
          time.sleep(2)
          p.join()
          
      '''
      列印hello,等待2s後主程序才結束
      '''
      
  • Value,Array

    • 用Value或Array儲存在一個共享記憶體地圖裡:如Value(‘d’,0.0),Array(‘i’,range(11)),其中“d”表示一個雙精度的浮點數,“i”表示一個有符號的整數,再把他們作為位置引數傳入args裡。

      from multiprocessing import Process, Value, Array
      
      def f(n, a):
          n.value = 3.1415927
          for i in range(len(a)):
              a[i] = -a[i]
      
      if __name__ == '__main__':
          num = Value('d', 0.0)
          arr = Array('i', range(10))
      
          p = Process(target=f, args=(num, arr))
          p.start()
          p.join()
      
          print(num.value)
          print(arr[:])
      
      # 輸出:
      #3.1415927
      #[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
      
  • Manager

    • 不會堵塞程序, 而且是多程序安全的

    • 通過with Manager() as man:返回的manager提供.list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array型別的支援

      from multiprocessing import Process, Manager
      
      def f(d, l):
          d[1] = '1'
          d['2'] = 2
          d[0.25] = None
          l.reverse()
      
      if __name__ == '__main__':
          with Manager() as manager:
              d = manager.dict()
              l = manager.list(range(10))
      
              p = Process(target=f, args=(d, l))
              p.start()
              p.join()
      
              print(d)
              print(l)
      
      # 輸出結果:
      #{{1: '1', '2': 2, 0.25: None}
      #[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
      

3.程序同步(鎖):

  • 鎖是為了確保資料一致性,比如讀寫鎖,如果在一個程序讀取但還沒有寫入的時候,另外的程序也同時讀取了,並寫入該值,則最後寫入的值是錯誤的,這時候就需要鎖。
  • Lock:當多個程序使用同一份資料資源的時候,就會引發資料安全或順序混亂問題,可以用lock.acquire(),lock.release()將資源鎖起來,雖然使用加鎖的形式實現了順序的執行,但是程式又重新變成串行了,加鎖可以保證多個程序修改同一塊資料時,同一時間只能有一個任務可以進行修改,即序列修改,速度是慢了,但犧牲了速度卻保證了資料的安全性。
  • Queue和Pipe:效率高(多個程序共享一塊記憶體的資料
  • Semaphore: 和 Lock 稍有不同,Semaphore 相當於 N 把鎖,獲取其中一把就可以執行了。 訊號量的總數 N 在構造時傳入,s = Semaphore(N)。 和 Lock 一樣,如果訊號量為0,則程序堵塞,直到訊號大於0
  • Condition(條件物件)
  • Event(事件)
  • Lock(鎖)
  • RLock(可重入鎖)
  • Semaphore(訊號量)

4.孤兒程序,殭屍程序,守護程序

  • 孤兒程序:父程序退出,子程序還在執行,這些子程序將成為孤兒程序。孤兒程序將被 init 程序(程序號為 1)所收養,並由 init 程序對它們完成狀態收集工作。由於孤兒程序會被 init 程序收養,所以孤兒程序不會對系統造成危害

  • 殭屍程序:一個子程序的程序描述符在子程序退出時不會釋放,只有當父程序通過 wait() 或 waitpid() 獲取了子程序資訊後才會釋放。所以如果一個子程序退出,而父程序並沒有呼叫 wait() 或 waitpid(),那麼子程序的程序描述符仍然儲存在系統中,這種程序稱之為殭屍程序。

    • 殭屍程序通過 ps 命令顯示出來的狀態為 Z(zombie)。系統所能使用的程序號是有限的,如果產生大量殭屍程序,將因為沒有可用的程序號而導致系統不能產生新的程序。要消滅系統中大量的殭屍程序,只需要將其父程序殺死,此時殭屍程序就會變成孤兒程序,從而被 init 所收養,這樣 init 就會釋放所有的殭屍程序所佔有的資源,從而結束殭屍程序。
  • 守護程序:是執行在後臺的一種特殊程序。它獨立於控制終端並且週期性地執行某種任務或等待處理某些發生的事件。它不需要使用者輸入就能執行而且提供某種服務,不是對整個系統就是對某個使用者程式提供服務。Linux系統的大多數伺服器就是通過守護程序實現的。常見的守護程序包括系統日誌程序syslogd、 web伺服器httpd、郵件伺服器sendmail和資料庫伺服器mysqld等

    • 守護程序一般在系統啟動時開始執行,除非強行終止,否則直到系統關機都保持執行。守護程序經常以超級使用者(root)許可權執行,因為它們要使用特殊的埠(1-1024)或訪問某些特殊的資源。
    • 一個守護程序的父程序是init程序,因為它真正的父程序在fork出子程序後就先於子程序exit退出了,所以它是一個由init繼承的孤兒程序。守護程序是非互動式程式,沒有控制終端,所以任何輸出,無論是向標準輸出裝置stdout還是標準出錯裝置stderr的輸出都需要特殊處理。
    • 守護程序的名稱通常以d結尾,比如sshd、xinetd、crond等

2.執行緒(併發)

1.定義

  • cpu排程執行的最小單位,也叫執行路徑,不能獨立存在,依賴程序存在,一個程序至少有一個執行緒,叫主執行緒,而多個執行緒共享記憶體(資料共享,共享全域性變數),從而極大地提高了程式的執行效率,開銷小。

  • 同一程序內的執行緒共享該程序的資料

  • 在主程序下開啟多個執行緒,每個執行緒都跟主程序的pid一樣,開啟多個子程序,每個程序有不同的pid

  • 多執行緒適合在IO密性型操作(讀寫資料操作比多的的,比如爬蟲)

  • 執行緒是並行(同一時刻多個任務同時在執行)

2.組成

  • 它被包含在程序之中,是程序中的實際運作單位。一條執行緒指的是程序中一個單一順序的控制流,一個程序中可以併發多個執行緒,每條執行緒並行執行不同的任務

  • 闡釋:執行緒不能夠獨立執行,必須依存在應用程式中,由應用程式提供多個執行緒執行控制。執行緒可以共享(呼叫)程序的資料資源

  • 優點:共享記憶體,IO操作時候,創造併發操作

3.執行緒模組threading

  • 方法介紹
    • t.start() : 啟用執行緒,
    • t.getName() : 獲取執行緒的名稱
    • t.setName() : 設定執行緒的名稱
    • t.name : 獲取或設定執行緒的名稱
    • t.is_alive() : 判斷執行緒是否為啟用狀態
    • t.isAlive() :判斷執行緒是否為啟用狀態
    • t.setDaemon() 設定為後臺執行緒或前臺執行緒(預設:False);通過一個布林值設定執行緒是否為守護執行緒,必須在執行start()方法之後才可以使用。如果是後臺執行緒,主執行緒執行過程中,後臺執行緒也在進行,主執行緒執行完畢後,後臺執行緒不論成功與否,均停止;如果是前臺執行緒,主執行緒執行過程中,前臺執行緒也在進行,主執行緒執行完畢後,等待前臺執行緒也執行完成後,程式停止
    • t.isDaemon() : 判斷是否為守護執行緒
    • t.ident :獲取執行緒的識別符號。執行緒識別符號是一個非零整數,只有在呼叫了start()方法之後該屬性才有效,否則它只返回None。
    • t.join() :逐個執行每個執行緒,執行完畢後繼續往下執行,該方法使得多執行緒變得無意義
    • t.run() :執行緒被cpu排程後自動執行執行緒物件的run方法threading.currentThread(): 返回當前的執行緒變數。
    • threading.enumerate(): 返回一個包含正在執行的執行緒的list。正在執行指執行緒啟動後、結束前,不包括啟動前和終止後的執行緒
    • threading.activeCount(): 返回正在執行的執行緒數量,與len(threading.enumerate())有相同的結果。

4.執行緒同步(鎖),訊號量,事件,佇列,條件變數

  • 同步鎖:Lock(互斥鎖)和RLock(可重入鎖)兩個物件都有 acquire 方法和 release 方法。對於那些需要每次只允許一個執行緒操作的資料,可以將其操作放到 acquire() 和 release() 方法之間。RLock允許在同一執行緒中被多次acquire。而Lock卻不允許這種情況。 如果使用RLock,那麼acquire和release必須成對出現,即呼叫了n次acquire,必須呼叫n次的release才能真正釋放所佔用的瑣

  • 死鎖和遞迴鎖:線上程間共享多個資源的時候,如果兩個執行緒分別佔有一部分資源並且同時等待對方的資源,就會造成死鎖,因為系統判斷這部分資源都正在使用,所有這兩個執行緒在無外力作用下將一直等待下去。解決死鎖可以用遞迴鎖RLock

  • 訊號量(Semaphore):從意義上來講,也可以稱之為一種鎖:訊號量:指同時開幾個執行緒併發。訊號量用來控制執行緒併發數的,BoundedSemaphore或Semaphore管理一個內建的計數 器,每當呼叫acquire()時-1,呼叫release()時+1。計數器不能小於0,當計數器為 0時,Semaphore.acquire()將阻塞執行緒至同步鎖定狀態,直到其他執行緒呼叫Semaphore.release()。(類似於停車位的概念)BoundedSemaphore與Semaphore的唯一區別在於前者將在呼叫release()時檢查計數器的值是否超過了計數器的初始值,如果超過了將丟擲一個異常

  • 事件threading.Event

    • 用於主執行緒控制其他執行緒的執行,一個執行緒設定Event物件,另一個執行緒等待Event物件,事件主要提供了三個方法 set、wait、clear

    • 事件處理的機制:全域性定義了一個“Flag”,如果“Flag”值為 False,那麼當程式執行 event.wait 方法時就會阻塞,如果“Flag”值為True,那麼event.wait 方法時便不再阻塞。

    • clear:將“Flag”設定為False

    • set:將“Flag”設定為True

    • Event.isSet() :判斷標識位是否為Ture。

      import threading
      def do(event):
          print('start')
          event.wait()
          print('execute')
      
      event_obj = threading.Event()
      for i in range(3):
          t = threading.Thread(target=do, args=(event_obj,))
          t.start()
      
      event_obj.clear()
      inp = input('input:')
      if inp == 'true':
          event_obj.set()
      
      '''
      start
      start
      start
      input:true
      execute
      execute
      execute
      '''
      
  • 佇列threding.Queue

    • 因為列表是不安全的資料結構,所以引申了新的模組——佇列,queue 模組中提供了同步的、執行緒安全的佇列類,包括

      queue.Queue()  #先進先出
      queue.LifoQueue() #後進先出
      queue.PriorityQueue() #優先順序佇列
      queue.deque() #雙線佇列
      
    • queue 模組中的常用方法:

    • queue.qsize() 返回佇列的大小

    • queue.empty() 如果佇列為空,返回True,反之False

    • queue.full() 如果佇列滿了,返回True,反之False

    • queue.full 與 maxsize 大小對應

    • queue.get([block[, timeout]])獲取佇列,timeout等待時間

    • queue.get_nowait() 相當queue.get(False)

    • queue.put([block[, timeout]) 寫入佇列,timeout等待時間,可選引數block預設為True,表示獲取值的時候,如果佇列為空,則阻塞,為False時,不阻塞

    • queue.put_nowait(item) 相當Queue.put(item, False)

    • queue.task_done() 在完成一項工作之後,queue.task_done()函式向任務已經完成的佇列傳送一個訊號

    • queue.join() 實際上意味著等到佇列為空,再執行別的操作

  • 條件變數threading.Condition

    • Condition類實現了一個conditon變數。可以像鎖機制那樣用,所以也有acquire ,release方法。conditiaon變數允許一個或多個執行緒等待,直到他們被另一個執行緒通知。Condition內部有一把鎖,預設是Rlock,在呼叫wait()和notify() 之前必須先呼叫acquire()來獲取這個鎖,當wait()和notify()執行完後,需呼叫release()釋放這個鎖,在執行with condition時,會先執行acquire(),with結束了,執行release()。
    • Condition有兩層鎖,最底層鎖在呼叫wait()時會釋放,同時會加一把鎖到等待佇列,等待notify()喚醒釋放鎖
    • wait(timeout=None) : 等待通知,或者等到設定的超時時間。當呼叫這wait()方法時,如果呼叫它的執行緒沒有得到鎖,那麼會丟擲一個RuntimeError 異常。 wati()釋放鎖以後,在被呼叫相同條件的另一個程序用notify() or notify_all() 叫醒之前 會一直阻塞。wait() 還可以指定一個超時時間。
    • 如果有等待的執行緒,notify()方法會喚醒一個在等待conditon變數的執行緒。notify_all() 則會喚醒所有在等待conditon變數的執行緒。 notify()和notify_all()不會釋放鎖,也就是說,執行緒被喚醒後不會立刻返回他們的wait() 呼叫。除非執行緒呼叫notify()和notify_all()之後放棄了鎖的所有權。例子: 生產者-消費者模型
    import threading
    import time
    
    def consumer(cond):
        with cond:
            print("consumer before wait")
            cond.wait()
            print("consumer after wait")
    
    def producer(cond):
        with cond:
            print("producer before notifyAll")
            cond.notifyAll()
            print("producer after notifyAll")
    
    condition = threading.Condition()
    c1 = threading.Thread(name="c1", target=consumer, args=(condition,))
    c2 = threading.Thread(name="c2", target=consumer, args=(condition,))
    p = threading.Thread(name="p", target=producer, args=(condition,))
    
    c1.start()
    time.sleep(2)
    c2.start()
    time.sleep(2)
    p.start()
    
    '''
    consumer before wait
    consumer before wait
    producer before notifyAll
    producer after notifyAll
    consumer after wait
    consumer after wait
    '''
    

5.什麼是執行緒同步和互斥

  • 同步:按預定的先後次序進行執行
  • 互斥:當有若干個執行緒都要使用某一共享資源時,任何時刻最多隻允許一個執行緒去使用,其它要使用該資源的執行緒必須等待,直到佔用資源者釋放該資源。執行緒互斥可以看成是一種特殊的執行緒同步

6.生產者-消費者模型

  • 產生資料的模組,就形象地稱為生產者;而處理資料的模組,就稱為消費者。在生產者與消費者之間在加個緩衝區,形象的稱之為倉庫,生產者負責往倉庫了進商 品,而消費者負責從倉庫裡拿商品,這就構成了生產者消費者模型。

  • 生產者消費者模型的優點:

      1. 解耦 假設生產者和消費者分別是兩個類。如果讓生產者直接呼叫消費者的某個方法,那麼生產者對於消費者就會產生依賴(也就是耦合)。將來如果消費者的程式碼發生變化, 可能會影響到生產者。而如果兩者都依賴於某個緩衝區,兩者之間不直接依賴,耦合也就相應降低了。
      2. 支援併發:由於生產者與消費者是兩個獨立的併發體,他們之間是用緩衝區作為橋樑連線,生產者只需要往緩衝區裡丟資料,就可以繼續生產下一個資料,而消費者只需要從緩衝區了拿資料即可,這樣就不會因為彼此的處理速度而發生阻塞
      3. 支援忙閒不均:當資料製造快的時候,消費者來不及處理,未處理的資料可以暫時存在緩衝區中。 等生產者的製造速度慢下來,消費者再慢慢處理掉。
      import time,random
      import queue,threading
      q = queue.Queue()
      
      def Producer(name):
          count =1
          while True:
              time.sleep(random.randrange(3))
              if q.qsize()<3:         # 只要盤子裡小於3個包子,廚師就開始做包子
                  q.put(count)
                  print("Producer %s has produced %s baozi.." %(name,count))
                  count += 1
      
      def Consumer(name):
          count =1
          while True:
              time.sleep(random.randrange(4))
              if not q.empty():       # 只要盤子裡有包子,顧客就要吃。
                  data = q.get()
                  print(data)
                  print('Consumer %s has eat %s baozi...' % (name,data))
              else:           # 盤子裡沒有包子
                  print("---no baozi anymore----")
              count+=1
      
      p1 = threading.Thread(target=Producer,args=('A',))
      c1 = threading.Thread(target=Consumer,args=('B',))
      c2 = threading.Thread(target=Consumer,args=('C',))
      p1.start()
      c1.start()
      c2.start()
      

3.協程:

1.定義

  • 又稱微執行緒,執行緒是系統級別的它們由作業系統排程,而協程則是程式級別的由程式根據需要自己排程。在一個執行緒中會有很多函式,我們把這些函式稱為子程式,在子程式執行過程中可以中斷去執行別的子程式,而別的子程式也可以中斷回來繼續執行之前的子程式,這個過程就稱為協程。也就是說在同一執行緒內一段程式碼在執行過程中會中斷然後跳轉執行別的程式碼,接著在之前中斷的地方繼續開始執行,類似與yield操作。協程是一中多工實現方式,它不需要多個程序或執行緒就可以實現多工。

  • 優點:①協程極高的執行效率。因為子程式切換不是執行緒切換,而是由程式自身控制,因此,沒有執行緒切換的開銷,和多執行緒比,執行緒數量越多,協程的效能優勢就越明顯②不需要多執行緒的鎖機制,因為只有一個執行緒,也不存在同時寫變數衝突,在協程中控制共享資源不加鎖,只需要判斷狀態就好了,所以執行效率比多執行緒高很多③高併發+高擴充套件性+低成本:一個CPU支援上萬的協程都不是問題。所以很適合用於高併發處理

  • 缺點:①無法利用多核資源:協程的本質是個單執行緒,它不能同時將 單個CPU 的多個核用上,協程需要和程序配合才能執行在多CPU上.當然我們日常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用②進行阻塞(Blocking)操作(如IO時)會阻塞掉整個程式

2.實現協程的方法

  • 通過yield實現協程
  • 通過from greenlet import greenlet實現協程:g1 = greenlet(A) #建立協程g1,g1.switch() #跳轉至協程g1
  • 通過import gevent實現協程:gevent會主動識別程式內部的IO操作,當子程式遇到IO後,切換到別的子程式。如果所有的子程式都進入IO,則阻塞.
  • g1 = gevent.spawn(A) # 建立一個協程,gevent.sleep(1) #用來模擬一個耗時操作,注意不是time模組中的sleep,#每當碰到耗時操作,會自動跳轉至其他協程,g1.join() #等待協程執行結束.

4.死鎖

1.什麼是死鎖?

  • 所謂死鎖,是指多個程序在執行過程中因爭奪資源而造成的一種僵局,當程序處於這種僵持狀態時,若無外力作用,它們都將無法再向前推進。 因此我們舉個例子來描述,如果此時有一個執行緒A,按照先鎖a再獲得鎖b的的順序獲得鎖,而在此同時又有另外一個執行緒B,按照先鎖b再鎖a的順序獲得鎖。

2.產生死鎖的原因?

a. 競爭資源

  • 系統中的資源可以分為兩類:
    • 可剝奪資源,是指某程序在獲得這類資源後,該資源可以再被其他程序或系統剝奪,CPU和主存均屬於可剝奪性資源;
    • 不可剝奪資源,當系統把這類資源分配給某程序後,再不能強行收回,只能在程序用完後自行釋放,如磁帶機、印表機等。
  • 產生死鎖中的競爭資源之一指的是競爭不可剝奪資源(例如:系統中只有一臺印表機,可供程序P1使用,假定P1已佔用了印表機,若P2繼續要求印表機列印將阻塞)
  • 產生死鎖中的競爭資源另外一種指的是競爭臨時資源(臨時資源包括硬體中斷、訊號、訊息、緩衝區內的訊息等),通常訊息通訊順序進行不當,則會產生死鎖

b. 程序間推進順序非法

  • 若P1保持了資源R1,P2保持了資源R2,系統處於不安全狀態,因為這兩個程序再向前推進,便可能發生死鎖
  • 例如,當P1執行到P1:Request(R2)時,將因R2已被P2佔用而阻塞;當P2執行到P2:Request(R1)時,也將因R1已被P1佔用而阻塞,於是發生程序死鎖

3.死鎖產生的4個必要條件?

  • 互斥條件:程序要求對所分配的資源進行排它性控制,即在一段時間內某資源僅為一程序所佔用。
  • 請求和保持條件:當程序因請求資源而阻塞時,對已獲得的資源保持不放。
  • 不剝奪條件:程序已獲得的資源在未使用完之前,不能剝奪,只能在使用完時由自己釋放。
  • 環路等待條件:在發生死鎖時,必然存在一個程序--資源的環形鏈。

4.解決死鎖的基本方法

預防死鎖:

  • 資源一次性分配:一次性分配所有資源,這樣就不會再有請求了:(破壞請求條件)
  • 只要有一個資源得不到分配,也不給這個程序分配其他的資源:(破壞請保持條件)
  • 可剝奪資源:即當某程序獲得了部分資源,但得不到其它資源,則釋放已佔有的資源(破壞不可剝奪條件)
  • 資源有序分配法:系統給每類資源賦予一個編號,每一個程序按編號遞增的順序請求資源,釋放則相反(破壞環路等待條件)
  1. 以確定的順序獲得鎖

    開發者可以嘗試按照鎖物件的hashCode值大小的順序,分別獲得兩個鎖,這樣鎖總是會以特定的順序獲得鎖,那麼死鎖也不會發生。問題變得更加複雜一些,如果此時有多個執行緒,都在競爭不同的鎖,簡單按照鎖物件的hashCode進行排序(單純按照hashCode順序排序會出現“環路等待”),可能就無法滿足要求了,這個時候開發者可以使用銀行家演算法,所有的鎖都按照特定的順序獲取,同樣可以防止死鎖的發生

  2. 超時放棄

    當使用synchronized關鍵詞提供的內建鎖時,只要執行緒沒有獲得鎖,那麼就會永遠等待下去,然而Lock介面提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法,該方法可以按照固定時長等待鎖,因此執行緒可以在獲取鎖超時以後,主動釋放之前已經獲得的所有的鎖。通過這種方式,也可以很有效地避免死鎖。

避免死鎖:

  • 預防死鎖的幾種策略,會嚴重地損害系統性能。因此在避免死鎖時,要施加較弱的限制,從而獲得 較滿意的系統性能。由於在避免死鎖的策略中,允許程序動態地申請資源。因而,系統在進行資源分配之前預先計算資源分配的安全性。若此次分配不會導致系統進入不安全的狀態,則將資源分配給程序;否則,程序等待。其中最具有代表性的避免死鎖演算法是銀行家演算法。
  • 銀行家演算法:首先需要定義狀態和安全狀態的概念。系統的狀態是當前給程序分配的資源情況。因此,狀態包含兩個向量Resource(系統中每種資源的總量)和Available(未分配給程序的每種資源的總量)及兩個矩陣Claim(表示程序對資源的需求)和Allocation(表示當前分配給程序的資源)。安全狀態是指至少有一個資源分配序列不會導致死鎖。當程序請求一組資源時,假設同意該請求,從而改變了系統的狀態,然後確定其結果是否還處於安全狀態。如果是,同意這個請求;如果不是,阻塞該程序知道同意該請求後系統狀態仍然是安全的。

檢測死鎖

  • 首先為每個程序和每個資源指定一個唯一的號碼;
  • 然後建立資源分配表和程序等待表。

解除死鎖:

當發現有程序死鎖後,便應立即把它從死鎖狀態中解脫出來,常採用的方法有:

  • 剝奪資源:從其它程序剝奪足夠數量的資源給死鎖程序,以解除死鎖狀態;
  • 撤消程序:可以直接撤消死鎖程序或撤消代價最小的程序,直至有足夠的資源可用,死鎖狀態消除為止;所謂代價是指優先順序、執行代價、程序的重要性和價值等。
#氣泡排序
def bubbleSort(arr):
    n = len(arr)
 
    # 遍歷所有陣列元素
    for i in range(n):
 
        # Last i elements are already in place
        for j in range(0, n-i-1):
 
            if arr[j] > arr[j+1] :
                arr[j], arr[j+1] = arr[j+1], arr[j]
 
arr = [64, 34, 25, 12, 22, 11, 90]
 
bubbleSort(arr)
 
print ("排序後的陣列:")
for i in range(len(arr)):
    print ("%d" %arr[i]),