Python Threading

大一下的時候學習了Python,但其實知識淺嘗輒止,只能算會個常用語法和結構,之前一段時候裏寫代碼足夠了,但是到了現在需要優化和統籌代碼的時候,我的Python基礎呈現了不足爲繼的situation。所以從現在開始要補起我的Python。今天解決多線程的問題

概念

引用:https://www.cnblogs.com/mhq-martin/p/9035640.html

進程(Process)

是Windows系統中的一個基本概念,它包含着一個運行程序所需要的資源。一個正在運行的應用程序在操作系統中被視爲一個進程,進程可以包括一個或多個線程。線程是操作系統分配處理器時間的基本單元,在進程中可以有多個線程同時執行代碼。進程之間是相對獨立的,一個進程無法訪問另一個進程的數據(除非利用分佈式計算方式),一個進程運行的失敗也不會影響其他進程的運行,Windows系統就是利用進程把工作劃分爲多個獨立的區域的。進程可以理解爲一個程序的基本邊界。是應用程序的一個運行例程,是應用程序的一次動態執行過程。

線程(Thread)

是進程中的基本執行單元,是操作系統分配CPU時間的基本單位,一個進程可以包含若干個線程,在進程入口執行的第一個線程被視爲這個進程的主線程。在.NET應用程序中,都是以Main()方法作爲入口的,當調用此方法時系統就會自動創建一個主線程。線程主要是由CPU寄存器、調用棧和線程本地存儲器(Thread Local Storage,TLS)組成的。CPU寄存器主要記錄當前所執行線程的狀態,調用棧主要用於維護線程所調用到的內存與數據,TLS主要用於存放線程的狀態信息。
棧區:存放函數的參數值、局部變量等,由編譯器自動分配和釋放,通常在函數執行完後就釋放了
堆區:就是通過new、malloc、realloc分配的內存塊,編譯器不會負責它們的釋放工作,需要用程序區釋放

區別

進程和線程的主要差別在於它們是不同的操作系統資源管理方式。進程有獨立的地址空間,一個進程崩潰後,在保護模式下不會對其它進程產生影響,而線程只是一個進程中的不同執行路徑。線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個進程死掉,所以多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行並且又要共享某些變量的併發操作,只能用線程,不能用進程。

  1. 簡而言之,一個程序至少有一個進程,一個進程至少有一個線程.
  2. 線程的劃分尺度小於進程,使得多線程程序的併發性高。
  3. 另外,進程在執行過程中擁有獨立的內存單元,而多個線程共享內存,從而極大地提高了程序的運行效率。
  4. 線程在執行過程中與進程還是有區別的。每個獨立的線程有一個程序運行的入口、順序執行序列和程序的出口。但是線程不能夠獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。
  5. 從邏輯角度來看,多線程的意義在於一個應用程序中,有多個執行部分可以同時執行。但操作系統並沒有將多個線程看做多個獨立的應用,來實現進程的調度和管理以及資源分配。這就是進程和線程的重要區別。

異步(Sync)和同步(Async)

所謂同步,就是發出一個功能調用時,在沒有得到結果之前,該調用就不返回或繼續執行後續操作。
簡單來說,同步就是必須一件一件事做,等前一件做完了才能做下一件事。
例如:B/S模式中的表單提交,具體過程是:客戶端提交請求->等待服務器處理->處理完畢返回,在這個過程中客戶端(瀏覽器)不能做其他事。

異步與同步相對,當一個異步過程調用發出後,調用者在沒有得到結果之前,就可以繼續執行後續操作。當這個調用完成後,一般通過狀態、通知和回調來通知調用者。對於異步調用,調用的返回並不受調用者控制。
對於通知調用者的三種方式,具體如下:

狀態
即監聽被調用者的狀態(輪詢),調用者需要每隔一定時間檢查一次,效率會很低。
通知
當被調用者執行完成後,發出通知告知調用者,無需消耗太多性能。
回調
與通知類似,當被調用者執行完成後,會調用調用者提供的回調函數。

例如:B/S模式中的ajax請求,具體過程是:客戶端發出ajax請求->服務端處理->處理完畢執行客戶端回調,在客戶端(瀏覽器)發出請求後,仍然可以做其他的事。
總結來說,同步和異步的區別:請求發出後,是否需要等待結果,才能繼續執行其他操作。

阻塞(block)與非阻塞

阻塞和非阻塞這兩個概念與程序(線程)等待消息通知(無所謂同步或者異步)時的狀態有關。也就是說阻塞與非阻塞主要是程序(線程)等待消息通知時的狀態角度來說的。
阻塞和非阻塞關注的是程序在等待調用結果(消息,返回值)時的狀態.
阻塞調用是指調用結果返回之前,當前線程會被掛起。調用線程只有在得到結果之後纔會返回。
非阻塞調用指在不能立刻得到結果之前,該調用不會阻塞當前線程。

同步/異步關注的是消息通知的機制,而阻塞/非阻塞關注的是程序(線程)等待消息通知時的狀態。
同步的情況下,是由處理消息者自己去等待消息是否被觸發,而異步的情況下是由觸發機制來通知處理消息者

併發並行

併發:在操作系統中,是指一個時間段中有幾個程序都處於已啓動運行到運行完畢之間,且這幾個程序都是在同一個處理機上運行,但任一個時刻點上只有一個程序在處理機上運行。當有多個線程在操作時,如果系統只有一個CPU,則它根本不可能真正同時進行一個以上的線程,它只能把CPU運行時間劃分成若干個時間段,再將時間 段分配給各個線程執行,在一個時間段的線程代碼運行時,其它線程處於掛起狀。.這種方式我們稱之爲併發(Concurrent)。

並行:當系統有一個以上CPU時,則線程的操作有可能非併發。當一個CPU執行一個線程時,另一個CPU可以執行另一個線程,兩個線程互不搶佔CPU資源,可以同時進行,這種方式我們稱之爲並行(Parallel)

併發和並行的區別:
你吃飯吃到一半,電話來了,你一直到吃完了以後纔去接,這就說明你不支持併發也不支持並行。
你吃飯吃到一半,電話來了,你停了下來接了電話,接完後繼續吃飯,這說明你支持併發。
你吃飯吃到一半,電話來了,你一邊打電話一邊吃飯,這說明你支持並行。
併發的關鍵是你有處理多個任務的能力,不一定要同時。並行的關鍵是你有同時處理多個任務的能力。
所以我認爲它們最關鍵的點就是:是否是『同時』。

原理

異步和多線程有什麼區別

其實,異步是目的,而多線程是實現這個目的的方法。異步是說,A發起一個操作後(一般都是比較耗時的操作,如果不耗時的操作就沒有必要異步了),可以繼續自顧自的處理它自己的事兒,不用幹等着這個耗時操作返回。

多線程和異步操作的異同

多線程和異步操作兩者都可以達到避免調用線程阻塞的目的,從而提高軟件的可響應性。甚至有些時候我們就認爲多線程和異步操作是等同的概念。但是,多線程和異步操作還是有一些區別的。而這些區別造成了使用多線程和異步操作的時機的區別。

異步操作的本質

所有的程序最終都會由計算機硬件來執行,所以爲了更好的理解異步操作的本質,我們有必要了解一下它的硬件基礎。 熟悉電腦硬件的朋友肯定對DMA這個詞不陌生,硬盤、光驅的技術規格中都有明確DMA的模式指標,其實網卡、聲卡、顯卡也是有DMA功能的。DMA就是直 接內存訪問的意思,也就是說,擁有DMA功能的硬件在和內存進行數據交換的時候可以不消耗CPU資源。只要CPU在發起數據傳輸時發送一個指令,硬件就開 始自己和內存交換數據,在傳輸完成之後硬件會觸發一箇中斷來通知操作完成。這些無須消耗CPU時間的I/O操作正是異步操作的硬件基礎。所以即使在DOS 這樣的單進程(而且無線程概念)系統中也同樣可以發起異步的DMA操作。

線程的本質

線程不是一個計算機硬件的功能,而是操作系統提供的一種邏輯功能,線程本質上是進程中一段併發運行的代碼,所以線程需要操作系統投入CPU資源來運行和調度。

異步操作的優缺點

因爲異步操作無須額外的線程負擔,並且使用回調的方式進行處理,在設計良好的情況下,處理函數可以不必使用共享變量(即使無法完全不用,最起碼可以減少 共享變量的數量),減少了死鎖的可能。當然異步操作也並非完美無暇。編寫異步操作的複雜程度較高,程序主要使用回調方式進行處理,與普通人的思維方式有些 初入,而且難以調試。

多線程的優缺點

多線程的優點很明顯,線程中的處理程序依然是順序執行,符合普通人的思維習慣,所以編程簡單。但是多線程的缺點也同樣明顯,線程的使用(濫用)會給系統帶來上下文切換的額外負擔。並且線程間的共享變量可能造成死鎖的出現。

異步與多線程,從辯證關係上來看,異步和多線程並不時一個同等關係,異步是目的,多線程只是我們實現異步的一個手段.什麼是異步:異步是當一個調用請求發送給被調用者,而調用者不用等待其結果的返回.實現異步可以採用多線程技術或則交給另外的進程來處理

在Python中

Threading

參考:https://www.runoob.com/python3/python3-multithreading.html
參考:https://www.cnblogs.com/loleina/p/9651478.html

