進程&線程&協程

進程 python

1、基本概念程序員

進程是系統資源分配的最小單位, 程序隔離的邊界系統由一個個進程(程序)組成。通常狀況下,包括文本區域(text region)、數據區域(data region)和堆棧(stack region)算法

 

  • 文本區域存儲處理器執行的代碼
  • 數據區域存儲變量和進程執行期間使用的動態分配的內存;
  • 堆棧區域存儲着活動過程調用的指令和本地變量。

所以進程的建立和銷燬都是相對於系統資源,因此是一種比較昂貴的操做。 進程有三個狀態:數據庫

  • 就緒態:運行條件都已知足,等待系統分配處理器以便運行。
  • 執行態:CPU正在運行進程。
  • 等待態:等待某個事件的完成。
  • 結束態:進程結束。

進程是爲了提升CPU的執行效率,減小由於程序等待帶來的CPU空轉以及其餘計算機軟硬件資源的浪費而提出來的,也讓併發成爲可能。現假設一個進程內有A、B兩個I/O任務而且CPU爲雙核,若是沒有多進程的實現,CPU在執行時只能利用到一個核的資源:  先等待A任務的讀取-寫入操做所有執行完畢後,而後才能去執行B任務。但這對於CPU來講是無疑是極大的浪費,既須要等待A任務的耗時操做,又不能利用到CPU多核資源。因此多進程是讓CPU執行某個耗時任務時(網絡延遲、睡眠等待等),建立一個新的子進程,讓子進程去執行任務B,等待任務A的耗時操做完畢以後,CPU再切換到另外一個任務執行。注意,由於涉及到切換那麼就須要有一個東西來記錄當前這個進程的狀態,好比進程運行時所需的系統資源:內存,硬盤,鍵盤等(地址空間,全局變量,文件描述符,各類硬件等等),因此進程的意義就在於經過進程來記錄任務所擁有的系統資源&標識任務。進程狀態的記錄、恢復、切換稱之爲上下文切換,但因爲CPU執行速度飛快,所以使得看上去就像是多個進程在同時進行.緩存

 
2、進程間數據共享

進程是系統進行資源分配和調度的一個獨立單位,擁有本身獨立的堆和棧,但既不共享堆,亦不共享棧,每一個進程都是獨立的,各自持有一份數據沒法共享,因此爲了可以讓進程之間實現數據共享:安全

  • Queue
  • Array
  • Manager.dict
  • pipe

 

1.Queue是多進程中安全的隊列,可使用Queue實現多進程之間的數據傳遞。服務器

  其中put方法用於插入數據到隊列中,有兩個可選參數:blocked和timeout。若是blocked爲True(默認值),而且timeout爲正值,該方法會阻塞timeout指定的時間,直到該隊列有剩餘的空間。若是超時,會拋出Queue.Full異常。若是blocked爲False,但該Queue已滿,會當即拋出Queue.Full異常網絡

  get方法能夠從隊列讀取而且刪除一個元素。一樣,get方法有兩個可選參數:blocked和timeout。若是blocked爲True(默認值),而且timeout爲正值,那麼在等待時間內沒有取到任何元素,會拋出Queue.Empty異常。若是blocked爲False,有兩種狀況存在,若是Queue有一個值可用,則當即返回該值,不然,若是隊列爲空,則當即拋出Queue.Empty異常多線程

 

2.pp = multiprocessing.Pipe()  Pipe方法返回(conn1, conn2)表明一個管道的兩個端。併發

  Pipe方法有duplex參數,若是duplex參數爲True(默認值),那麼這個管道是全雙工模式,也就是說conn1和conn2都可收發。duplex爲False,conn1只負責接受消息,conn2只負責發送消息

  管道的兩個端還有send方法負責發送消息,和recv方法負責接受消息。例如,在全雙工模式下,能夠調用conn1.send發送消息,conn1.recv接收消息。若是沒有消息可接收,recv方法會一直阻塞。若是管道已經被關閉,那麼recv方法會拋出EOFError

 

 

3、進程池

目的:重複利用進程池中原先創建好的的進程,簡化進程的建立、銷燬、任務分配.使用完後的進程由進程池管理

