1. 程式人生 > >線程和進程復習

線程和進程復習

多線程操作 內存地址 輪換 acc 線程模型 cpu yield threading 文件的

一 背景知識
進程的概念起源於操作系統,是操作系統最核心的概念。

進程是對正在運行程序的一個抽象,操作系統的其他所有內容都是圍繞進程的概念展開的。所以想要真正了解進程,必須事先了解操作系統,點擊進入

進程是操作系統提供的最古老也是最重要的抽象概念之一。即使可以利用的cpu只有一個(早期的計算機確實如此),也能保證支持(偽)並發的能力。將一個單獨的cpu變成多個虛擬的cpu(多道技術:時間多路復用和空間多路復用 + 硬件上支持隔離),沒有進程的抽象,現代計算機將不復存在。

本文將將著重介紹進程以及它的親戚->線程
二 進程
2.1 什麽是進程
進程:正在進行的一個過程或者說一個任務。而負責執行任務則是cpu。

舉例:

egon在一個時間段內有很多任務要做:python備課的任務,寫書的任務,交女朋友的任務,王者榮耀上分的任務,  

但egon同一時刻只能做一個任務(cpu同一時間只能幹一個活),如何才能玩出多個任務並發執行的效果?

egon備一會課,再去跟李傑的女朋友聊聊天,再去打一會王者榮耀....這就保證了每個任務都在進行中,這就是進程的執行與切換

2.2 進程與程序的區別
程序僅僅只是一堆代碼而已,而進程指的是程序的運行過程。

  舉例:

想象一位有一手好廚藝的計算機科學家egon正在為他的女兒元昊烘制生日蛋糕。

他有做生日蛋糕的食譜,

廚房裏有所需的原料: 面粉、雞蛋、韭菜,蒜泥等。

在這個比喻中:

做蛋糕的食譜就是程序(即用適當形式描述的算法)

計算機科學家就是處理器(cpu)

而做蛋糕的各種原料就是輸入數據。

進程就是廚師閱讀食譜、取來各種原料以及烘制蛋糕等一系列動作的總和。



現在假設計算機科學家egon的兒子alex哭著跑了進來,說:XXXXXXXXXXXXXX。

科學家egon想了想,處理兒子alex蟄傷的任務比給女兒元昊做蛋糕的任務更重要,於是

計算機科學家就記錄下他照著食譜做到哪兒了(保存進程的當前狀態),然後拿出一本急救手冊,按照其中的指示處理蟄傷。這裏,我們看到處理機從一個進程(做蛋糕)
切換到另一個高優先級的進程(實施醫療救治),每個進程擁有各自的程序(食譜和急救手冊)。當蜜蜂蟄傷處理完之後,這位計算機科學家又回來做蛋糕,從他
離開時的那一步繼續做下去。

  需要強調的是:同一個程序執行兩次,那也是兩個進程,比如打開暴風影音,雖然都是同一個軟件,但是一個可以播放蒼井空,一個可以播放飯島愛。
2.3 並發與並行
無論是並行還是並發,在用戶看來都是‘同時‘運行的,不管是進程還是線程,都只是一個任務而已,真是幹活的是cpu,cpu來做這些任務,而一個cpu同一時刻只能執行一個任務

  並行:同時運行,只有具備多個cpu才能實現並行

並發:是偽並行,即看起來是同時運行。單個cpu+多道技術就可以實現並發,(並行也屬於並發)

單cpu,多進程,並發的概念(例一):

你是一個cpu,你同時談了三個女朋友,每一個都可以是一個戀愛任務,你被這三個任務共享
要玩出並發戀愛的效果,
應該是你先跟女友1去看電影,看了一會說:不好,我要拉肚子,然後跑去跟第二個女友吃飯,吃了一會說:那啥,我
去趟洗手間,然後跑去跟女友3開了個房

