python學習筆記之線程、進程和協程(第八天)

     參考文檔: html

          金角大王博客:http://www.cnblogs.com/alex3714/articles/5230609.htmlpython

          銀角大王博客:http://www.cnblogs.com/wupeiqi/articles/5040827.html多線程

 

線程是操做系統可以進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運做單位。一條線程指的是進程中一個單一順序的控制流,一個進程中能夠併發多個線程,每條線程並行執行不一樣的任務併發

Threading用於提供線程相關的操做,線程是應用程序中工做的最小單元。app

更多方法:ide

start            線程準備就緒,等待CPU調度ui

setName      爲線程設置名稱spa

getName      獲取線程名稱操作系統

setDaemon      設置爲後臺線程或前臺線程(默認)線程

               若是是後臺線程,主線程執行過程當中,後臺線程也在進行,主線程執行完畢後,後臺線程不論成功與否,均中止

               若是是前臺線程,主線程執行過程當中,前臺線程也在進行,主線程執行完畢後,等待前臺線程也執行完成後,程序停

join           逐個執行每一個線程,執行完畢後繼續往下執行,該方法使得多線程變得無心義

run                     線程被cpu調度後自動執行線程對象的run方法

1、線程有2種調用方式:

      一、直接調用

      二、繼承式調用

具體實例以下:

直接調用