地球語言:進程池是事先劃分一塊系統資源區域,這組資源區域在服務器啓動時就已經建立和初始化,用戶若是想建立新的進程,能夠直接取得資源,從而避免了動態分配資源(這是很耗時的)。

進程池中的全部子進程都運行着相同的代碼,並具備相同的屬性,好比優先級、 PGID 等。當有新的任務來到時,主進程將經過某種方式選擇進程池中的某一個子進程來爲之服務。相比於動態建立子進程,選擇一個已經存在的子進程的代價顯得小得多。進程池內子進程的個數可經過maxsize參數指定。

主進程選擇子進程的兩種方式:

  • 最簡單、最經常使用的算法是隨機算法和 Round Robin (輪流算法)。
  • 主進程和全部子進程經過一個共享的工做隊列來同步,子進程所有睡眠在該工做隊列上。當有新的任務到來時,主進程將任務添加到工做隊列中。這將喚醒正在等待任務的子進程
    • 但只有一個子進程將得到新任務的「接管權」,它能夠從工做隊列中取出任務並執行之,而其餘子進程將繼續睡眠在工做隊列上。
       

當選擇好子進程後,須要在主進程和子進程之間預先創建好一條管道,而後經過管道或者Queue隊列來實現全部的進程間通訊。注 : 進程池建立的進程 默認都是守護進程,只要主進程結束,子進程就結束。

提交進程池的方式有同步提交&異步提交,同步提交的方式必須是一個子進程執行完纔會執行下一個,因此主進程是最後結束的,會等待進程池中全部進程結束; 可是異步方式(子進程同時執行)必須結合close&join使用,pool.close() 必須先關閉進程池,表示不容許再添加新的任務,再使用pool.join()表示等待進程池中全部進程結束,主進程再繼續執行。



線程

1、基本概念

線程是程序執行最小單元。線程擁有本身獨立的棧和共享的堆,共享堆,但不共享棧。一個進程內能夠有多個線程(至少一個主線程),多個線程共享該進程的全部變量

線程是爲了減小進程切換和建立的開銷,提升執行效率和節省資源,提升系統的併發性,解決一個進程只能執行一個任務的缺陷,使得進程內併發成爲可能。
 
假設,一個文本程序,須要接受鍵盤輸入,將內容顯示在屏幕上,還須要保存信息到硬盤中。但只有一個進程執行此任務,那麼勢必形成同一時間只能執行其中一個任務的局面(當保存時,就不能經過鍵盤輸入內容)。若是使用多進程來實現的話:每一個進程只負責其中一個任務,例如,進程A負責接收鍵盤輸入的任務,進程B負責將內容顯示在屏幕上的任務,進程C負責保存內容到硬盤中的任務。但此時進程A,B,C間的協做涉及到了進程通訊問題,並且都有共同須要的東西 --- 文本內容,但進程之間本來是不支持數據共享的,即便能夠經過隊列實現數據共享,但不停的切換會形成性能上的損失,並且由於一個簡單的任務而要額外的建立子進程,還得給它分配主進程全部的資源(諸如系統資源,地址空間,全局變量等),對於CPU來講過小題大作了。如有一種機制,可使任務A,B,C共享資源,這樣上下文切換所須要保存和恢復的內容就少了,同時又能夠減小通訊所帶來的性能損耗,因此線程就是這種機制,線程共享進程的大部分資源,並參與CPU的調度, 固然線程本身也是擁有本身的資源的,例如,棧,寄存器等等。 此時進程同時也是線程的容器。但線程也有着本身的缺陷的,若一個線程掛掉了,一整個進程也掛掉了,這意味着其它線程也掛掉了,進程卻沒有這個問題,一個進程掛掉,另外的進程仍是活着,由於進程之間都是擁有獨立的資源,互不干擾的。

  • 線程屬於進程
  • 線程共享進程的內存地址空間
  • 線程幾乎不佔有系統資源
  • 通訊問題:進程至關於一個容器,而線程而是運行在容器裏面的,所以對於容器內的東西,線程是共同享有的,所以線程間的通訊能夠直接經過全局變量進行通訊,可是由此帶來的例如多個線程讀寫同一個地址變量的時候則將帶來不可預期的後果,所以這時候引入了各類鎖的做用,例如互斥鎖等。

 

