多進程並發編程


多進程實現原理-多道技術

操作系統介紹

下圖是操作系統在整個計算機中所在的位置:

image-20181228060338147

位於應用軟件和硬件設備之間,本質上也是一個軟件,

由系統內核(管理所有硬件資源)與系統接口(提供給程序員使用的接口)組成

操作系統是為方便用戶操作計算機而提供的一個運行在硬件之上的軟件

操作系統的兩個核心作用

1.為用戶屏蔽了復雜繁瑣的硬件接口,為應用程序提供了,清晰易用的系統接口

有了這些接口以后程序員不用再直接與硬件打交道了

例子:有了操作系統后我們就可以使用資源管理器來操作硬盤上的數據,而不用操心,磁頭的移動啊,數據的讀寫等等

2.操作系統將應用程序對硬件資源的競爭變成有序的使用

例子:所有軟件 qq啊 微信啊 吃雞啊都共用一套硬件設備 假設現有三個程序都在使用打印機,如果不能妥善管理競爭問題,可能一個程序打印了一半圖片后,另一個程序搶到了打印機執行權於是打印了一半文本,導致兩個程序的任務都沒能完成,操作系統的任務就是將這些無序的操作變得有序

操作系統與應用程序的區別

它們都是軟件,而操作系統可以看做一款特殊的軟件

1.操作系統是是受保護的:無法被用戶修改(應用軟件如qq不屬於操作系統可以隨便卸載)

2.大型:linux或widows源代碼都在五百萬行以上,這僅僅是內核,不包括用戶程序,如GUI,庫以及基本應用軟件(如windows Explorer等),很容易就能達到這個數量的10倍或者20倍之多

3.長壽:由於操作系統源碼量巨大,編寫是非常耗時耗力的,一旦完成,操作系統所有者便不會輕易的放棄重寫,二是在原有基礎上改進,基本上可以把windows95/98/Me看出一個操作系統

多道技術

多道技術中的多道指的是多個程序,多道技術的實現是為了解決多個程序競爭或者說共享同一個資源(比如cpu)的有序調度問題,解決方式即多路復用,多路復用分為時間上的復用和空間上的復用。

空間復用

同一時間,加載多個任務到內存中,多個進程之間內存區域需要相互隔離,這種隔離是物理層面的隔離,其目的是為了保證數據安全

時間復用

指的是,操作系統會在多個進程之間做切換執行

​ 切換任務的兩種情況 :

​ 1.當一個進程遇到了IO操作,時會自動切換

​ 2.當一個任務執行時間超過閾值,會強制切換

​ 注意:在切換前必須保存狀態,以便后續恢復執行

頻繁的切換其實也需要消耗資源,當所有任務都沒有IO操作時,切換執行效率反而降低,但是為了保證並發執行,必須犧牲效率

並發編程-多進程

什么是並發編程

並發指的是多個任務同時被執行,並發編程指的是編寫支持多任務並發的應用程序在。

之前的TCP通訊中,服務器在建立連接后需要一個循環來與客戶端循環的收發數據,但服務器並不知道客戶端什么時候會發來數據,導致沒有數時服務器進入了一個等待狀態,此時其他客戶端也無法鏈接服務器,很明顯這是不合理的,學習並發編程就是要找到一種方案,讓一個程序中的的多個任務可以同時被處理;

什么是進程

進程指的是正在運行的程序,是一系列過程的統稱,也是操作系統在調度和進行資源分配的基本單位

進程是實現並發的一種方式,在學習並發編程之前要先了解進程的基本概念以及多進程的實現原理,這就不得不提到操作系統了,因為進程這個概念來自於操作系統,沒有操作系統就沒有進程

多進程的實現原理見:
https://www.cnblogs.com/yangyuanhu/p/11112763.html

進程與程序

進程是正在運行的程序,程序是程序員編寫的一堆代碼,也就是一堆字符,當這堆代碼被系統加載到內存中並執行時,就有了進程。

例如:生活中我們會按照菜譜來做菜,那么菜譜就是程序,做菜的過程就是進程

