本文介紹進程、線程、協程的概念。
預備知識
程序的定義:由若干條具有一定功能的指令所組成的解題順序和步驟。
早期的計算機只能跑單道程序,同一時間段內,各種資源(CPU,內存,硬盤,I/O)被當前跑的程序占用。
后期計算機引入了多道程序,同一時刻,有多個程序并發的運行。
問題:內存中同時放入多道程序,各個程序的代碼、運算數據存放的位置不同。操作系統要怎么才能找到各程序的存放位置呢?
解決:為了方便操作系統管理,完成各程序并發執行,引入了進程、進程實體的概念。系統為每個運行的程序配置一個數據結構,稱為集成控制(PCB),用來描述集成的各種信息(如程序代碼存放的位置),PCB、程序段、數據段三部分構成了進程實體(進程映像)。
進程的定義:
進程是程序的一次執行過程。
進程是一個程序及其數據在處理機上順序執行時所發生的活動。
進程是具有獨立功能的程序在數據集合上運行的過程,它是系統進行資源分配和調度的一個獨立單位。
進程的組成:
進程的組成 [01]
PCB介紹:
PCB介紹
進程的特征:
進程的特征
Python創建進程:
- os.fork() 方法
- Process方法
- Pool方法
進程與程序區別
- 程序是指令和數據的有序集合,其本身沒有任何運行的含義,是一個靜態的概念。
- 而進程是程序在處理機上的一次執行過程,它是一個動態的概念。
- 程序可以作為一種軟件資料長期存在,而進程是有一定生命期的。
- 程序是永久的,進程是暫時的。
線程的定義:
現代操作系統在運行一個程序時,會為其創建一個進程。例如,啟動一個JAVA程序,操作系統就會創建一個Java進程。現代操作系統調度的最小單元是線程,也叫輕量級進程(Light Weight Process),在一個進程里可以創建多個線程,這些線程都擁有各自的計數器、堆棧和局部變量等屬性,并且能夠訪問共享的內存變量。處理器在這些線程上高速切換,讓使用者感覺到這些線程在同時執行。
一個Java程序從main()方法開始執行,然后按照既定的代碼邏輯執行,看似沒有其他線程參與,但實際上Java程序天生就是多線程程序,因為執行main()方法的是一個名稱為main的線程。
為什么要使用多線程?
執行一個簡單的“Hello, World!”,卻啟動了那么多的“無關”線程,是不是把簡單的問題復雜化了?當然不是,因為正確使用多線程,總是能夠給開發人員帶來顯著的好處,而使用多線程的原因主要有以下幾點。
(1)更多的處理器核心
隨著處理器上的核心數量越來越多,以及超線程技術的廣泛運用,現在大多數計算機都比以往更加擅長并行計算,而處理器性能的提升方式,也從更高的主頻向更多的核心發展。如何利用好處理器上的多個核心也成了現在的主要問題。
線程是大多數操作系統調度的基本單元,一個程序作為一個進程來運行,程序運行過程中能夠創建多個線程,而一個線程在一個時刻只能運行在一個處理器核心上。試想一下,一個單線程程序在運行時只能使用一個處理器核心,那么再多的處理器核心加入也無法顯著提升該程序的執行效率。相反,如果該程序使用多線程技術,將計算邏輯分配到多個處理器核心上,就會顯著減少程序的處理時間,并且隨著更多處理器核心的加入而變得更有效率。
(2)更快的響應時間
有時我們會編寫一些較為復雜的代碼(這里的復雜不是說復雜的算法,而是復雜的業務邏輯),例如,一筆訂單的創建,它包括插入訂單數據、生成訂單快照、發送郵件通知賣家和記錄貨品銷售數量等。用戶從單擊“訂購”按鈕開始,就要等待這些操作全部完成才能看到訂購成功的結果。但是這么多業務操作,如何能夠讓其更快地完成呢?
在上面的場景中,可以使用多線程技術,即將數據一致性不強的操作派發給其他線程處理(也可以使用消息隊列),如生成訂單快照、發送郵件等。這樣做的好處是響應用戶請求的線程能夠盡可能快地處理完成,縮短了響應時間,提升了用戶體驗。
多線程的缺點:
(1)等候使用共享資源時造成程序的運行速度變慢。這些共享資源主要是獨占性的資源 ,如打印機等。
(2)對線程進行管理要求額外的 CPU開銷。線程的使用會給系統帶來上下文切換的額外負擔。當這種負擔超過一定程度時,多線程的特點主要表現在其缺點上,比如用獨立的線程來更新數組內每個元素。
(3)線程的死鎖。即較長時間的等待或資源競爭以及死鎖等多線程癥狀。
多進程和多線程的主要區別?
線程是進程的子集(部分),一個進程可能由多個線程組成。多進程的數據是分開的、共享復雜,需要用IPC;但同步簡單。多線程共享進程數據,共享簡單;但同步復雜。
什么是多進程?
進程是程序在計算機上的一次執行活動,即正在運行中的應用程序,通常稱為進程。當你運行一個程序,你就啟動了一個進程。每個進程都有自己獨立的地址空間(內存空間),每當用戶啟動一個進程時,操作系統就會為該進程分配一個獨立的內存空間,讓應用程序在這個獨立的內存空間中運行。
在同一個時間里,同一個計算機系統中如果允許兩個或兩個以上的進程處于運行狀態,這便是多進程,也稱多任務。現代的操作系統幾乎都是多任務操作系統,能夠同時管理多個進程的運行。
多任務帶來的好處是明顯的,比如你可以邊聽mp3邊上網,與此同時甚至可以將下載的文檔打印出來,而這些任務之間絲毫不會相互干擾。
什么是多線程?
線程是一個輕量級的子進程,是最小的處理單元;是一個單獨的執行路徑。可以說:線程是進程的子集(部分),一個進程可能由多個線程組成。
線程是獨立的。如果在一個線程中發生異常,則不會影響其他線程。它使用共享內存區域。
多線程是一種執行模型,它允許多個線程存在于進程的上下文中,以便它們獨立執行但共享其進程資源。
多進程和多線程的區別:
多進程和多線程的區別
大量的進程/線程出現了新的問題?
- 高內存占用
- 調度的高消耗CPU
好了,然后工程師們就發現,其實一個線程分為“內核態“線程和”用戶態“線程。
(1)用戶級線程
用戶級線程(User Level Thread,ULT)是在用戶(目態)空間實現的,是建立在用戶進程內的線程,由對應的應用程序通過存放在用戶空間的一組管理線程的過程(即線程庫)完成對線程的管理工作。ULT的特點為:ULT與內核無關,因此,同一進程中的ULT的調度和切換簡單而快速;雖然內核不知道用戶級線程的存在,但卻管理著該線程所屬進程的活動,如果該用戶級線程調用了系統調用而處于運行態,那么所屬進程還是該阻塞時要阻塞,即線程狀態與進程狀態是獨立的;如果內核阻塞了一個進程,則該進程中所有的線程都將被阻塞;內核只將處理器分配給進程,因此每次只有一個進程中的一個線程在一個處理器上運行。
(2)內核級線程
內核級線程(Kernel Level Thread,KLT)是在核心空間實現的,通過系統調用,由操作系統創建,是依賴于系統內核的線程。由操作系統內核完成對線程的管理工作。線程是CPU調度的基本單位,操作系統為每個線程保持一個核心棧。KLT可以在用戶進程中,也可以在系統進程中。
KLT的特點:同一進程中的KTL切換需要進入核心模式下才能完成;當進程中的一個線程被阻塞時,進程中的另一個線程可以被調度;內核可以調度進程中的多個線程,使其同時在多個CPU上并行運行;內核本身也都可以使用多線程方式編寫,從而改善操作系統本身的并行性。
除了內核級和用戶級線程之外,還可以采用將兩者混合的實現機制。即線程的實現分為兩個層次:用戶層和核心層。內核必須支持內核級線程的創建、調度和管理,同時也允許應用程序創建、調度和管理用戶級線程。線程的創建、調度、同步通常在用戶空間完成,某個應用程序的多個ULT能分配和對應于一個或多個KLT,KLT可同時并行運行于多個CPU,且在阻塞一個ULT時,內核可調度另一個線程執行。
線程模型
線程模型就是指操作系統內線程的實現策略,一般有一對一、多對一和多對多3種模型。
(1)一對一模型
將每個ULT映射到一個KLT,它能夠保障并發性,但是對能支持的線程總數有一定的限制。
(2)多對一模型
將多個ULT映射到一個KLT。線程管理在用戶空間中進行,效率較高。雖然它能創建很多ULT,但內核一次只能調度一個線程執行,因此,并發性得不到保障。
(3)多對多模型
將多個ULT映射到同樣數量或較少數量的KLT。它很好地解決了上面出現的問題,能夠創建需要的線程,并且內核也能并行執行這些線程。
協程的定義
協程,又稱微線程,纖程。英文名Coroutine,是一種用戶態的輕量級線程。
程序,或者稱為函數,在所有語言中都是層級調用,比如A調用B,B在執行過程中又調用了C,C執行完畢返回,B執行完畢返回,最后是A執行完畢。
所以子程序調用是通過棧實現的,一個線程就是執行一個子程序。子程序調用總是一個入口,一次返回,調用順序是明確的。
而協程的調用和子程序不同。
線程是系統級別的它們由操作系統調度,而協程則是程序級別的由程序根據需要自己調度。
在一個線程中會有很多函數,我們把這些函數稱為子程序,在子程序執行過程中可以中斷去執行別的子程序,而別的子程序也可以中斷回來繼續執行之前的子程序,這個過程就稱為協程。
也就是說在同一線程內一段代碼在執行過程中會中斷然后跳轉執行別的代碼,接著在之前中斷的地方繼續開始執行。
協程的優點:
(1)無需線程上下文切換的開銷,協程避免了無意義的調度,由此可以提高性能(但也因此,程序員必須自己承擔調度的責任,同時,協程也失去了標準線程使用多CPU的能力);
(2)無需原子操作鎖定及同步的開銷;
(3)方便切換控制流,簡化編程模型;
(4)高并發+高擴展性+低成本:一個CPU支持上萬的協程都不是問題。所以很適合用于高并發處理。
協程的缺點:
(1)無法利用多核資源:協程的本質是個單線程,它不能同時將單個CPU的多個核用上,協程需要和進程配合才能運行在多CPU上。當然我們日常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。
(2)進行阻塞(Blocking)操作(如IO時)會阻塞掉整個程序。