線程&進程的上下文切換

進程切換:

  1. 切換頁目錄以使用新的地址空間
  2. 切換內核棧
  3. 切換硬件上下文

線程切換僅需第二、3步,所以進程的切換代價比較大,但相比進程不夠穩定容易丟失數據。

 
2、自定義線程類    
    1.自定義的線程類必須繼承  threading.Thread 爲父類。

        class Mythread(threading.Thread):
    2.自定義線程類中重寫 run方法,再根據業務需求在run方法中添加邏輯代碼。
        def run(self):

    3.經過 自定義線程類的實例化對象調用start() 方法,啓動自定義線程

        mythread.start()
    

注:使用實例化對象調用start方法,start會自動調用 run方法,若在建立實例對象須要進行傳參,要重寫 __init__ 方法,可是必須初始化父類的 __init__ 方法,否則沒法調用自定義線程類中重寫的 run方法
    

    方法一:
      def __init__(self, num):
        super().__init__()
        self.num = num


    方法二:
      def __init__(self, num):
        super(當前類名, self).__init__()
        self.num = num

 
 
3、多線程間的資源競爭
 
產生緣由:因爲線程之間的全局變量都是共享的,任何一個線程均可以對其進行修改,若多個線程同時對同一個全局變量進行訪問和改寫操做,會出現資源競爭問題,致使最終的數據結果錯誤。
 
一個栗子:假設線程A對全局變量num進行修改須要必定的執行時間,但因爲cpu都是併發執行程序(不停切換),因此致使在計算大數值計算時,線程A獲得計算結果,但沒有放入內存中,就切換到了線程B,  而線程B執行完了這一次的操做,把結果放入內存,再次輪到線程A時,會直接覆蓋了線程B上一次的計算結果

解決機制:1.同步解決,按照順序依次執行每一個線程。先等待線程A執行完畢,再執行線程B。缺陷:本來能使用多個線程實現多任務操做,如此的話,多線程就沒有存在的意義了,程序又變成了單任務。

      2. 異步解決,互斥鎖用於防止兩條線程同時對同一公共資源(好比全局變量)進行讀寫的機制經過將代碼切片成一個一個的臨界區域而達成的。

      地球語言:當線程A對某個全局變量進行操做以前,將其上鎖,此時就算CPU切換到線程B它也沒法解鎖沒法操做,只有當線程A真正操做完畢解鎖以後,線程B纔可以執行它的操做

 

互斥鎖底層:

 互斥鎖實質是一種變量,上鎖解鎖實際上是給它置0、置1的操做。現假設mutex=1時表示鎖是空閒的,若是此時某個線程調用acquire( )函數【上鎖】就能夠得到鎖資源,而且mutex此時被置爲0。

 當mutex=0時表示鎖正在被其餘線程佔用,若是此時有其餘線程也調用acquire( )函數來得到鎖時會被掛起等待。

流程:

  mutex = threading.Lock   # 建立互斥鎖,默認鎖是開啓狀態
  mutex.acquire( )       # 上鎖
  mutex.release( )      # 解鎖

   

互斥鎖可能致使的死鎖問題:

死鎖:線程間共享多個資源的時候,若是兩個線程分別佔有⼀部分資源而且同時等待對⽅的資源,而處於的一種永久等待狀態,就會形成死鎖。

地球語言:假設A線程持有鎖a,B線程持有鎖b,而主線程訪問臨界區的條件是須要同時具備鎖a和鎖b,那麼A就會等待B釋放鎖b,B會等待A釋放鎖a,若是沒有一種措施,他兩會一直等待,這樣就產生了死鎖。

 

如何產生的死鎖:
一、系統資源不足:若是系統資源足夠,每一個申請鎖的線程都能後得到鎖,那麼產生死鎖的狀況就會大大下降;
二、申請鎖的順序不當:當兩個線程按照不一樣的順序申請、釋放鎖資源時也會產生死鎖。

