【OS】進程、線程與協程的區別

一、進程

進程,保存在硬盤上的程序運行以後,會在內存空間裏形成一個獨立的內存體,這個內存體有自己獨立的地址空間,有自己的堆,上級掛靠單位是操作系統
 操作系統會以進程爲單位,分配系統資源(CPU時間片、內存等資源),進程是資源分配的最小單位,是操作系統進行資源分配和調度的一個獨立單位
 進程一般由程序,數據集合和進程控制塊三部分組成
在這裏插入圖片描述
進程狀態圖
在這裏插入圖片描述
進程間通信(IPC)

  • 管道(Pipe)
  • 命名管道(FIFO)
  • 消息隊列(Message Queue)
  • 信號量(Semaphore)
  • 共享內存(Shared Memory)
  • 套接字(Socket)

二、線程

線程,有時被稱爲輕量級進程(Lightweight Process,LWP),是操作系統調度(CPU調度)執行的最小單位

線程狀態圖
在這裏插入圖片描述

三、協程

協程,又稱微線程,纖程。英文名Coroutine
一種比線程更加輕量級的存在,協程不是被操作系統內核所管理,而完全是由程序所控制(也就是在用戶態執行)

子程序,或者稱爲函數,在所有語言中都是層級調用,比如A調用B,B在執行過程中又調用了C,C執行完畢返回,B執行完畢返回,最後是A執行完畢。所以子程序調用是通過棧實現的,一個線程就是執行一個子程序。子程序調用總是一個入口,一次返回,調用順序是明確的。而協程的調用和子程序不同

協程看上去也是子程序,但執行過程中,在子程序內部可中斷,然後轉而執行別的子程序,在適當的時候再返回來接着執行。

注意,在一個子程序中中斷,去執行其他子程序,不是函數調用,有點類似CPU的中斷。比如子程序A、B:

def A():
    print '1'
    print '2'
    print '3'

def B():
    print 'x'
    print 'y'
    print 'z'

假設由協程執行,在執行A的過程中,可以隨時中斷,去執行B,B也可能在執行過程中中斷再去執行A,結果可能是:

1
2
x
y
3
z

在A中是沒有調用B的,所以協程的調用比函數調用理解起來要難一些

因爲協程是一個線程執行,那怎麼利用多核CPU呢?最簡單的方法是多進程+協程,既充分利用多核,又充分發揮協程的高效率,可獲得極高的性能

例子:
傳統的生產者-消費者模型是一個線程寫消息,一個線程取消息,通過鎖機制控制隊列和等待,但一不小心就可能死鎖
如果改用協程,生產者生產消息後,直接通過yield跳轉到消費者開始執行,待消費者執行完畢後,切換回生產者繼續生產,效率極高

import time

def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('[CONSUMER] Consuming %s...' % n)
        time.sleep(1)
        r = '200 OK'

def produce(c):
    c.next()
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] Producing %s...' % n)
        r = c.send(n)
        print('[PRODUCER] Consumer return: %s' % r)
    c.close()

if __name__=='__main__':
    c = consumer()
    produce(c)

執行結果:

[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK

注意到consumer函數是一個generator(生成器),把一個consumer傳入produce後:

  1. 調用c.next()啓動生成器
  2. 一旦生產了東西,通過c.send(n)切換到consumer執行
  3. consumer通過yield拿到消息,處理,又通過yield把結果傳回
  4. produce拿到consumer處理的結果,繼續生產下一條消息
  5. produce決定不生產了,通過c.close()關閉consumer,整個過程結束
  6. 整個流程無鎖,由一個線程執行,produce和consumer協作完成任務,所以稱爲“協程”,而非線程的搶佔式多任務

四、關聯圖

相互關係
在這裏插入圖片描述

進程與線程
在這裏插入圖片描述

五、區別

1. 調度

  • 線程:調度和分配的基本單位
  • 進程:擁有資源的基本單位

2. 擁有資源

  • 進程:擁有資源的一個獨立單位,進程所維護的是程序所包含的資源(靜態資源), 如:地址空間,打開的文件句柄集,文件系統狀態,信號處理handler等
  • 線程:不擁有系統資源,但可以訪問隸屬於進程的資源。線程所維護的運行相關的資源(動態資源),如:運行棧,調度相關的控制信息,待處理的信號集等

3. 系統開銷:

  • 進程:在創建或撤消進程時,由於系統都要爲之分配和回收資源,導致系統的開銷明顯大於創建或撤消線程時的開銷。進程有獨立的地址空間,一個進程崩潰後,在保護模式下不會對其它進程產生影響。一個進程死掉就等於所有的線程死掉,所以多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些
  • 線程:線程只是一個進程中的不同執行路徑。線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間。線程的調度需要內核態與用戶的頻繁切入切出,資源消耗也不小
  • 協程:最大的優勢就是協程極高的執行效率。因爲子程序切換不是線程切換,而是由程序自身控制,因此,沒有線程切換的開銷,和多線程比,線程數量越多,協程的性能優勢就越明顯。不需要多線程的鎖機制,因爲只有一個線程,也不存在同時寫變量衝突,在協程中控制共享資源不加鎖,只需要判斷狀態就好了,所以執行效率比多線程高很多。一個線程的內存在 MB 級別,而協程只需要 KB 級別

4. 切換性能消耗

進程切換分兩步:
1.切換頁目錄以使用新的地址空間
2.切換內核棧和硬件上下文
對於linux來說,線程和進程的最大區別就在於地址空間,對於線程切換,第1步是不需要做的,第2是進程和線程
切換都要做的。

切換的性能消耗:
1.線程上下文切換和進程上下問切換一個最主要的區別是線程的切換虛擬內存空間依然是相同的,但是進程切換
是不同的。這兩種上下文切換的處理都是通過操作系統內核來完成的。內核的這種切換過程伴隨的最顯著的性能
損耗是將寄存器中的內容切換出。
2.另外一個隱藏的損耗是上下文的切換會擾亂處理器的緩存機制。簡單的說,一旦去切換上下文,處理器中所有
已經緩存的內存地址一瞬間都作廢了。還有一個顯著的區別是當你改變虛擬內存空間的時候,處理的頁表緩衝會被全部刷新,這將導致內存訪問在一段時間內相當的低效。但是在線程的切換中,不會出現這個問題

5. 實現

  • 線程:線程是操作系統的內核對象,多線程編程時,如果線程數過多,就會導致頻繁的上下文切換,這些 cpu時間是一個額外的耗費
  • 協程:在應用層模擬的線程,他避免了上下文切換的額外耗費,兼顧了多線程的優點。簡化了高併發程序的複雜度

6. 堆棧

  • 進程:擁有自己獨立的堆和棧,既不共享堆,也不共享棧,進程由操作系統調度
  • 線程:擁有自己獨立的棧和共享的堆,共享堆,不共享棧,標準線程由操作系統調度
  • 協程:擁有自己獨立的棧和共享的堆,共享堆,不共享棧,協程由程序員在協程的代碼裏顯示調度

假設有一個單核的操作系統,系統上沒有其它的程序需要運行,現有兩個線程 A 和 B,A 和 B 在單獨
運行時都需要10 秒來完成自己的任務,而且任務都是運算操作,線程 A 和 B 之間沒有競爭和共享數據
的問題。現在讓 A 和 B 兩個線程並行,則操作系統會不停的在 A 和 B 兩個線程之間切換,達到一種
僞並行的效果

如果操作系統切換的頻率是每秒一次,切換的成本是 0.1 秒(主要是棧切換),則總共需
20 + 19 * 0.1 = 21.9 秒;如果使用協程的方式,可以先運行協程 A,A 結束的時候讓位給協程 B,
只發生一次切換,則總共需要 20 + 1 * 0.1 = 20.1 秒。如果操作系統是雙核的,而且線程是標準線程,
那麼線程 A 和 B 可以達到真的並行,則總時間爲 10 秒;而協程的方式仍然需要 20.1 秒的時間

六、聯繫

  1. 一個線程只能屬於一個進程,而一個進程可以有多個線程,但至少有一個線程
  2. 資源分配給進程,同一進程的所有線程共享該進程的所有資源
  3. 線程在執行過程中,需要協作同步。不同進程的線程間要利用消息通信的辦法實現同步
  4. 進程和線程只在某個cpu上運行(多核系統)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章