單cpu,多進程,並發的概念(例二
某天下午,egon,yuanhao,wupeiqi,alex約好了一起去嫖娼,但娼只有一個,cpu只有一個,但是卻要‘同時’幹
四個任務(嫖出並發的效果),那就必須是幹一會egon,再幹一會yuanhao,再幹一會wupeiqi,再幹一會alex
egon:花了200塊錢,因為人美活好
yuanhao:500塊錢
wupeiqi:100塊錢,可能是不太行
alex:沒要錢,為啥???因為大家剛剛嫖的是他女朋友

所有現代計算機經常會在同一時間做很多件事,一個用戶的PC(無論是單cpu還是多cpu),都可以同時運行多個任務(一個任務可以理解為一個進程)。

    啟動一個進程來殺毒(360軟件)

    啟動一個進程來看電影(暴風影音)

    啟動一個進程來聊天(騰訊QQ)

  所有的這些進程都需被管理,於是一個支持多進程的多道程序系統是至關重要的

多道技術:內存中同時存入多道(多個)程序,cpu從一個進程快速切換到另外一個,使每個進程各自運行幾十或幾百毫秒,這樣,雖然在某一個瞬間,一個cpu只能執行一個任務,但在1秒內,cpu卻可以運行多個進程,這就給人產生了並行的錯覺,即偽並發,以此來區分多處理器操作系統的真正硬件並行(多個cpu共享同一個物理內存)

2.4 同步與異步
同步就是指一個進程在執行某個請求的時候,若該請求需要一段時間才能返回信息,那麽這個進程將會一直等待下去,直到收到返回信息才繼續執行下去;

異步是指進程不需要一直等下去,而是繼續執行下面的操作,不管其他進程的狀態。當有消息返回時系統會通知進程進行處理,這樣可以提高執行的效率。

舉個例子,打電話時就是同步通信,發短息時就是異步通信。

2.5 進程的創建
但凡是硬件,都需要有操作系統去管理,只要有操作系統,就有進程的概念,就需要有創建進程的方式,一些操作系統只為一個應用程序設計,比如微波爐中的控制器,一旦啟動微波爐,所有的進程都已經存在。

  而對於通用系統(跑很多應用程序),需要有系統運行過程中創建或撤銷進程的能力,主要分為4中形式創建新的進程

  1.系統初始化(查看進程linux中用ps命令,windows中用任務管理器,前臺進程負責與用戶交互,後臺運行的進程與用戶無關,運行在後臺並且只在需要時才喚醒的進程,稱為守護進程,如電子郵件、web頁面、新聞、打印)

  2.一個進程在運行過程中開啟了子進程(如nginx開啟多進程,os.fork, subprocess.Popen等)

  3.用戶的交互式請求,而創建一個新進程(如用戶雙擊暴風影音)

  4.一個批處理作業的初始化(只在大型機的批處理系統中應用)

  

  無論哪一種,新進程的創建都是由一個已經存在的進程執行了一個用於創建進程的系統調用而創建的:

  1.在UNIX中該系統調用是:fork,fork會創建一個與父進程一模一樣的副本,二者有相同的存儲映像、同樣的環境字符串和同樣的打開文件(在shell解釋器進程中,執行一個命令就會創建一個子進程)

  2.在windows中該系統調用是:CreateProcess,CreateProcess既處理進程的創建,也負責把正確的程序裝入新進程。

 

  關於創建的子進程,UNIX和windows

  1.相同的是:進程創建後,父進程和子進程有各自不同的地址空間(多道技術要求物理層面實現進程之間內存的隔離),任何一個進程的在其地址空間中的修改都不會影響到另外一個進程。

  2.不同的是:在UNIX中,子進程的初始地址空間是父進程的一個副本,提示:子進程和父進程是可以有只讀的共享內存區的。但是對於windows系統來說,從一開始父進程與子進程的地址空間就是不同的。

2.6 進程的終止
  1. 正常退出(自願,如用戶點擊交互式頁面的叉號,或程序執行完畢調用發起系統調用正常退出,在linux中用exit,在windows中用ExitProcess)

  2. 出錯退出(自願,python a.py中a.py不存在)

  3. 嚴重錯誤(非自願,執行非法指令,如引用不存在的內存,1/0等,可以捕捉異常,try...except...)

  4. 被其他進程殺死(非自願,如kill -9)

2.7 進程的層次結構
無論UNIX還是windows,進程只有一個父進程,不同的是:

  1. 在UNIX中所有的進程,都是以init進程為根,組成樹形結構。父子進程共同組成一個進程組,這樣,當從鍵盤發出一個信號時,該信號被送給當前與鍵盤相關的進程組中的所有成員。

  2. 在windows中,沒有進程層次的概念,所有的進程都是地位相同的,唯一類似於進程層次的暗示,是在創建進程時,父進程得到一個特別的令牌(稱為句柄),該句柄可以用來控制子進程,但是父進程有權把該句柄傳給其他子進程,這樣就沒有層次了。

2.8 進程的狀態
tail -f access.log |grep ‘404‘

  執行程序tail,開啟一個子進程,執行程序grep,開啟另外一個子進程,兩個進程之間基於管道‘|‘通訊,將tail的結果作為grep的輸入。

  進程grep在等待輸入(即I/O)時的狀態稱為阻塞,此時grep命令都無法運行

  其實在兩種情況下會導致一個進程在邏輯上不能運行,

  1. 進程掛起是自身原因,遇到I/O阻塞,便要讓出CPU讓其他進程去執行,這樣保證CPU一直在工作

  2. 與進程無關,是操作系統層面,可能會因為一個進程占用時間過多,或者優先級等原因,而調用其他的進程去使用CPU。

  因而一個進程由三種狀態

2.9 進程並發的實現
進程並發的實現在於,硬件中斷一個正在運行的進程,把此時進程運行的所有狀態保存下來,為此,操作系統維護一張表格,即進程表(process table),每個進程占用一個進程表項(這些表項也稱為進程控制塊)
該表存放了進程狀態的重要信息:程序計數器、堆棧指針、內存分配狀況、所有打開文件的狀態、帳號和調度信息,以及其他在進程由運行態轉為就緒態或阻塞態時,必須保存的信息,從而保證該進程在再次啟動時,就像從未被中斷過一樣。

三 線程
在傳統操作系統中,每個進程有一個地址空間,而且默認就有一個控制線程

  多線程(即多個控制線程)的概念是,在一個進程中存在多個控制線程,多個控制線程共享該進程的地址空間



  進程只是用來把資源集中到一起(進程只是一個資源單位,或者說資源集合),而線程才是cpu上的執行單位,

例如,北京地鐵與上海地鐵是不同的進程,而北京地鐵裏的13號線是一個線程,北京地鐵所有的線路共享北京地鐵所有的資源,比如所有的乘客可以被所有線路拉。

3.1 什麽是線程
在傳統操作系統中,每個進程有一個地址空間,而且默認就有一個控制線程

  多線程(即多個控制線程)的概念是,在一個進程中存在多個控制線程,多個控制線程共享該進程的地址空間



  進程只是用來把資源集中到一起(進程只是一個資源單位,或者說資源集合),而線程才是cpu上的執行單位,

例如,北京地鐵與上海地鐵是不同的進程,而北京地鐵裏的13號線是一個線程,北京地鐵所有的線路共享北京地鐵所有的資源,比如所有的乘客可以被所有線路拉。

3.2 為何要用多線程
多線程指的是,在一個進程中開啟多個線程,簡單的講:如果多個任務公用一塊地址空間,那麽必須在一個進程內開啟多個線程。詳細的講分為4點:

   1. 多線程共享一個進程的地址空間

2. 線程比進程更輕量級,線程比進程更容易創建可撤銷,在許多操作系統中,創建一個線程比創建一個進程要快10-100倍,在有大量線程需要動態和快速修改時,這一特性很有用

3. 對於計算/cpu密集型應用,多線程並不能提升性能,但對於I/O密集型應用,使用多線程會明顯地提升速度(I/O密集型根本用不上多核優勢)

4. 在多cpu系統中,為了最大限度的利用多核,可以開啟多個線程(比開進程開銷要小的多)

3.3 多線程的應用舉例
開啟一個字處理軟件進程,該進程肯定需要辦不止一件事情,比如監聽鍵盤輸入,處理文字,定時自動將文字保存到硬盤,這三個任務操作的都是同一塊數據,因而不能用多進程。只能在一個進程裏並發地開啟三個線程,如果是單線程,那就只能是,鍵盤輸入時,不能處理文字和自動保存,自動保存時又不能輸入和處理文字。
3.5 經典的線程模型
多個線程共享同一個進程的地址空間中的資源,是對一臺計算機上多個進程的模擬,有時也稱線程為輕量級的進程

  而對一臺計算機上多個進程,則共享物理內存、磁盤、打印機等其他物理資源。

  多線程的運行也多進程的運行類似,是cpu在多個線程之間的快速切換。
不同的進程之間是充滿敵意的,彼此是搶占、競爭cpu的關系,如果迅雷會和QQ搶資源。而同一個進程是由一個程序員的程序創建,所以同一進程內的線程是合作關系,一個線程可以訪問另外一個線程的內存地址,大家都是共享的,一個線程幹死了另外一個線程的內存,那純屬程序員腦子有問題。

  類似於進程,每個線程也有自己的堆棧

不同的進程之間是充滿敵意的,彼此是搶占、競爭cpu的關系,如果迅雷會和QQ搶資源。而同一個進程是由一個程序員的程序創建,所以同一進程內的線程是合作關系,一個線程可以訪問另外一個線程的內存地址,大家都是共享的,一個線程幹死了另外一個線程的內存,那純屬程序員腦子有問題。

  類似於進程,每個線程也有自己的堆棧
不同於進程,線程庫無法利用時鐘中斷強制線程讓出CPU,可以調用thread_yield運行線程自動放棄cpu,讓另外一個線程運行。

  

  線程通常是有益的,但是帶來了不小程序設計難度,線程的問題是:

  1. 父進程有多個線程,那麽開啟的子線程是否需要同樣多的線程

   如果是,那麽附近中某個線程被阻塞,那麽copy到子進程後,copy版的線程也要被阻塞嗎,想一想nginx的多線程模式接收用戶連接。

  2. 在同一個進程中,如果一個線程關閉了問題,而另外一個線程正準備往該文件內寫內容呢?

如果一個線程註意到沒有內存了,並開始分配更多的內存,在工作一半時,發生線程切換,新的線程也發現內存不夠用了,又開始分配更多的內存,這樣內存就被分配了多次,這些問題都是多線程編程的典型問題,需要仔細思考和設計。
3.6 POSIX線程
為了實現可移植的線程程序,IEEE在IEEE標準1003.1c中定義了線程標準,它定義的線程包叫Pthread。大部分UNIX系統都支持該標準,簡單介紹如下

3.7 在用戶空間實現的線程
線程的實現可以分為兩類:用戶級線程(User-Level Thread)和內核線線程(Kernel-Level Thread),後者又稱為內核支持的線程或輕量級進程。在多線程操作系統中,各個系統的實現方式並不相同,在有的系統中實現了用戶級線程,有的系統中實現了內核級線程。

用戶級線程內核的切換由用戶態程序自己控制內核切換,不需要內核幹涉,少了進出內核態的消耗,但不能很好的利用多核Cpu,目前Linux pthread大體是這麽做的。
在用戶空間模擬操作系統對進程的調度,來調用一個進程中的線程,每個進程中都會有一個運行時系統,用來調度線程。此時當該進程獲取cpu時,進程內再調度出一個線程去執行,同一時刻只有一個線程執行。

3.8 在內核空間實現的線程
內核級線程:切換由內核控制,當線程進行切換的時候,由用戶態轉化為內核態。切換完畢要從內核態返回用戶態;可以很好的利用smp,即利用多核cpu。windows線程就是這樣的
3.9 用戶級與內核級線程的對比
一: 以下是用戶級線程和內核級線程的區別:

內核支持線程是OS內核可感知的,而用戶級線程是OS內核不可感知的。
用戶級線程的創建、撤消和調度不需要OS內核的支持,是在語言(如Java)這一級處理的;而內核支持線程的創建、撤消和調度都需OS內核提供支持,而且與進程的創建、撤消和調度大體是相同的。
用戶級線程執行系統調用指令時將導致其所屬進程被中斷,而內核支持線程執行系統調用指令時,只導致該線程被中斷。
在只有用戶級線程的系統內,CPU調度還是以進程為單位,處於運行狀態的進程中的多個線程,由用戶程序控制線程的輪換運行;在有內核支持線程的系統內,CPU調度則以線程為單位,由OS的線程調度程序負責線程的調度。
用戶級線程的程序實體是運行在用戶態下的程序,而內核支持線程的程序實體則是可以運行在任何狀態下的程序。
二: 內核線程的優缺點

  優點:

當有多個處理機時,一個進程的多個線程可以同時執行。
  缺點:

由內核進行調度。
三: 用戶進程的優缺點

  優點:

線程的調度不需要內核直接參與,控制簡單。
可以在不支持線程的操作系統中實現。
創建和銷毀線程、線程切換代價等線程管理的代價比內核線程少得多。
允許每個進程定制自己的調度算法,線程管理比較靈活。
線程能夠利用的表空間和堆棧空間比內核級線程多。
同一進程中只能同時有一個線程在運行,如果有一個線程使用了系統調用而阻塞,那麽整個進程都會被掛起。另外,頁面失效也會產生同樣的問題。
  缺點:

資源調度按照進程進行,多個處理機下,同一個進程中的線程只能在同一個處理機下分時復用

3.10 混合實現
用戶級與內核級的多路復用,內核同一調度內核線程,每個內核線程對應n個用戶線程
四 python並發編程之多進程
4.1 multiprocessing模塊介紹
python中的多線程無法利用多核優勢,如果想要充分地使用多核CPU的資源(os.cpu_count()查看),在python中大部分情況需要使用多進程。Python提供了非常好用的多進程包multiprocessing。
multiprocessing模塊用來開啟子進程,並在子進程中執行我們定制的任務(比如函數),該模塊與多線程模塊threading的編程接口類似。

  multiprocessing模塊的功能眾多:支持子進程、通信和共享數據、執行不同形式的同步,提供了Process、Queue、Pipe、Lock等組件。

需要再次強調的一點是:與線程不同,進程沒有任何共享狀態,進程修改的數據,改動僅限於該進程內。
4.2 Process類的介紹
創建進程的類:
Process([group [, target [, name [, args [, kwargs]]]]]),由該類實例化得到的對象,表示一個子進程中的任務(尚未啟動)

強調:
1. 需要使用關鍵字的方式來指定參數
2. args指定的為傳給target函數的位置參數,是一個元組形式,必須有逗號
參數介紹:
group參數未使用,值始終為None

target表示調用對象,即子進程要執行的任務

args表示調用對象的位置參數元組,args=(1,2,‘egon‘,)

kwargs表示調用對象的字典,kwargs={‘name‘:‘egon‘,‘age‘:18}

name為子進程的名稱

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

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

p.join([timeout]):主線程等待p終止(強調:是主線程處於等的狀態,而p是處於運行的狀態)。timeout是可選的超時時間,需要強調的是,p.join只能join住start開啟的進程,而不能join住run開啟的進程
屬性介紹:
p.daemon:默認值為False,如果設為True,代表p為後臺運行的守護進程,當p的父進程終止時,p也隨之終止,並且設定為True後,p不能創建自己的新進程,必須在p.start()之前設置

p.name:進程的名稱

p.pid:進程的pid

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

p.authkey:進程的身份驗證鍵,默認是由os.urandom()隨機生成的32字符的字符串。這個鍵的用途是為涉及網絡連接的底層進程間通信提供安全性,這類連接只有在具有相同的身份驗證鍵時才能成功(了解即可)

4.3 Process類的使用
=====================part1:創建並開啟子進程的兩種方式

註意:在windows中Process()必須放到# if __name__ == ‘__main__‘:下

Since Windows has no fork, the multiprocessing module starts a new Python process and imports the calling module.
If Process() gets called upon import, then this sets off an infinite succession of new processes (or until your machine runs out of resources).
This is the reason for hiding calls to Process() inside

# if __name__ == "__main__"
# since statements inside this if-statement will not get called upon import.

由於Windows沒有fork,多處理模塊啟動一個新的Python進程並導入調用模塊。
如果在導入時調用Process(),那麽這將啟動無限繼承的新進程(或直到機器耗盡資源)。
這是隱藏對Process()內部調用的原,使用if __name__ == “__main __”,這個if語句中的語句將不會在導入時被調用。

線程和進程復習