死鎖產生的條件:
一、互斥屬性:即每次只能有一個線程佔用資源。
二、請求與保持:即已經申請到鎖資源的線程能夠繼續申請。在這種狀況下,一個線程也能夠產生死鎖狀況,即抱着鎖找鎖。
三、不可剝奪:線程已經獲得鎖資源,在沒有本身主動釋放以前,不能被強行剝奪。
四、循環等待:多個線程造成環路等待,每一個線程都在等待相鄰線程的鎖資源。


死鎖的避免:
一、既然死鎖的產生是因爲使用了鎖,那麼若是能夠在不使用鎖的狀況下完成任務,就儘可能不使用互斥鎖機制。若是有多種方案都能實現,那麼儘可能不選用帶鎖的這種方案
二、儘可能避免同時得到多把鎖,若是有必要,就要保證得到鎖的順序相同。

三、只有一把互斥鎖存在時,尤爲注意互斥鎖使用完畢且代碼即將終止前,必須解鎖。

四、儘量少的鎖定所競爭的資源代碼

五、將鎖加入到隊列中,由於隊列遵循的是先進先出規範。

 

4、GIL鎖 

GIL鎖:Global Interpreter Lock 全局解釋器鎖,互斥鎖保證的是線程間公共數據資源的安全。GIL也是一種特殊的互斥鎖,解釋器被這個GIL全局解釋器鎖保護着,它確保任什麼時候候都只有一個Python線程執行。同一進程內在同一時間只能有一個線程使用到CPU(僞併發)GIL並非Python的特性,GIL只在CPython解釋器上存在,Python徹底能夠不依賴於GIL

基本概念:任何Python線程執行前,必須先得到GIL鎖,而後每執行100條字節碼,解釋器就自動釋放GIL,讓別的線程有機會執行。但在此線程執行期間別的線程沒法使用CPU,只有等待當前上鎖的線程執行完畢解鎖後纔會切換到下一個線程。這個GIL全局鎖實際上把全部線程的執行代碼都給上了鎖,因此多線程在Python中只能交替執行,即便100個線程跑在100核CPU上,也只能用到1個核。

 

互斥鎖&GIL鎖:

    • 線程互斥鎖是Python代碼層面的鎖,解決Python程序中多線程共享資源的問題(線程數據共共享,當各個線程訪問數據資源時會出現競爭狀態,形成數據混亂)。
    • GIL是Python解釋層面的鎖,解決解釋器中多個線程的競爭資源問題(多個子線程在系統資源競爭時,都在等待對方某個部分資源解除佔用狀態,結果誰也不肯意先解鎖,而後互相等着,程序沒法執行下去)。
    • 互斥鎖保證的是讓當前的線程把事情作完再解鎖給下一個線程 , GIL鎖保證的是同一時間只能有一個線程在執行,但GIL不能保證這個線程所有執行完了再讓CPU去調度下一個,可能線程1只執行了20%就被cpu切換到線程2 , 因此就算有GIL鎖若是存在公共資源競爭的狀況也必須同時存在互斥鎖 . 互斥鎖:邏輯上只有一個線程執行 , GIL:調度上只有一個線程執行 。 

       

       

產生緣由:
  • Python語⾔和GIL沒有半⽑錢關係。僅僅是因爲歷史緣由在Cpython虛擬機(解釋器),難以移除GIL。

  • 早期計算機都是單核設計,爲了防止出現資源競爭而發生錯誤,因此加上一把全局解釋器鎖。
  • CPython在執行多線程的時候並非線程安全的,因此爲了程序的穩定性,加⼀把全局解釋鎖,可以確保任什麼時候候都只有⼀個Python線程。
 
對程序的影響:
  • Python中同一時刻有且只有一個線程會執行;
  • Python中的多個線程因爲GIL鎖的存在沒法利用多核CPU
  • Python中的多線程不適合計算機密集型的程序;
  • 若是程序須要大量的計算,利用多核CPU資源,可使用多進程來解決;
  • GIL鎖對於IO密集型的阻塞操做會自動釋放鎖;
  • Python 3.2開始使用新的GIL。新的GIL實現了用一個固定的超時時間來指示當前的線程放棄全局鎖。在當前線程保持這個鎖,且其餘線程請求這個鎖時,當前線程就會在5毫秒後被強制釋放該鎖。

 

GIL鎖釋放時機:

  • 在當前線程執行超時後會自動釋放
  • 在當前線程執行阻塞操做時會自動釋放
  • 在當前線程執行完畢後自動釋放。
  • 在執行CPU密集型任務時,任務執行每100條字節碼就會釋放。

 

GIL鎖解決方案:

  • 更換⼀個解釋器執⾏程序(jython:⽤JAVA寫的python解釋器)。
  • 使⽤多進程替換多線程 multiprocessing 是⼀個多進程模塊,開多個進程,每一個進程都帶⼀個GIL,就至關於多線程來⽤了。
  • 使⽤python語言的特性:膠水特性.讓子線程部分用c語言來寫。(實質上也至關於那部分代碼繞過了cython解釋)。

 

 

協程

1、基本概念

能夠暫停執行的函數,協程經過在線程中實現調度,避免了陷入內核級別的上下文切換形成的性能損失,進而突破了線程在IO上的性能瓶頸。 當涉及到大規模的併發鏈接時,例如10K鏈接。以線程做爲處理單元,系統調度的開銷仍是過大。在實現多任務時, 線程切換從系統層面遠不止保存和恢復 CPU上下文這麼簡單。 操做系統爲了程序運行的高效性,每一個線程都有本身緩存Cache等等數據,操做系統還會幫你作這些數據的恢復操做。因此線程的切換很是耗性能。可是協程的切換隻是單純的操做CPU的上下文,因此⼀秒鐘切換個上百萬次系統都抗的住。

當鏈接數不少 —> 須要大量的線程來幹活 —> 可能大部分的線程處於ready狀態 —> 系統會不斷地進行上下文切換。既然性能瓶頸在上下文切換,那乾脆就在線程中本身實現調度,不陷入內核級別的上下文切換,協程切換隻涉及到CPU的上下文,因此在線程內實現協程可大大提高性能。

  • 協程屬於線程。協程運行在線程裏面,所以協程又稱微線程和纖程等
  • 協程沒有線程的上下文切換消耗。協程的調度切換是用戶(程序員)手動切換的,所以更加靈活,所以又叫用戶空間線程.
  • 原子操做性。因爲協程是用戶調度的,因此不會出現執行一半的代碼片斷被強制中斷了,所以無需原子操做鎖。

通俗的理解:在⼀個線程中的某個函數,能夠在任何地方保存當前函數的⼀些臨時變量等信息,而後切換到另外⼀個函數中執⾏,注意不是經過調用函數的方式作到的,而且切換的次數以及何時再切換到原來的函數都由開發者本身肯定。

 

2、迭代器和生成器

Python中,使用了yield的函數爲生成器,生成器一次只返回一個數據,而且能夠屢次返回值。中途能夠暫停一下,轉而執行其餘代碼,再回來繼續執行函數往下的代碼。

 

可迭代對象:能夠經過 for ... in... 的循環語法,迭代讀取數據的叫作可迭代對象,可以使用 isinstance(對象,Iterable) 判斷一個對象是不是可迭代對象

可迭代對象包括 : Python中的字符串,元組,字典,列表,集合,range 。 不可迭代類型 : 數字類型,整型,浮點型,自定義的普通類。

迭代器:實現可迭代對象進行遍歷的工具,幫助可迭代對象記錄當前的迭代位置,而且返回下一個數據的結果。迭代器規定了對象能夠經過next方法返回下一個值。

可迭代對象&迭代器 : 可迭代對象包含迭代器。一個對象所屬的類中實現了__iter__方法(調用一次),那麼它就是一個可迭代對象;若是一個對象所屬的類中實現了  

__next__方法(調用n次),那麼它是一個迭代器。可以使用 isinstance(對象,Iterator) 判斷一個對象是不是迭代器。自定義可迭代對象時, 必須實現 __iter__  方法,;自定義迭代器時,必須實現__iter__和__next__方法。當調用iter(迭代器對象)時會調用__iter__()方法,當調用next(迭代器對象)時會調用__next__()方法。若是自定義迭代器類時,同時實現了__iter__和__next__方法,那麼__iter__方法返回自身便可(self)。

__iter__方法返回的是:當前對象的迭代器類的實例對象 ; __next__方法返回迭代的每一步,當迭代完最後一個數據後,再次調用next拋出StopIteration異常。

 

class MyList(object):
    def __init__(self):
        self.li = list()
        
    def add_item(self, elem):
        self.li.append(elem)

    def __iter__(self):
        return MyIterator(self.li)


    
class MyIterator(object):
    def __init__(self, li):
        self.li = li
        self.count = 0

    def __iter__(self):
        pass

    def __next__(self):    
        if self.count < len(self.li):
            self.count += 1
            return self.li[self.count]    
        else:
            return StopIteration

# TypeError: iter() returned non-iterator of type 'MyIterator'

 

生成器: 是⼀類特殊的迭代器,已經自動實現了__iter__和__next__方法,無需再手動實現。生成器在迭代過程當中能夠修改當前迭代值,但修改普通迭代器的當前迭代值每每會發生異常。Python中有兩種建立生成器的方式

  • 使用 yield 關鍵字的函數,能夠屢次返回後面的值給調用者,生成器實際上也算是實現了迭代器接口(協議)。即生成器也可經過next返回下一個值。
  • 把列表推導式的中括號換成小括號(存儲的是生成列表的方式,不佔空間,再經過for循環就可取出) 

  yield與return不一樣的是:return以後函數會被釋放,而生成器不會。  

 

yield關鍵字:

  • yield 關鍵字會返回跟在後面的結果
  • 暫停程序,保存程序狀態,將函數掛起,直到下次調用next/send,再次回到上次暫停的位置
  • 調用next()/send(),可讓生成器從上一次的斷點處繼續執行(喚醒生成器),並獲得生成器中yield返回的結果。
  • yield關鍵字須要一個變量來接收它後面的結果

注:第一次啓動生成器時,最好使用next方法,若執意使用send,給定參數爲None。生成器中使用return會終止生成器,並拋出異常。

生成器做用:例如,當生成一個包含大量元素的集合時,並非像列表同樣一次性所有返回,而是根據需求使用next方法取值,可大量節約內存空間。



3、協程的實現

只使用一個線程(單線程),在一個線程中規定某個函數的執行順序:一個函數執行後切換到另外一個函數執行,切換不是經過調用函數作到的,切換的次數和什麼時候切換都由開發者自定義。可理解爲單線程上的多任務/生成器

協程 - yield : 建立兩個生成器 , 在主程序中使用死循環切換執行兩個任務

def work1()

  print(「w1」)
  yield

def work2

  print(「w2」)
  yield


w1 = worl1()
w2 = work2()
while True:
  next(w1)
  next(w2)

 

 

協程 - greenlent:

sudo pip3 install greenlet    # 0.安裝模塊

from greenlet import greenlet  # 1. 導入模塊

gre1 = greenlet(work1)       # 2. 建立協程

gre1.switch(*args, **kwargs)  # 3. 啓動協程

 


協程 - gevent

sudo pip3 install gevent            # 0.安裝模塊

import gevent            # 1. 導入模塊

g1 = gevent.spawn(work1, args, kwds)     # 2. 建立並運行

# 3. gevent會根據耗時操做自動切換
# 例如 : gevent.sleep()

# 4. 等待協程執行結束
g1.join()

# 須要打補丁:gevent默認不識別其他耗時操做 : read,recv,accept,sleep... 

# 5. 導入模塊
from gevent import monkey

# 6. 破解
monkey.patch_all()

# 7.獲取當前協程的信息:
gevent.getcurrent() --> 返回一個greenlet對象    

打補丁做用:無需再手動切換協程,會根據阻塞自動切換。

 

 

三者比較

 

應用場景:
  進程:CPU密集型(使用多核)
  線程:io密集型(網絡io,文件io,數據庫io)
  協程:io密集型(網絡io),非阻塞異步併發

多核CPU,CPU密集型應用:多進程

多核CPU,IO密集型應用:多線程 // 多協程

單核CPU,CPU密集型應用:單進程

單核CPU,IO密集型應用:多協程

 

性能比較:
  消耗大小:進程>線程>協程
  切換速度:協程>線程>進程

 

三者區別:
進程:資源分配的最小單位,是線程的容器。進程間數據不共享 ,每一個進程各自有獨立運行空間,殺死其中一個進程不會影響其餘的進程 , 穩定安全,建立銷燬切換耗時長。

     一個程序至少一個進程,一個進程至少一個線程 ; 線程不能獨立運行,必須依存於進程中。需充分使用多核性能的使用進程。  multiprocessing.Process


線程:CPU真正調度的單位,必須依賴於進程,線程之間共享進程的運行空間,共享數據資源,殺死其中一個線程死 ,可能會影響到別的線程 ,建立銷燬切換耗時更短,穩定和安全差些(死鎖)
    互斥鎖保證的是讓當前的線程把事情作完再解鎖給下一個線程 , GIL鎖保證的是同一時間只能有一個線程在執行,但GIL不能保證這個線程所有執行完了再讓cpu去調度下一個,可能線程1只執行了20%,就被cpu切換到線程2 ,因此就算有GIL鎖也必須同時存在互斥鎖 . 互斥鎖:邏輯上只有一個線程執行 , GIL:調度上只有一個線程執行。  threading.Thread

    

協程:  用戶級,輕量級線程。用戶控制,單線程中的多任務,建立銷燬切換耗時最短。    yield、next、greenlet、switch、gevent、spwan

 

一、進程多與線程進行比較

  • 地址空間:線程是CPU真正調度的單位,也是進程內的一個執行單元,進程內至少有一個線程,線程之間共享進程的地址空間,而進程有本身獨立的地址空間。
  • 資源擁有:進程是系統資源分配的最小單位,每一個進程各自有獨立的資源互不干擾。同一個進程內的線程共享進程的資源。
  • 每一個獨立的線程必須有一個程序運行的入口、順序執行序列和程序的出口,可是線程不可以獨立執行,必須依存在進程當中。

 

二、協程與線程進行比較

  •  一個線程能夠多個協程,一個進程也能夠單獨擁有多個協程,這樣python中則能使用多核CPU。
  • 線程進程都是同步機制,而協程能夠選擇同步或異步。
  • 協程能保留上一次調用時的狀態,每次過程重入時,就至關於進入上一次調用的狀態。
 

併發與並行

並行 : 任務數 <= 核數 ; 真正的 多個任務同時執行
併發 : 任務數 > 核數 ; 快速的依次執行多個任務

並行

指的是任務數小於等於CPU核數,即同一時刻多個任務真的是⼀起執行的,真正的並行執行多任務只能在多核CPU上實現,可是因爲任務數量遠遠多於CPU的核心數量,因此操做系統也會自動把不少任務輪流調度到每一個核心上執行。單線程永遠沒法達到並行狀態,另外的,因爲Python的多線程GIL鎖的存在,對於Python來講沒法經過多線程到達並行狀態。

 

併發

指的是任務數多於cpu核數,經過操做系統的各類任務調度算法,實現用多個任務「⼀起」執行(實際上總有⼀些任務不在執行,由於切換任務的速度至關快,看上去的假象是一塊兒執行)

併發設計的標準:使多個操做能夠在重疊的時間段內進行,重疊時間能夠理解爲一段時間內。例如:在時間1s秒內, 具備IO操做的task1和task2都完成,就能夠說是併發執行。因此單線程也是能夠作到併發運行的。固然並行確定是併發的。一個程序可否併發執行,取決於設計,也取決於部署方式。例如, 當給程序開一個線程(協程是不開的)它不多是併發的,由於在重疊時間內根本就沒有兩個task在運行。當一個程序被設計成完成一個任務再去完成下一個任務的時候,即使部署是多線程多協程的也是沒法達到併發運行的。

並行與併發的關係: 併發的設計使到併發執行成爲可能,而並行是併發執行的其中一種模式

相關文章
相關標籤/搜索