線上預覽:http://github.lesschina.com/python/base/concurrency/1.并發程式設計~程序先導篇.html
Python3 與 C# 并發程式設計之~ 程序篇:https://www.cnblogs.com/dotnetcrazy/p/9426279.html
先寫幾個問号來概況下今天準備說的内容:(謎底自己解開,文中都有)
你知道<code>Ctrl+C</code>終止程序的本質嗎?你知道<code>Kill -9 pid</code>的真正含義嗎?
你知道那些跨平台架構(Python,NetCore)在Linux下建立程序幹了啥?
你了解<code>僵屍程序</code>和<code>孤兒程序</code>的悲催生産史嗎?<code>孤兒找幹爹</code>,<code>僵屍送往生</code>想知道不?
想知道建立子程序後怎麼<code>李代桃僵</code>嗎?<code>ps aux | grep xxx</code>的背後到底隐藏了什麼?
你了解Linux磁盤中p類型的檔案到底是個啥嗎?
為什麼要realase釋出而不用debug直接部署?這背後的性能相差幾何?
還有更多程序間的<code>密密私語</code>等着你來檢視哦~
關于幫助文檔的說明:
所有用到的系統函數你都可以使用<code>man</code>檢視,eg:<code>man 2 pipe</code>
Python裡面的方法你都可以通過<code>help</code>檢視,eg:<code>help(os.pipe)</code>
正式講課之前,先說些基本概念,難了解的用程式跑跑然後再了解:如有錯誤歡迎批評指正
并發 :一個時間段中有幾個程式都處于已啟動運作到運作完畢之間,且這幾個程式都是在同一個處理機上運作,但任一個時刻點上隻有一個程式在處理機上運作。
并行 :當一個CPU執行一個線程時,另一個CPU可以執行另一個線程,兩個線程互不搶占CPU資源,可以同時進行,這種方式我們稱之為并行(Parallel)。(在同一個時間段内,兩個或多個程式執行,有時間上的重疊)
通俗的舉個例子:
小明、小潘、小張、小康去食堂打飯,4個小夥子Coding了3天,餓爆了,現在需要1分鐘内讓他們都吃上飯,不然就有可怕的事情發生。
按照正常的流程,1分鐘可能隻夠他們一個人打飯,這不行啊,于是有了幾種處理方法:
并發:快快快,一人先吃一口,輪着來,一直喂到你們都飽了(隻有一個食堂打飯的視窗)(單核CPU)
并行:
開了2~3個視窗,和上面處理一樣,隻是競争的強度沒那麼大了
開了4個視窗,不着急,一人一個視窗妥妥的
對于作業系統來說,一個任務就是一個程序(Process),比如打開一個浏覽器就是啟動一個浏覽器程序,打開兩個浏覽器就啟動了兩個浏覽器程序。
有些程序還不止同時幹一件事,比如Word,它可以同時進行打字、拼寫檢查、列印等事情。在一個程序内部,要同時幹多件事,就需要同時運作多個“子任務”,我們把程序内的這些“子任務”稱為線程(Thread)。
由于每個程序至少要幹一件事,是以,一個程序至少有一個線程。像Word這種複雜的程序可以有多個線程,多個線程可以同時執行,多線程的執行方式和多程序是一樣的,也是由作業系統在多個線程之間快速切換,讓每個線程都短暫地交替運作,看起來就像同時執行一樣。
通俗講:線程是最小的執行單元,而程序由至少一個線程組成。<code>如何排程程序和線程,完全由作業系統決定,程式自己不能決定什麼時候執行,執行多長時間</code>
PS:程序5态下次正式講程式的時候會說,然後就是==> 程式實戰不會像今天這樣繁瑣的,Code很簡單,但是不懂這些基本概念往後會吃很多虧,逆天遇到太多坑了,是以避免大家入坑,簡單說說這些概念和一些偏底層的東西~看不懂沒事,有個印象即可,以後遇到問題至少知道從哪個方向去解決
示例代碼:https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/Linux
示例代碼:https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/Linux/base
(linux/unix)作業系統提供了一個<code>fork()</code>系統調用。普通的函數調用,調用一次,傳回一次,但是<code>fork()</code>一次調用,兩次傳回。
因為作業系統自動把父程序複制了一份,分别在父程序和子程序内傳回。為了便于區分,作業系統是這樣做的:子程序永遠傳回0,而父程序傳回子程序的ID
檢視下幫助文檔:
我們來跑個程式驗證一下:(PID傳回值如果小于0一般都是出錯了)
結果:
可以檢視下這個程序是啥:

這個指令如果還不熟悉,Linux基礎得好好複習下了:https://www.cnblogs.com/dunitian/p/4822807.html,簡單分析下吧:a是檢視所有(可以聯想ls -a),u是顯示詳細資訊,x是把不依賴終端的程序也顯示出來(終端可以了解為:人與機器互動的那些)
技巧:指令學習可以遞增式學習:<code>ps</code>,<code>ps a</code> <code>ps au</code> <code>ps aux</code> <code>ps ajx</code>
現在驗證一下複制一份是什麼意思:(代碼原封不動,隻是在最下面添加了一行輸出)
輸出:(父子程序的執行順序是系統排程決定的)
的确是Copy了一份,Code都一樣(玩過逆向的應該知道,這份Code其實就放在了<code>.text</code>(代碼段)裡面)
子程序被父程序fork出來後,在fork處往下執行(Code和父程序一樣),這時候他們就為了搶CPU各自為戰了
最後驗證一下:各個程序位址空間中資料是完全獨立的(有血緣關系的則是:讀時共享,寫時複制,比如父子程序等)
輸出:(程序間通信下一節課會系統的講,今天隻談Linux和概念)
擴充:(簡單了解下即可)
程式:二進制檔案(占用磁盤)
程序:啟動的程式(所有資料都在記憶體中,需要占用CPU、記憶體等資源)
程序是CPU、記憶體、I/0裝置的抽象(各個程序位址空間中資料是完全獨立的)
0号程序是Linux核心程序(這麼了解:初代吸血鬼)
1号程序是使用者程序,所有程序的建立或多或少和它有關系(<code>init</code> or <code>systemd</code>)
2号程序和1号程序一樣,都是0号程序建立的,所有線程排程都和他有關系
先看看Linux啟動的圖示:(圖檔來自網絡)
檢視一下init程序
CentOS進行了優化管理~<code>systemd</code>
其實程式開機啟動方式也可以知道差別了:<code>systemctl start mongodb.service</code> and <code>sudo /etc/init.d/ssh start</code>
Win系列的0号程序:
第5點的說明:(以遠端CentOS伺服器為例) pstree -ps
再看一個例子:
其實你可以在虛拟機試試幹死1号程序,就到了登入頁面了【現在大部分系統都不讓你這麼幹了】 kill -9 1
先看看定義:
孤兒程序 :一個父程序退出,而它的一個或多個子程序還在運作,那麼那些子程序将成為孤兒程序。孤兒程序将被init程序(程序号為1)所收養,并由init程序對它們完成狀态收集工作。
僵屍程序 :一個程序使用fork建立子程序,如果子程序退出,而父程序并沒有調用wait或waitpid擷取子程序的狀态資訊,那麼子程序的程序描述符仍然儲存在系統中。這種程序稱之為僵死程序。
通俗講就是:
孤兒程序:你爸在你之前死了,你成了孤兒,然後你被程序1收養,你死後的事宜你幹爹幫你解決
僵屍程序:你挂了,你爸忙着幹其他事情沒有幫你安葬,你變成了孤魂野鬼,你的怨念一直長存世間
舉個例子看看:
輸出:
輸出+測試:
其實僵屍程序的危害真的很大,這也就是為什麼有些人為了追求效率過度調用底層,不考慮自己實際情況最後發現還不如用自托管的效率高
僵屍程序是殺不死的,必須殺死父類才能徹底解決它們,下面說說怎麼讓父程序為子程序‘收屍’
講解之前先簡單分析一下上面的Linux指令(防止有人不太清楚)
<code>kill -9 pid</code> ==> 以前逆天說過,是無條件殺死程序,其實這種說法不準确,應該是發信号給某個程序
-9指的就是信号道裡面的<code>SIGKILL</code>(信号終止),你寫成<code>kill -SIGKILL pid</code>也一樣
-9隻是系統給的一種簡化寫法(好像記得1~31信号,各個Linux中都差不多,其他的有點不一樣)
一般搜尋程序中的某個程式一般都是用這個:<code>ps -aux | grep xxx</code> (<code>|</code>其實就是管道,用于有血緣關系程序間通信,等會講)
如果安裝了<code>pstree</code>就更友善了:<code>pstree 13570 -ps</code> (Ubuntu自帶,CentOS要裝下<code>yum install psmisc</code>)
擴充:我們平時<code>Ctrl+C</code>其實就是給 2)<code>SIGINT</code>發一個信号
代碼執行個體:https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/Linux/wait
步入正題:
Python的Wait和C系列的稍有不同,這邊重點說說Python:
<code>os.wait()</code>傳回一個元組,第一個是程序id,第二個是狀态,正常退出是0,被九号信号幹死就傳回9
來個案例:
示範一下被9号信号幹死的情況:
擴充:(回收所有子程序,status傳回-1代表沒有子程序了,Python裡面沒有子程序會觸發異常)
示範:看最後一句輸出,父程序掃尾工作做完就over了
代碼執行個體:https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/Linux/waitpid
上面說的<code>wait</code>方法是阻塞程序的一種方式,<code>waitpid</code>可以設定不阻塞程序
等待程序id為pid的程序結束,傳回一個tuple,包括程序的程序ID和退出資訊(和os.wait()一樣),參數options會影響該函數的行為。在預設情況下,options的值為0。
如果pid是一個正數,waitpid()請求擷取一個pid指定的程序的退出資訊
如果pid為0,則等待并擷取目前程序組中的任何子程序的值
如果pid為-1,則等待目前程序的任何子程序
如果pid小于-1,則擷取程序組id為pid的絕對值的任何一個程序
當系統調用傳回-1時,抛出一個OSError異常。
官方原話是這樣的:(英語好的可以看看我有沒有翻譯錯)
options:(宏)
補充:
程序組:每一個程序都屬于一個“程序組”,當一個程序被建立的時候,它預設是其父程序所在組的成員(你們一家)
會 話:幾個程序組又構成一個會話(你們小區)
用法和<code>wait</code>差不多,就是多了一個不阻塞線程的方法:
代碼執行個體:https://github.com/lotapp/BaseCode/blob/master/python/5.concurrent/Linux/wait3.py
這個是Python擴充的方法,用法和<code>wait、waitpid</code>差不多,我就不一個個的舉例子了,以<code>wait3</code>為例
輸出
代碼執行個體:https://github.com/lotapp/BaseCode/blob/master/python/5.concurrent/Linux/execl.py
之前有說fork後,相當于copy了一份,.text裡面放的是代碼段,如果想要調用另一個程式,可以使用<code>execlxxx</code>,他會把.text裡面的代碼替換掉
來看個例子,<code>os.execl("絕對路徑","參數或者指令")</code> or <code>os.execlp("Path中包含的指令","參數或者指令")</code>
提示:檢視指令路徑:eg:<code>which ls</code>
注意輸出資訊:<code>os.execlp("ls", "ls", "-al")</code>
代碼執行個體:https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/Linux/程序通信/1.file
講管道之前,先說個簡單的:通過檔案進行通信
來一個簡單讀寫的案例先适應下檔案操作:
In [1]:
來個簡單的案例:
圖示:
代碼執行個體:https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/Linux/程序通信/2.Queue
執行個體化對象幫助文檔:
詳細内容(如:非阻塞、池中應用等)下次講代碼的時候會詳說,簡單看個例子:
輸出:
代碼執行個體:https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/Linux/程序通信/3.pipe
知識普及:
指令模式下預設有六個終端tty1-tty6
tty7代表圖形登入
遠端登入會顯示pts/0,1,2...
如果終端的概念還不清楚可以看之前的文章:https://www.cnblogs.com/dunitian/p/6658273.html
匿名管道的模式其實我們平時都在用,隻是不知道而已,比如:<code>ps aux | grep "python"</code> 這個 <code>|</code> 就是匿名管道
本質:核心的緩沖區,不占用磁盤空間(可以看成僞檔案)【預設4k,相差不大的情況下系統會自動微調大小】
我們來看一下:<code>ulimit -a</code>
Ubuntu 18.04
CentOS 7.5
原理:算法實作的環形隊列(隊列:先進先出)
特點:
操作管道的程序被銷毀後,管道自動被釋放
管道預設是阻塞(讀、寫兩端都阻塞)
管道有兩個端,一個是讀端(read_fd,一般都為3),一個是寫端(write_fd,一般都為4)
單向傳輸
4的意思是這樣的(網上找個圖,然後改造一下)
驗證一下3:
為什麼是3開始呢?檢視一下源碼:(https://github.com/python/cpython/blob/v3.7.0/Lib/pty.py)
畫個大綱圖了解一下:(讀的時候關閉寫,寫的時候關閉讀)
結合單向傳輸了解一下:(父子隻能一個人寫,另一個人隻能讀)
簡單概況上圖:子程序隻讀,父程序隻寫 or 子程序隻寫,父程序隻讀 (如果想要互相讀寫通信~兩根管道走起)
簡單分析一下 <code>ps aux | grep python</code> ,本來ps aux是準備在終端中輸出的,現在寫入核心緩沖區了,grep從核心緩沖區裡面讀取,把符合條件的輸出到終端
終端檔案描述擷取:
我們用程式實作一個同樣效果的:(<code>grep</code>有顔色,其實就是加了<code>--color=auto</code>)
輸出:(用到的函數:<code>os.pipe()</code> and <code>os.dup2(oldfd,newfd)</code>)
PS:在C系列裡面如果你該關閉的fd沒關,會資源浪費,python好像做了處理,沒能夠問題複現,是以還是建議父子一方隻讀,一方隻寫
概念再了解:fork了兩個子程序,則檔案描述符被複制了2份,大家檔案描述符的3、4都指向了<code>pipe管道</code>的<code>read_fd</code>和<code>write_fd</code>
來張圖了解一下,哪些fd被close了(如果讓子程序之間通信,父程序因為不讀不寫,是以讀寫都得關閉)
代碼示範:(這次注釋很全)
上面知識點忘了可以複習一下:https://www.cnblogs.com/dotnetcrazy/p/9278573.html#1.2.字元串和編碼
用到的函數:(這個就不需要使用<code>dup2</code>來重定向到終端了【有血緣關系的程序之間通信,并不依賴于終端顯示】)
<code>os.write(fd, str)</code>寫入字元串到檔案描述符 fd中. 傳回實際寫入的字元串長度
<code>os.read(fd, n)</code>從檔案描述符 fd 中讀取最多 n 個位元組,傳回包含讀取位元組的字元串
如果檔案描述符fd對應檔案已達到結尾, 傳回一個空字元串
舉個父子間通信的例子(比C系列簡單太多)【下次講的通用Code會更簡單】
輸出結果:
隊列的<code>get</code>,<code>put</code>方法預設也是阻塞的,如果想非阻塞可以調用<code>get_nowait</code>和<code>put_nowait</code>來變成非阻塞,那pipe管道呢?
C系列一般使用<code>fcntl</code>來實作,Python進行了封裝,我們可以通過<code>os.pipe2(os.O_NONBLOCK)</code>來設定非阻塞管道
舉個例子:
擴充:
資料隻能讀1次(隊列和棧都這樣)
匿名管道必須有血緣關系的程序才能通信
半雙工通信:同一時刻裡,資訊隻能有一個傳輸方向(類似于對講機)
單工通信:消息隻能單方向傳輸(類似于遙控器)
半雙工通信:類似于對講機
雙工通信:類似于電話
代碼執行個體:https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/Linux/程序通信/4.fifo
FIFO管道:
有名管道,除了血緣關系程序通信,沒有血緣關系的程序也可以通信
在磁盤上有會存放一個檔案類型為<code>p</code>,大小為<code>0</code>的管道檔案(僞檔案,大小始終為0)
核心中有一個對應的緩沖區(資料就放在裡面)
fifo要求讀寫雙方必須同時打開才可以繼續進行讀寫操作,否則打開操作會堵塞直到對方也打開
如果讀端全部關閉,管道破裂,程序自動被終止(PIPE也是這樣的)
對2的驗證: 其實你用<code>ll</code>來檢視,就是檔案類型為<code>p</code>的檔案(大小始終為0)
Linux底層提供了<code>mkfifo</code>函數,Python建立使用<code>os.mkfifo()</code>
畫個圖來看3:
<code>flags</code> -- 該參數可以是以下選項,多個使用 <code>|</code> 隔開:
os.O_RDONLY: 以隻讀的方式打開
os.O_WRONLY: 以隻寫的方式打開
os.O_RDWR : 以讀寫的方式打開
os.O_NONBLOCK: 打開時不阻塞
os.O_APPEND: 以追加的方式打開
os.O_CREAT: 建立并打開一個新檔案
os.O_TRUNC: 打開一個檔案并截斷它的長度為零(必須有寫權限)
os.O_EXCL: 如果指定的檔案存在,傳回錯誤
os.O_SHLOCK: 自動擷取共享鎖
os.O_EXLOCK: 自動擷取獨立鎖
os.O_DIRECT: 消除或減少緩存效果
os.O_FSYNC : 同步寫入
os.O_NOFOLLOW: 不追蹤軟連結
很多人直接使用了Open方法<code>open(fifo_path, "r")</code>和<code>open(fifo_path, "w")</code>貌似也是可以的,但是不推薦
我們使用官方推薦的方法:
fifo操作非常簡單,和檔案IO操作幾乎一樣,看個無血緣關系程序通信的例子:
程序1源碼:r_fifo.py
程序2源碼:w_fifo.py
做個讀端的測試:
讀寫雙測:(fifo檔案大小始終為0,隻是僞檔案而已)
擴充一下,如果你通過終端讀寫呢?(同上)
再來個讀寫的案例
3.rw_fifo1.py
rw_fifo2.py
來個父子間通信:(代碼比較簡單,和上面差不多,看看即可)
代碼執行個體:https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/Linux/程序通信/5.mmap
好處:記憶體操作,比IO快
缺點:和檔案一樣不會像管道一樣阻塞(讀的可能不全,需要自己考慮讀寫效率)
畫個簡單的圖示:
PS:記憶體映射一個檔案并不會導緻整個檔案被讀取到記憶體中:
檔案并沒有被複制到記憶體緩存中,作業系統僅僅為檔案内容保留了一段虛拟記憶體。
當你通路檔案的不同區域時,這些區域的内容才根據需要被讀取并映射到記憶體區域中。
沒有通路的部分還是留在磁盤上
以Linux為例,簡單解析一下幫助文檔:(加粗的是必填參數)
fileno:就是我們經常說的<code>檔案描述fd</code>
可以通過<code>os.open()</code>直接打開fd
也可以調用檔案的<code>f.fileno()</code>
length:映射區大小,(一般寫0就OK了)
如果length為0,則映射的最大長度将是調用時檔案的目前大小
一般把<code>檔案大小 os.path.getsize(path)</code>傳進去就可以了
flags:映射區性質,預設是用共享
MAP_SHARED 共享(資料會自動同步磁盤)
MAP_PRIVATE 私有(不同步磁盤)
prot:映射區權限,如果指定,就會提供記憶體保護(預設即可)
PROT_READ 讀
PROT_READ | PROT_WRITE 寫(必須有讀的權限)
access:可以指定通路來代替flags和prot作為可選的關鍵字參數【這個是Python為了簡化而添加的】
ACCESS_READ:隻讀
ACCESS_WRITE:讀和寫(會影響記憶體和檔案)
ACCESS_COPY:寫時複制記憶體(影響記憶體,但不會更新基礎檔案)
ACCESS_DEFAULT:延遲到prot(3.7才添加)
offset,偏移量,預設是0(和檔案一緻)
Out[1]:
In [2]:
看個簡單的案例快速熟悉mmap子產品:(大檔案處理這塊先不說,以後要是有機會講資料分析的時候會再提)
熟悉一下上面幾個方法:
輸出:(測試了一下,切片操作【讀、寫】不會影響postion)
看下open打開的案例:
其他方法可以參考:這篇文章(Python3很多都和Python2不太相同,辯證去看吧)
注意一點:
通過MMap記憶體映射之後,程序間通信并不是對檔案操作,而是在記憶體中。檔案保持同步隻是因為mmap的flags預設設定的是共享模式(MAP_SHARED)
PS:還記得之前講類方法和執行個體方法的時候嗎?Python中類方法可以直接被對象便捷調用,這邊mmap執行個體對象中的方法,其實很多都是類方法 步入正軌:
來看一個有血緣關系的通信案例:(一般用匿名)
父程序建立了一份mmap對象,fork産生子程序的時候相當于copy了一份指向,是以可以進行直接通信(聯想fd的copy)
因為不同程序之前沒有關聯,必須以檔案為媒介(檔案描述符fd)
程序1:
程序2:
輸出圖示:
代碼執行個體:https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/Linux/程序通信/6.signal
信号:它是一種異步的通知機制,用來提醒程序一個事件已經發生。當一個信号發送給一個程序,作業系統中斷了程序正常的控制流程,此時,任何非原子操作都将被中斷。如果程序定義了信号的處理函數,那麼它将被執行,否則就執行預設的處理函數。
一般信号不太用于程序間通信,常用就是發個信号把xxx程序幹死。
先來個例子,等會講理論:
Python裡面一般用<code>os.kill(pid,signalnum)</code>來發信号:eg:<code>kill 9 pid</code>
擴充一下:
<code>signal.pthread_kill(thread_id,signal.SIGKILL)) # 殺死線程</code>
<code>os.abort() # 給自己發異常終止信号</code>
這邊開始說說理論:
信号狀态:
産生狀态
未決狀态(信号産生後沒有被處理)
遞達狀态(信号已經傳達到程序中)
産生、傳遞等都是通過核心進行的,結合上面的例子畫個圖了解下:
未決信号集:沒有被目前程序處理的信号集合(可以通過<code>signal.sigpending()</code>擷取<code>set</code>集合)
阻塞信号集:要屏蔽的信号(不能被使用者操作)
回顧一下上面說<code>kill 9 pid</code>原理的知識:<code>kill -l</code>
說下常用的幾個信号:
9号信号(<code>sigkill</code>)是<code>kill 9</code>
2号信号(<code>sigint</code>)是<code>Ctrl+C</code>終止程序
3号信号(<code>sigquit</code>)是<code>Ctrl+\</code>終止程序
現在說說信号捕捉<code>signal.signal(signalnum, handler)</code>
<code>handler處理函數</code>,除了自定義信号處理函數外也可以使用系統提供的兩種方式:
<code>SIG_IGN</code>(忽略該信号)
<code>SIG_DFL</code>(系統預設操作)
注意一點:<code>SIGSTOP</code> 和 <code>SIGKILL</code> 信号是不能被捕獲、忽略和阻塞的(這個是系統預留的,如果連預留都沒有可以想象肯定木馬橫向)
PS:信号的優先級一般都是比較高的,往往程序收到信号後都會停下手上的事情先處理信号(死循環也一樣歇菜)
來看一個例子:(處理singint,忽略sigquit)
輸出圖示:(我休息3s,在3s内給程式發送了<code>sigint</code>信号(Ctrl+C)就立馬處理了)
如果你隻是等一個信号就退出,可以使用:<code>signal.pause()</code>,不必使用死循環來輪詢了
<code>os.killpg(pgid, sid)</code>程序組結束
<code>signal.siginterrupt(signal.SIGALRM, False)</code> 防止系統調用被信号打斷所設立(其實一般也不太用,出問題才用)
通俗的講就是,要是系統和你發一樣的信号可能也就被處理了,加上這句就ok了,eg:
舉個例子,有時候有些惡意程式蓄意破壞或者被所謂的安全軟體誤殺比如系統函數<code>kill(-1)</code>【有權限的都殺了】
再說兩個定時器就進下一個話題把,這個主要就是信号捕捉用得比較多,然後就是一般都是守護程序發信号
先驗證一個概念:alarm鬧鐘不能被fork後的子程序繼承
這個你可以自己驗證:不受程序影響,每個程序隻能有一個定時器,再設定隻是重置
其實好好看逆天的問題都會發現各種小技巧的,所有小技巧自我總結一下就會産生質變了
運作一下:<code>time python3 xxx.py</code>
運作一下:<code>time python3 xxx.py > temp</code>
簡單說下三個參數:
<code>real</code>總共運作時間(real=user+sys+損耗時間)
<code>user</code>(使用者代碼真正運作時間)
<code>sys</code>(核心運作時間)【核心不運作,你系統也不正常了】
其實就是減少了IO操作,性能方面就相差幾倍!我這邊隻是一台老電腦,要是真在伺服器下性能相差可能讓你吓一跳
現在知道為什麼要realase釋出而不用debug直接部署了吧(線上項目非必要情況,一般都會删除所有日記輸出的)
<code>signal.setitimer(which, seconds, interval=0.0)</code> which參數說明:
signal.TIMER_REAL:按實際時間計時,計時到達将給程序發送SIGALRM信号
signal.ITIMER_VIRTUAL:僅當程序執行時才進行計時。計時到達将發送SIGVTALRM信号給程序。
signal.ITIMER_PROF:當程序執行時和系統為該程序執行動作時都計時。與ITIMER_VIRTUAL是一對,該定時器經常用來統計程序在使用者态和核心态花費的時間。計時到達将發送SIGPROF信号給程序。
這個一般在守護程序中經常用,看個簡單案例:
執行個體代碼:"https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/Linux/程序守護
守護程序應用場景很多,比如程式上線後有個bug被不定時的觸發,每次都導緻系統爆卡或者退出,而程式員修複bug需要時間,但是線上項目又不能挂,這時候就可以使用一個心跳檢測的守護程序(查錯也可以使用守護程序)【為惡就不說了】
正式開始前,先來個僞案例:
模拟一個漏洞百出的程式
寫個簡單版本的守護程序:
寫了個僞牌子的,現在說說正規的,看看概念的東西:
背景服務程序
脫離于控制終端(setpid)
周期性的執行某個任務|等待某個事件發生(setitimer)
不受使用者登入登出影響(關機影響,不過你可以添加啟動項)
一般使用以d結尾的服務名稱(約定俗成)
講正式流程前先複習一下上面說的<code>程序組</code>和<code>會話</code>
需要擴充幾點:
程序組:
組長:第一個程序
組長ID==程序組ID
組長挂了不影響程序組
會話:
組長不能建立會話(你都有官了,不留點門路給後人?)
建立會話的程序成為新程序組的組長(新程序組裡面就它一個嘛)
建立出新會話會丢棄原有的控制終端(到了新環境裡面,人脈得重建立立)
稍微驗證一下,然後步入正題:
驗證結果:<code>父程序ID==程序組ID</code>,<code>父程序挂了程序組依舊在</code>,順便驗證了下<code>ps -ajx</code>的參數
先看看這個SessionID是啥:
<code>ps ajx</code>的參數現在全知道了:PPID PID PGID SID (你不加grep就能看到的)
驗證一下SessionID的事情:
步入正軌:
建立守護程序的步驟:
fork子程序,父程序退出(子程序變成了孤兒)
子程序建立新會話(建立出新會話會丢棄原有的控制終端)
改變目前工作目錄【為了減少bug】(eg:你在某個檔案夾下運作,這個檔案夾被删了,多少會點受影響)
重置檔案掩碼(繼承了父程序的檔案掩碼,通過<code>umask(0)</code>重置一下,這樣可以擷取777權限)
關閉檔案描述符(既然用不到了,就關了)
自己的邏輯代碼
先簡單弄個例子實作上面步驟:
運作效果:(直接背景走起了)
如果對Linux基礎不熟,可以看看幾年前說的LinuxBase:
Linux基礎指令:http://www.cnblogs.com/dunitian/p/4822807.html
Linux系列其他文章:https://www.cnblogs.com/dunitian/p/4822808.html#linux
如果對部署運作系列不是很熟,可以看之前寫的小demo:
用Python3、NetCore、Shell分别開發一個Ubuntu版的定時提醒(附NetCore跨平台兩種釋出方式):https://www.cnblogs.com/dotnetcrazy/p/9111200.html
如果對OOP不是很熟悉可以檢視之前寫的OOP文章:
Python3 與 C# 面向對象之~封裝https://www.cnblogs.com/dotnetcrazy/p/9202988.html
Python3 與 C# 面向對象之~繼承與多态https://www.cnblogs.com/dotnetcrazy/p/9219226.html
Python3 與 C# 面向對象之~異常相關https://www.cnblogs.com/dotnetcrazy/p/9219751.html
如果基礎不牢固,可以看之前寫的PythonBase:
Python3 與 C# 基礎文法對比(Function專欄)https://www.cnblogs.com/dotnetcrazy/p/9175950.html
Python3 與 C# 擴充之~子產品專欄https://www.cnblogs.com/dotnetcrazy/p/9253087.html
Python3 與 C# 擴充之~基礎衍生https://www.cnblogs.com/dotnetcrazy/p/9278573.html
Python3 與 C# 擴充之~基礎拓展https://www.cnblogs.com/dotnetcrazy/p/9333792.html
現在正兒八經的來個簡化版的守護程序:(你可以根據需求多加點信号處理)
運作效果:(關閉檔案描述符後就不要printf了)
擴充說明,如果你要檔案描述符重定向的話可以這麼寫:
之後你printf就自動到指定的檔案了
Socket,在講基礎最後一個系列~網絡程式設計的時候會講,不急,而且程序間通信不需要這麼<code>‘重量級’</code>的
線程相關打算和代碼一起講,有機會也可以單獨拉出來說一個結尾篇
業餘拓展:
官方文檔大全
程序間通信和網絡
os - 其他作業系統接口
mmap - 記憶體映射檔案支援
signal - 設定異步事件的處理程式
Other:
作者:毒逆天
出處:https://www.cnblogs.com/dotnetcrazy
打賞:<b>18i4JpL6g54yAPAefdtgqwRrZ43YJwAV5z</b>
本文版權歸作者和部落格園共有。歡迎轉載,但必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接!