import threading,time import os,sys def sayhi(num): print("running on number:%s" % num) time.sleep(3) if __name__ == '__main__': #t1 = threading.Thread(target=sayhi,name='abcd',args=(1,),daemon=True)
    #t2 = threading.Thread(target=sayhi,name='1234',args=(2,),daemon=True)
    ''' t1 = threading.Thread(target=sayhi,name='abcd',args=(1,)) t2 = threading.Thread(target=sayhi,name='1234',args=(2,)) t1.start() t2.start() print(t1.getName()) print(t2.getName())
直接調用

繼承式調用

import threading,time import os,sys class MyThread(threading.Thread): def __init__(self,num): threading.Thread.__init__(self) self.num = num def run(self): print("running on number:%s" % self.num) time.sleep(3) if __name__ == '__main__': t1 = MyThread(1) t2 = MyThread(2) t1.start() t2.start() print(t1.getName()) print(t2.getName())
繼承式調用

 

2、線程之join和 setDaemon

import threading,time def run(n): print('[%s]------running-----\n' % n) time.sleep(5) print('----done----') def main(): for i in range(5): t = threading.Thread(target=run,args=(i,)) t.start() #t.join() #阻塞變串行
        #t.join(2) #設置等待超時時間,和time.sleep等待時間,誰先到誰繼續
        #t.join(timeout=9) #超時時間大於sleep時間,沒有啥意義
        print('start threading %s' % i,t.getName()) #main()
 m = threading.Thread(target=main,args=()) m.setDaemon(True) #將main線程設置爲Daemon線程,它作爲程序主線程的守護線程,當主線程退出時,m線程也會退出,由m啓動的其它子線程會同時退出,不論是否執行完任務
m.start() m.join() #變爲不阻塞,但run中sleep後的輸出不會執行
print("----------main thread done------------")

 

3、線程之鎖:

Python GIL(Global Interpreter Lock)  

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

上面的核心意思就是,不管你啓多少個線程,你有多少個cpu, Python在執行的時候會淡定的在同一時刻只容許一個線程運行,擦。。。,那這還叫什麼多線程呀?莫如此早的下結結論,聽我現場講。

 

首先須要明確的一點是GIL並非Python的特性,它是在實現Python解析器(CPython)時所引入的一個概念。就比如C++是一套語言(語法)標準,可是能夠用不一樣的編譯器來編譯成可執行代碼。有名的編譯器例如GCCINTEL C++Visual C++等。Python也同樣,一樣一段代碼能夠經過CPythonPyPyPsyco等不一樣的Python執行環境來執行。像其中的JPython就沒有GIL。然而由於CPython是大部分環境下默認的Python執行環境。因此在不少人的概念裏CPython就是Python,也就想固然的把GIL歸結爲Python語言的缺陷。因此這裏要先明確一點:GIL並非Python的特性,Python徹底能夠不依賴於GIL

這篇文章透徹的剖析了GILpython多線程的影響,強烈推薦看一下:http://www.dabeaz.com/python/UnderstandingGIL.pdf 

 

線程鎖(互斥鎖Mutex)

因爲線程之間是進行隨機調度,而且每一個線程可能只執行n條執行以後,當多個線程同時修改同一條數據時可能會出現髒數據,因此,出現了線程鎖 - 同一時刻容許一個線程執行操做。

一個進程下能夠啓動多個線程,多個線程共享父進程的內存空間,也就意味着每一個線程能夠訪問同一份數據,此時,若是2個線程同時要修改同一份數據,會出現什麼情況?

未加鎖狀態

import time import threading   def addNum():     global num #在每一個線程中都獲取這個全局變量
    print('--get num:',num )     time.sleep(1)     num  -=1 #對此公共變量進行-1操做
  num = 100  #設定一個共享變量
thread_list = [] for i in range(100):     t = threading.Thread(target=addNum)     t.start()     thread_list.append(t)   for t in thread_list: #等待全部線程執行完畢
    t.join()     print('final num:', num )
未加鎖

正常來說,這個num結果應該是0, 但在python 2.7上多運行幾回,會發現,最後打印出來的num結果不老是0,爲何每次運行的結果不同呢? 哈,很簡單,假設你有A,B兩個線程,此時都 要對num 進行減1操做, 因爲2個線程是併發同時運行的,因此2個線程頗有可能同時拿走了num=100這個初始變量交給cpu去運算,當A線程去處完的結果是99,但此時B線程運算完的結果也是99,兩個線程同時CPU運算的結果再賦值給num變量後,結果就都是99。那怎麼辦呢? 很簡單,每一個線程在要修改公共數據時,爲了不本身在還沒改完的時候別人也來修改此數據,能夠給這個數據加一把鎖, 這樣其它線程想修改此數據時就必須等待你修改完畢並把鎖釋放掉後才能再訪問此數據。 

*注:不要在3.x上運行,不知爲何,3.x上的結果老是正確的,多是自動加了鎖

加鎖狀態

import time import threading def addNum(i): global num #在每一個線程中都獲取這個全局變量
    print("---get num:",i,num) time.sleep(1) lock.acquire() #修改數據前加鎖
    num -= 1 #對此公共變量進行-1操做
    lock.release()  # 修改後釋放
 num = 100 thread_list = [] lock = threading.Lock()  #生成全局鎖
for i in range(100): t = threading.Thread(target=addNum,args=(i,)) t.start() thread_list.append(t) for t in thread_list: t.join() print('final num:',num)
加鎖

GIL VS Lock 

機智的同窗可能會問到這個問題,就是既然你以前說過了,Python已經有一個GIL來保證同一時間只能有一個線程來執行了,爲何這裏還須要lock? 注意啦,這裏的lock是用戶級的lock,跟那個GIL不要緊

那你又問了, 既然用戶程序已經本身有鎖了,那爲何C python還須要GIL呢?加入GIL主要的緣由是爲了下降程序的開發的複雜度,好比如今的你寫python不須要關心內存回收的問題,由於Python解釋器幫你自動按期進行內存回收,你能夠理解爲python解釋器裏有一個獨立的線程,每過一段時間它起wake up作一次全局輪詢看看哪些內存數據是能夠被清空的,此時你本身的程序裏的線程和 py解釋器本身的線程是併發運行的,假設你的線程刪除了一個變量,py解釋器的垃圾回收線程在清空這個變量的過程當中的clearing時刻,可能一個其它線程正好又從新給這個還沒來及得清空的內存空間賦值了,結果就有可能新賦值的數據被刪除了,爲了解決相似的問題,python解釋器簡單粗暴的加了鎖,即當一個線程運行時,其它人都不能動,這樣就解決了上述的問題,  這能夠說是Python早期版本的遺留問題。

 

 

RLock(遞歸鎖)

說白了就是在一個大鎖中還要再包含子鎖

#!/Library/Frameworks/Python.framework/Versions/3.6/bin/python3 # -*- coding: utf-8 -*-

import threading, time def run1(): print("grab the first part data") lock.acquire() global num num += 1 lock.release() return num def run2(): print("grab the second part data") lock.acquire() global num2 num2 += 1 lock.release() return num2 def run3(): lock.acquire() res = run1() print('--------between run1 and run2-----')  #爲防止運行run1和run2時被打斷,讓兩者做爲一個總體,因此加把鎖
    res2 = run2() lock.release() print(res, res2) if __name__ == '__main__': num, num2 = 0, 0 lock = threading.RLock() for i in range(10): t = threading.Thread(target=run3) t.start() while threading.active_count() != 1: print(threading.active_count()) else: print('----all threads done---') print(num, num2)
遞歸鎖

 

Semaphore(信號量)

互斥鎖 同時只容許一個線程更改數據,而Semaphore是同時容許必定數量的線程更改數據 ,好比廁全部3個坑,那最多隻容許3我的上廁所,後面的人只能等裏面有人出來了才能再進去。

import threading,time def run(n): semaphore.acquire() time.sleep(1) print("run the thread: %s\n" %n) semaphore.release() if __name__ == '__main__': num= 0 semaphore = threading.BoundedSemaphore(5) #最多容許5個線程同時運行
    for i in range(20): t = threading.Thread(target=run,args=(i,)) t.start() while threading.active_count() != 1: pass #print threading.active_count()
else: print('----all threads done---') print(num)
semaphore
相關文章
相關標籤/搜索