python用_Thread對接曾經的線程,但是python 3之後推薦使用Threading,我學習的也是Threading
首先來了解一下他的可調用方法

  • threading.Thread(target=func,args=())
  • threading.currentThread():返回當前的線程變量
  • threading.enumerate():返回一個包含正在運行的線程的list。正在運行指線程啓動後、結束前,不包括啓動前和終止後的線程。 ---- enumerate 列舉
  • threading.activeCount():返回正在運行的線程數量,與len(threading.enumerate())有相同的結果
  • run():線程的活動,當我們通過threading.Thread繼承創建一個子類,並且實例化爲線程時,需要重寫run
    start():啓動線程活動
  • join([time]):等待至線程中止。這阻塞調用線程直至線程的join() 方法被調用中止-正常退出或者拋出未處理的異常-或者是可選的超時發生
  • isAlive():返回線程是否活動的
  • getName():返回線程名
  • setName():設置線程名

如果多個線程共同對某個數據修改,則可能出現不可預料的結果,爲了保證數據的正確性,需要對多個線程進行同步。
使用 Thread 對象的 Lock 和 Rlock 可以實現簡單的線程同步,這兩個對象都有 acquire 方法和 release 方法,對於那些需要每次只允許一個線程操作的數據,可以將其操作放到 acquire 和 release 方法之間。
多線程的優勢在於可以同時運行多個任務(至少感覺起來是這樣)。但是當線程需要共享數據時,可能存在數據不同步的問題。
考慮這樣一種情況:一個列表裏所有元素都是0,線程"set"從後向前把所有元素改成1,而線程"print"負責從前往後讀取列表並打印。
那麼,可能線程"set"開始改的時候,線程"print"便來打印列表了,輸出就成了一半0一半1,這就是數據的不同步。爲了避免這種情況,引入了鎖的概念。
鎖有兩種狀態——鎖定和未鎖定。每當一個線程比如"set"要訪問共享數據時,必須先獲得鎖定;如果已經有別的線程比如"print"獲得鎖定了,那麼就讓線程"set"暫停,也就是同步阻塞;等到線程"print"訪問完畢,釋放鎖以後,再讓線程"set"繼續。
經過這樣的處理,打印列表時要麼全部輸出0,要麼全部輸出1,不會再出現一半0一半1的尷尬場面

  • threading.Lock.acquire():
  • threading.Lock.release():

守護線程(setDaemon)
如果一個線程是守護線程,那麼它將會和主線程一起結束,而主線程會等待所有的非守護線程的子線程結束而退出。因此可以認爲,守護線程是“不重要的線程”,主線程不等它。

  • t =threading.Thread(target=func,args=())
  • t.setDaemon(True)

互斥鎖(Lock)
  通過獲取鎖對象,訪問共有數據,最後釋放鎖來完成一次操作,一旦某個線程獲取了鎖,當這個線程被切換時,下個個進程無法獲取該公有數據

  • threading.Lock.acquire():
  • threading.Lock.release():

遞歸鎖(RLock)
  RLock內部維護着一個Lock和一個counter變量,counter記錄了acquire的次數,從而使得資源可以被多次require。直到一個線程所有的acquire都被release,其他的線程才能獲得資源。

  • threading.RLock.acquire():
  • threading.RLock.release():

事件(Event)
  如果某一個線程執行,需要判斷另一個線程的狀態,就可以使用Event,如:用Event類初始化一個event對象,線程a執行到某一步,設置event.wait(),即線程a阻塞,直到另一個線程設置event.set(),將event狀態設置爲True(默認是False)

  • event = threading.Event()
  • event.isSet():返回event的狀態值
  • event.clear():恢復event的狀態值爲False
  • event.wait():如果 event.isSet()==False將阻塞線程
  • event.set(): 設置event的狀態值爲True,所有阻塞池的線程激活進入就緒狀態, 等待操作系統調度

線程隊列(queue)
特點:先進先出,
作用:多個線程之間進行通信(作用不大,多進程的隊列用處大)

  • q=queue.Queue()
  • q.get() 獲取 無數據時會阻塞
  • q.set(‘item’) 設置,先設置的數據,先取出
  • q.empty() 是否爲空

Process

引用:https://www.cnblogs.com/ifyoushuai/p/9471569.html

  • p=Process(target=, args=())

  • p.start():啓動進程,並調用該子進程中的p.run()

  • p.run():進程啓動時運行的方法,正是它去調用target指定的函數,我們自定義類的類中一定要實現該方法

  • p.terminate():強制終止進程p,不會進行任何清理操作,如果p創建了子進程,該子進程就成了殭屍進程,使用該方法需要特別小心這種情況。如果p還保存了一個鎖那麼也將不會被釋放,進而導致死鎖

  • p.is_alive():如果p仍然運行,返回True

  • p.join([timeout]):主線程等待p終止。timeout是可選的超時時間

  • p.daemon:默認值爲False,如果設爲True,代表p爲後臺運行的守護進程,當p的父進程終止時,p也隨之終止,並且設定爲True後,p不能創建自己的新進程,必須在p.start()之前設置

  • p.name:進程的名稱

  • p.pid:進程的pid

  • p.exitcode:進程在運行時爲None、如果爲–N,表示被信號N結束(瞭解即可)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章