需要注意的是:一個程序是可以產生多個進程的,就像我們可以同時運行多個QQ程序一樣,會形成多個進程

測試:

import time
while True:
    time.sleep(1)

多次運行該文件,就會產生多個python.exe進程,可以通過tasklist來查看運行的程序

PID和PPID

PID

在一個操作系統中通常都會運行多個應用程序,也就是多個進程,那么如何來區分進程呢?

系統會給每一個進程分配一個進程編號即PID,如同人需要一個身份證號來區分。

驗證:

tasklist 用於查看所有的進程信息

taskkill /f /pid pid 該命令可以用於結束指定進程

# 在python中可以使用os模塊來獲取pid
import os
print(os.getpid())

PPID

當一個進程a開啟了另一個進程b時,a稱為b的父進程,b稱為a的子進程

在python中可以通過os模塊來獲取父進程的pid

# 在python中可以使用os模塊來獲取ppid
import os
print("self",os.getpid()) # 當前進程自己的pid
print("parent",os.getppid()) # 當前進程的父進程的pid

如果是在pycharm中運行的py文件,那pycahrm就是這個python.exe的父進程,當然你可以從cmd中來運行py文件,那此時cmd就是python.exe的父進程

並發與並行,阻塞與非阻塞

並發指的是,多個事件同時發生了

例如洗衣服和做飯,同時發生了,但本質上是兩個任務在切換,給人的感覺是同時在進行,也被稱為偽並行

並行指的是,多個事件同時進行着

例如一個人在寫代碼另一個人在寫書,這兩件事件是同時在進行的,要注意的是一個人是無法真正的並行執行任務的,在計算機中單核CPU也是無法真正並行的,之所以單核CPU也能同時運行qq和微信其實就是並發執行

阻塞與非阻塞指的是程序的狀態

阻塞狀態是因為程序遇到了IO操作,或是sleep,導致后續的代碼不能被CPU執行

非阻塞與之相反,表示程序正在正常被CPU執行

補充:進程有三種狀態

就緒態,運行態,和阻塞態

img

多道技術會在進程執行時間過長或遇到IO時自動切換其他進程,意味着IO操作與進程被剝奪CPU執行權都會造成進程無法繼續執行

python中實現多進程

在一個應用程序中可能會有多個任務需要並發執行,但是對於操作系統而言,一個進程就是一個任務,CPU會從上往下依次執行代碼,當代碼中遇到IO操作時,操作系統就會剝奪CPU執行權給其他應用程序,這樣對於當前應用程序而言,效率就降低了,如何使得程序既能完成任務又不降低效率呢?答案就是讓把當前程序中的耗時操作交給子進程來完成,如此當前應用程序可以繼續執行其他任務!

python中開啟子進程的兩種方式

方式1:

實例化Process類

from multiprocessing import Process
import time

def task(name):
    print('%s is running' %name)
    time.sleep(3)
    print('%s is done' %name)
if __name__ == '__main__':
    # 在windows系統之上,開啟子進程的操作一定要放到這下面
    # Process(target=task,kwargs={'name':'egon'})
    p=Process(target=task,args=('jack',))
    p.start() # 向操作系統發送請求,操作系統會申請內存空間,然后把父進程的數據拷貝給子進程,作為子進程的初始狀態
    print('======主')

方式2:

繼承Process類 並覆蓋run方法

from multiprocessing import Process
import time

class MyProcess(Process):
    def __init__(self,name):
        super(MyProcess,self).__init__()
        self.name=name

    def run(self):
        print('%s is running' %self.name)
        time.sleep(3)
        print('%s is done' %self.name)
if __name__ == '__main__':
    p=MyProcess('jack')
    p.start()
    print('主')

需要注意的是

1.在windows下 開啟子進程必須放到__main__下面,因為windows在開啟子進程時會重新加載所有的代碼造成遞歸創建進程

2.第二種方式中,必須將要執行的代碼放到run方法中,子進程只會執行run方法其他的一概不管

3.start僅僅是給操作系統發送消息,而操作系統創建進程是要花費時間的,所以會有兩種情況發送

3.1開啟進程速度慢於程序執行速度,先打印”主“ 在打印task中的消息

3.2開啟進程速度快於程序執行速度,先打印task中的消息,在打印”主“

進程間內存相互隔離

from multiprocessing import Process
import time
x=1000
def task():
    global x
    x=0
    print('兒子死啦',x)


if __name__ == '__main_
    print(x)
    p=Process(target=task)
    p.start()
    time.sleep(5)
    print(x)

join函數

調用start函數后的操作就由操作系統來玩了,至於何時開啟進程,進程何時執行,何時結束都與應用程序無關,所以當前進程會繼續往下執行,join函數就可以是父進程等待子進程結束后繼續執行

案例1:

from multiprocessing import Process
import time

x=1000

def task():
    time.sleep(3)
    global x
    x=0
    print('兒子死啦',x)
if __name__ == '__main__':
    p=Process(target=task)
    p.start()

    p.join() # 讓父親在原地等
    print(x)

案例2:

from multiprocessing import Process
import time,random

x=1000

def task(n):
    print('%s is runing' %n)
    time.sleep(n)

if __name__ == '__main__':
    start_time=time.time()

    p1=Process(target=task,args=(1,))
    p2=Process(target=task,args=(2,))
    p3=Process(target=task,args=(3,))
    p1.start()
    p2.start()
    p3.start()

    p3.join() #3s
    p1.join()
    p2.join()

    print('主',(time.time() - start_time))

    start_time=time.time()
    p_l=[]
    for i in range(1,4):
        p=Process(target=task,args=(i,))
        p_l.append(p)
        p.start()
    for p in p_l:
        p.join()
    
    print('主',(time.time() - start_time))

Process對象常用屬性

from multiprocessing import Process
def task(n):
    print('%s is runing' %n)
    time.sleep(n)

if __name__ == '__main__':
    start_time=time.time()
    p1=Process(target=task,args=(1,),name='任務1')
    p1.start() # 啟動進程
    print(p1.pid) # 獲取進程pid
    print(p1.name) # 獲取進程名字
    p1.terminate() # 終止進程
    p1.join() # 提高優先級
    print(p1.is_alive()) # 獲取進程的存活狀態
    print('主')

孤兒進程與僵屍進程

什么是孤兒進程

孤兒進程指的是開啟子進程后,父進程先於子進程終止了,那這個子進程就稱之為孤兒進程

例如:qq聊天中別人發給你一個鏈接,點擊后打開了瀏覽器,那qq就是瀏覽器的父進程,然后退出qq,此時瀏覽器就成了孤兒進程

孤兒進程是無害的,有其存在的必要性,在父進程結束后,其子進程會被操作系統接管。

什么是僵屍進程

僵屍進程指的是,當子進程比父進程先結束,而父進程又沒有回收子進程,釋放子進程占用的資源,此時子進程將成為一個僵屍進程。該情況僅在linux下出現。windows中進程間完全是獨立的沒有任何關聯。

如果父進程先退出 ,子進程被操作系統接管,子進程退出后操作系統會回收其占用的相關資源!

僵屍進程的危害

由於子進程的結束和父進程的運行是一個異步過程,即父進程永遠無法預測子進程 到底什么時候結束. 那么會不會因為父進程太忙來不及wait子進程,或者說不知道 子進程什么時候結束,而丟失子進程結束時的狀態信息呢? 不會。因為UNⅨ提供了一種機制可以保證只要父進程想知道子進程結束時的狀態信息, 就必然可以得到。這種機制就是: 在每個進程退出的時候,內核釋放該進程所有的資源,包括打開的文件,占用的內存等。但是仍然為其保留一定的信息(包括進程號the process ID,退出狀態the termination status of the process,運行時間the amount of CPU time taken by the process等)。直到父進程通過wait / waitpid來取時才釋放. 但這樣就導致了問題,如果進程不調用wait / waitpid的話,那么保留的那段信息就不會釋放,其進程號就會一直被占用,但是系統所能使用的進程號是有限的,如果大量的產生[僵死進程],將因為沒有可用的進程號而導致系統不能產生新的進程. 此為僵屍進程的危害,應當避免。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM