併發編程之進程1

什麼是進程?linux

進程(Process)是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操做系統結構的基礎。在早期面向進程設計的計算機結構中,進程是程序的基本執行實體;在當代面向線程設計的計算機結構中,進程是線程的容器。程序是指令、數據及其組織形式的描述,進程是程序的實體。算法

狹義定義:進程是正在運行的程序的實例(an instance of a computer program that is being executed)。
廣義定義:進程是一個具備必定獨立功能的程序關於某個數據集合的一次運行活動。
第一,進程是一個實體。每個進程都有它本身的地址空間,通常狀況下,包括文本區域(text region)、數據區域(data region)
和堆棧(stack region)。文本區域存儲處理器執行的代碼;數據區域存儲變量和進程執行期間使用的動態分配的內存;堆棧區域存儲着活動過程調用的指令和本地變量。 第二,進程是一個「執行中的程序」。程序是一個沒有生命的實體,只有處理器賦予程序生命時(操做系統執行之),它才能成爲一個活動的實體,咱們稱其爲進程。[3] 進程是操做系統中最基本、重要的概念。是多道程序系統出現後,爲了刻畫系統內部出現的動態狀況,描述系統內部各道程序的活動規律引進的一個概念,
全部多道程序設計操做系統都創建在進程的基礎上。
從理論角度看,是對正在運行的程序過程的抽象;
從實現角度看,是一種數據結構,目的在於清晰地刻畫動態系統的內在規律,有效管理和調度進入計算機系統主存儲器運行的程序。

動態性:進程的實質是程序在多道程序系統中的一次執行過程,進程是動態產生,動態消亡的。
併發性:任何進程均可以同其餘進程一塊兒併發執行
獨立性:進程是一個能獨立運行的基本單位,同時也是系統分配資源和調度的獨立單位;
異步性:因爲進程間的相互制約,使進程具備執行的間斷性,即進程按各自獨立的、不可預知的速度向前推動
結構特徵:進程由程序、數據和進程控制塊三部分組成。
多個不一樣的進程能夠包含相同的程序:一個程序在不一樣的數據集裏就構成不一樣的進程,能獲得不一樣的結果;可是執行過程當中,程序不能發生改變。
程序是指令和數據的有序集合,其自己沒有任何運行的含義,是一個靜態的概念。
而進程是程序在處理機上的一次執行過程,它是一個動態的概念。
程序能夠做爲一種軟件資料長期存在,而進程是有必定生命期的。
程序是永久的,進程是暫時的。

注意:同一個程序執行兩次,就會在操做系統中出現兩個進程,因此咱們能夠同時運行一個軟件,分別作不一樣的事情也不會混亂。json

在採用多級反饋隊列調度算法的系統中,調度算法的實施過程以下所述。

(1) 應設置多個就緒隊列,併爲各個隊列賦予不一樣的優先級。第一個隊列的優先級最高,第二個隊列次之,
其他各隊列的優先權逐個下降。該算法賦予各個隊列中進程執行時間片的大小也各不相同,在優先權愈高的隊列中,
爲每一個進程所規定的執行時間片就愈小。例如,第二個隊列的時間片要比第一個隊列的時間片長一倍,……,第i+1個
隊列的時間片要比第i個隊列的時間片長一倍。 (2) 當一個新進程進入內存後,首先將它放入第一隊列的末尾,按FCFS原則排隊等待調度。當輪到該進程執行時,
如它能在該時間片內完成,即可準備撤離系統;若是它在一個時間片結束時還沒有完成,調度程序便將該進程轉入第二隊
列的末尾,再一樣地按FCFS原則等待調度執行;若是它在第二隊列中運行一個時間片後仍未完成,再依次將它放入第三
隊列,……,如此下去,當一個長做業(進程)從第一隊列依次降到第n隊列後,在第n 隊列便採起按時間片輪轉的方式運
行。 (3) 僅當第一隊列空閒時,調度程序才調度第二隊列中的進程運行;僅當第1~(i-1)隊列均空時,纔會調度第i隊列中
的進程運行。若是處理機正在第i隊列中爲某進程服務時,又有新進程進入優先權較高的隊列(第1~(i-1)中的任何一個
隊列),則此時新進程將搶佔正在運行進程的處理機,即由調度程序把正在運行的進程放回到第i隊列的末尾,把處理機分
配給新到的高優先權進程。

進程的並行與併發

並行 :是指一個時間段內,有幾個程序都在幾個CPU上運行,任意一個時刻點上,有多個程序在同時運行,而且多道程序之間互不干擾。(資源夠用,好比三個線程,四核的CPU )安全

併發 : 是指一個時間段內,有幾個程序都在同一個CPU上運行,但任意一個時刻點上只有一個程序在處理機上運行,併發是指資源有限的狀況下,二者交替輪流使用資源。服務器

區別:網絡

並行是從微觀上,也就是在一個精確的時間片刻,有不一樣的程序在執行,這就要求必須有多個處理器。
併發是從宏觀上,在一個時間段上能夠看出是同時執行的,好比一個服務器同時處理多個session。session

 

同步阻塞與非阻塞數據結構

 

  在瞭解其餘概念以前,咱們首先要了解進程的幾個狀態。在程序運行的過程當中,因爲被操做系統的調度算法控制,程序會進入幾個狀態:就緒,運行和阻塞。併發

  (1)就緒(Ready)狀態app

  當進程已分配到除CPU之外的全部必要的資源,只要得到處理機即可當即執行,這時的進程狀態稱爲就緒狀態。

  (2)執行/運行(Running)狀態當進程已得到處理機,其程序正在處理機上執行,此時的進程狀態稱爲執行狀態。

  (3)阻塞(Blocked)狀態正在執行的進程,因爲等待某個事件發生而沒法執行時,便放棄處理機而處於阻塞狀態。引發進程阻塞的事件可有多種,例如,等待I/O完成、申請緩衝區不能知足、等待信件(信號)等。

 

 

 
 
 

process模塊介紹

process模塊是一個建立進程的模塊,藉助這個模塊,就能夠完成進程的建立。

 
 
 
Process([group [, target [, name [, args [, kwargs]]]]]),由該類實例化獲得的對象,表示一個子進程中的任務(還沒有啓動)

強調:
1. 須要使用關鍵字的方式來指定參數
2. args指定的爲傳給target函數的位置參數,是一個元組形式,必須有逗號

參數介紹:
1 group參數未使用,值始終爲None
2 target表示調用對象,即子進程要執行的任務
3 args表示調用對象的位置參數元組,args=(1,2,'egon',)
4 kwargs表示調用對象的字典,kwargs={'name':'egon','age':18}
5 name爲子進程的名稱
 
1 p.start():啓動進程,並調用該子進程中的p.run() 
2 p.run():進程啓動時運行的方法,正是它去調用target指定的函數,咱們自定義類的類中必定要實現該方法  
3 p.terminate():強制終止進程p,不會進行任何清理操做,若是p建立了子進程,該子進程就成了殭屍進程,使用該方法須要特別當心這種狀況。
若是p還保存了一個鎖那麼也將不會被釋放,進而致使死鎖
4 p.is_alive():若是p仍然運行,返回True
5 p.join([timeout]):主線程等待p終止(強調:是主線程處於等的狀態,而p是處於運行的狀態)。timeout是可選的超時時間,須要強調的是,
p.join只能join住start開啓的進程,而不能join住run開啓的進程  
 
 
1 p.daemon:默認值爲False,若是設爲True,表明p爲後臺運行的守護進程,當p的父進程終止時,p也隨之終止,而且設定爲True後,p不能建立自
己的新進程,必須在p.start()以前設置
2 p.name:進程的名稱 3 p.pid:進程的pid 4 p.exitcode:進程在運行時爲None、若是爲–N,表示被信號N結束(瞭解便可) 5 p.authkey:進程的身份驗證鍵,默認是由os.urandom()隨機生成的32字符的字符串。這個鍵的用途是爲涉及網絡鏈接的底層進程間通訊提供安全性
,這類鏈接只有在具備相同的身份驗證鍵時才能成功(瞭解便可)

 

在Windows操做系統中因爲沒有fork(linux操做系統中建立進程的機制),在建立子進程的時候會自動 import 啓動它的這個文件,而在 import 的時
候又執行了整個文件。所以若是將process()直接寫在文件中就會無限遞歸建立子進程報錯。因此必須把建立子進程的部分使用if __name__ ==‘__main__’ 判斷
保護起來,import 的時候 ,就不會遞歸運行了。

 

multiprocessing模塊建立進程的方式1:

from multiprocessing  import Process
import time
def test(name):
    print('你是叫%s是吧'%name)
    print("這是子進程")
if __name__ == '__main__':
    p=Process(target=test,args=('xiaohong',))   #建立一個進程對象
    p.start() #告訴操做系統幫你建立一個進程,至於何時建立,怎麼創,由操做系統隨機決定
    #time.sleep(0.1)
    print('我是....')
    '''
    爲什麼先運行start下面的print,再運行子進程?
    由於在運行到start()時,會在內存中申請一塊空間,而後運行.py文件,將代碼丟到內存空間裏面,這時子進程才建立好,
    這時建立進程的時間遠遠大於執行start下面print代碼的時間,顧先打印「我是子進程」
    進程與進程之間數據是隔離的,沒法直接交互,能夠經過某些技術實現間接交互。
若是在start下方加上睡眠時間,則子進程先啓動,0.1秒的時間足夠操做系統建立子進程,讓CPU讀取子進程中動態運行的代碼

=========
我是....
你是叫xiaohong是吧
這是子進程

multiprocessing模塊建立進程的方式2:

'''
自定義的類體中必需要有run方法,才能建立出來一個子進程,其內部函數體代碼才能運行,其餘函數則運行不了
'''
import time
from multiprocessing import Process

class MyProcess(Process):

    def __init__(self,name):
        super().__init__()
        self.name=name

    def run(self):  #必須是run方法,否則建立不了子進程,其餘方法就只能是一個靜態實體,並不能運行

        print('%s is running'%self.name)
        time.sleep(3)
        print('%s is overing'%self.name)

if __name__ == '__main__':

    p=MyProcess('zhenhua')
    p.start()
    print('一個py文件就是一個主進程')

=====
一個py文件就是一個主進程
zhenhua is running
zhenhua is overing

 

進程的join方法:p.join()可使主進程處於等待狀態,待子進程運行完畢,再啓動主進程。

進程中的join方法1:

在這裏首先說明一下,Process在同時建立多個子進程的時候,同時各啓動子進程,各子進程、主進程是各自隔離運行的,數據不共享,運行也互不干擾,至於操做系統先建立哪一個子進程,由操做系統決定,因此不是按照咱們書寫代碼的順序去建立

from multiprocessing import  Process
import time

def test(name):

    print('%s is running'%name)
    time.sleep(2)
    print('%s is overing'%name)

if __name__ == '__main__':
    p=Process(target=test,args=('king',))
    p1=Process(target=test,args=('queen',))
    p2=Process(target=test,args=('hero',))
    p.start()
    p1.start()
    p2.start() #CPU讀取的速度肯快,操做系統至關於同時受到三個建立命令,三個子進程的建立順序及建立時間是操做系統隨機決定的,
    p.join()  #join 可讓主進程暫停運行,等待子進程運行結束才繼續運行
    #p.join()
    print('啥時間運行我呢?')

======
queen is running
king is running
hero is running
queen is overing
king is overing
hero is overing
啥時間運行我呢?

 

進程中的join方法2:

from multiprocessing import  Process
import time

def test(name,i):

    print('%s is running'%name)
    time.sleep(2)
    print('%s is overing'%name)

if __name__ == '__main__':
    for i in range(3):
        p=Process(target=test,args=('進程%s'%i,i))
        p.start()
        p.join()  #join 可讓主進程暫停運行,等待子進程運行結束才繼續運行

        print('啥時間運行我呢?')


========
進程0 is running
進程0 is overing
啥時間運行我呢?
進程1 is running
進程1 is overing
啥時間運行我呢?
進程2 is running
進程2 is overing
啥時間運行我呢?

 

進程中的join方法3:

from multiprocessing import Process
import time
def test(name,i):
    print('%s is runing'%name)
    time.sleep(i)
    print('%s is overing'%name)
if __name__ == '__main__':
    start = time.time()
    p_list=[]
    for i in range(1,4):
        p=Process(target=test,args=('子進程%s'%i,i ))
        p_list.append(p)
        p.start()

    for p in p_list:
        p.join()
    print('', (time.time() - start))

=====
子進程3 is runing
子進程1 is runing
子進程2 is runing
子進程1 is overing
子進程2 is overing
子進程3 is overing
主 3.1881821155548096

 

各進程之間啓動是隔離的

from multiprocessing import Process
'''

'''

money=100
def test():
    global money
    money=888


if __name__ == '__main__':
    p=Process(target=test)
    p.start()
    p.join()
    print(money)
'''
經過添加join讓子進程先運行結束,再繼續運行父進程,發現money的值還
是100,global並無修改父進程中的money,而是修改本身內存中的money
'''

=====
100

 

from multiprocessing import Process,current_process

import time
import os
def func(i):
    print(f'子進程{i}的父進程進程號:{os.getppid()},子進程{i}的進程號是:{current_process().pid}')

    time.sleep(2)

if __name__ == '__main__':
   for i in range(1,5):
        p = Process(target=func,args=(i,))
        p.start()
        # p.join()
        # p.terminate()  # 截斷進程,其上不能再用join()方法,這樣已經達到讓子進程先運行的效果了,就沒法用terminate阻斷子進程了。
        
        # print(p.is_alive())

   print(f'主進程進程號是:{current_process().pid},主進程的父進程是{os.getppid()}')


'''
主進程進程號是:6964,主進程的父進程是2944
子進程2的父進程進程號:6964,子進程2的進程號是:5872
子進程3的父進程進程號:6964,子進程3的進程號是:6772
子進程4的父進程進程號:6964,子進程4的進程號是:1836
子進程1的父進程進程號:6964,子進程1的進程號是:6656
'''
說明:進程的建立須要由操做系統來決定,建立進程須要須要從新開闢內存空間,把要運行的代碼複製進去,
把運行過程當中產生的名字放到名稱空間,消耗資源;每開闢一個進程,就要開闢一塊新的內存空間,開闢進
程的的所需時間遠遠大於主進程(一個.py文件中的代碼)代碼運行時間,因此,永遠先運行的都是主進程
中的代碼,除非使用join或者設置睡眠時間;建立的歌子進程之間以及主進程之間都是隔離的,互補交涉,
因此是併發運行的,運行時間2s多一點。

 

 

進程對象及其餘方法:

current_process.pid()查看當前(子/主)進程號
os.getpid()
查看當前(子/主)進程號
os.getppid()查看當前進程的上一級進程號
from multiprocessing import Process,current_process

import time
import os
def func(i):
    print(f'子進程{i}的父進程進程號:{os.getppid()},子進程{i}的進程號是:{current_process().pid}')

    time.sleep(60)

if __name__ == '__main__':
   for i in range(1,5):
        p = Process(target=func,args=(i,))
        p.start()
 
   print(f'主進程進程號是:{current_process().pid},主進程的父進程是{os.getppid()}')

'''
主進程進程號是:5816,主進程的父進程是2944
子進程3的父進程進程號:5816,子進程3的進程號是:4020
子進程4的父進程進程號:5816,子進程4的進程號是:5204
子進程1的父進程進程號:5816,子進程1的進程號是:6148
子進程2的父進程進程號:5816,子進程2的進程號是:6316
'''
'''
無論是主進程仍是子進程,或是子進程的父進程的進程號都是運行Python解釋器(進程),主進程(Python解釋器)的父進程是pycharm

 

殭屍進程
    子進程是由主進程建立的,主進程先死,子進程死了以後所佔用的PID及其餘資源沒有被主進程回收,這個子進程就步入了殭屍進程;全部的進程最終都會步入到殭屍進程。

    殭屍進程帶來的缺點:操做系統所給的進程號是有限的,若是殭屍進程過多,將致使過多的PID被佔用(沒有回收),致使後期再啓程序會受到阻礙。

  父進程回收子進程資源的兩種方式:

    1)使用join方法,讓子進程先死,這樣主進程就能夠回收子進程的進程號;據說調用一個叫wait的方法。

    2)父進程正常死亡(父進程等子進程死亡本身也死了)

孤兒進程:子進程沒死,父進程意外死亡;

    針對Linux會有兒童福利院(init),若是父進程意外死亡,他們所建立的子進程都會被福利院收養。孤兒進程是無害的,由於他的進程號會被福利院回收。

 
 
from multiprocessing import Process,current_process
import os
import time
def test(name):
    print('%s is running'%name,current_process().pid,'父進程PID:%s'%os.getppid()) #查看子進程號
    time.sleep(3)
    print('%s is overing'%name)
if __name__ == '__main__':
    p=Process(target=test,args=('egon',))  #args括號內逗號必需要加上,否則會報錯
    p.start()
    '''
    p.terminate()  #殺死進程,告訴操做系統,至於操做系統何時殺由他決定,
    可是代碼執行的速度比操做系統反應快,因此頗有可能,在進程還沒殺死以前代碼運行結束,
    is_live的返回值是True,咱們能夠經過在terminate以後加上time.sleep()稍微睡眠一下,
   '''
    p.terminate()
    time.sleep(0.1)
    print(p.is_alive() ) #判斷進程是否被殺死,返回的是bool值TRUE、False
    print('主進程號:%s'%current_process().pid,'主主進程PID:%s'%os.getppid())  #查看主進程號

========

False
主進程號:7332 主主進程PID:3924

 

守護進程:其本質就是一個子進程「」,該子進程的生命週期<=被守護進程的生命週期,只要主進程一旦結束,子進程不管有沒有結束,都要跟着一塊兒死。

#守護進程: 本質就是一個"子進程",該"子進程"的生命週期<=被守護進程的生命週期
from multiprocessing import Process
import time

def task(name):
    print('老太監%s活着....' %name)
    time.sleep(3)
    print('老太監%s正常死亡....' %name)

if __name__ == '__main__':
    p=Process(target=task,args=('劉清政',))
    p.daemon=True  #守護進程要在start以前設置,告訴操做系統該進程是守護進程;在start以後設置就會報錯
    p.start()
    time.sleep(1)
    print('皇上:EGON正在死...')


=======
老太監劉清政活着....
皇上:EGON正在死...

 

互斥鎖:

當多個進程操做同一份數據的時候 會形成數據的錯亂,這個時候必須加鎖處理,這樣將併發變成串行,雖然下降了效率可是提升了數據的安全,

注意:
1.鎖不要輕易使用 容易形成死鎖現象
2.只在處理數據的部分加鎖 不要在全局加鎖

3.鎖必須在主進程中產生 交給子進程去使用 

 

如下依搶票爲例:

    

進程之間數據不共享,可是共享同一套文件系統,因此訪問同一個文件,或同一個打印終端,是沒有問題的,

而共享帶來的是競爭,競爭帶來的結果就是錯亂,如何控制,就是加鎖處理

'''
0.000幾秒for循環就已經已經結束了,此時操做系統或許連一個進程都尚未建立,當第一個進程建立好以後
運行search函數,在睡眠一秒的時候,全部的進程都起來了,也都在這裏睡眠,第一我的查看到餘票還有1張,別的其餘人查看餘票也是1張
以後都到了買票的環節,此時票數是1,你們都對字典的一進行修改,致使全部人都買票成功,但實際只有一張票
這裏使用互斥鎖,將這種併發問題變成串行來解決,在一我的在作操做的時候其餘人都不能作操做,其餘人作操做是基於前一我的作完操做以後再繼續
互斥鎖把併發(併發效率高於串行)變成串行,犧牲效率,但保證數據安全了
方法一:使用join,這種方法,一次只能啓動一個進程,只有進程1啓動了,進程1 搶票成功,這樣數據安全了,可是永遠只有進程1
搶票成功,也是不合理的。
方法二:導入lock
'''
import time ,json,random
from multiprocessing import Process,Lock

def search(name):
     with open(r'xxx.json','rt',encoding='utf-8')as f:
         dic=json.load(f)
     time.sleep(1)
     print('%s查詢餘票爲%s'%(name,dic['count']))

def buy(name):

    with open(r'xxx.json','rt',encoding='utf_8') as f:
        dic=json.load(f)

        if dic['count']>0:
            dic['count']-=1
            time.sleep(random.randint(1, 2))
            with open(r'xxx.json', 'wt', encoding='utf-8')as f:
                json.dump(dic, f)
                print('%s購票成功' % name)

        else:
            print('餘票已經沒了!')



def common(name,mutex):
    search(name)#這裏應該變成併發,枷鎖以後,好多人都阻塞到這裏了

    mutex.acquire() #獲取鎖,且同一時間只有一人得到鎖
    buy(name) #這裏應該變成串行
    mutex.release() #釋放鎖,接下來的人繼續搶鎖

if __name__ == '__main__':
    mutex=Lock()
    for i in range(10):
        p=Process(target=common,args=('路人%s'%i,mutex))

        p.start()
       # p.join()

======
路人3查詢餘票爲5
路人1查詢餘票爲5
路人0查詢餘票爲5
路人2查詢餘票爲5
路人7查詢餘票爲5
路人4查詢餘票爲5
路人9查詢餘票爲5
路人5查詢餘票爲5
路人6查詢餘票爲5
路人8查詢餘票爲5
路人3購票成功
路人1購票成功
路人0購票成功
路人2購票成功
路人7購票成功
餘票已經沒了!
餘票已經沒了!
餘票已經沒了!
餘票已經沒了!
餘票已經沒了!
#加鎖能夠保證多個進程修改同一塊數據時,同一時間只能有一個任務能夠進行修改,即串行的修改,沒錯,速度是慢了,但犧牲了速度卻保證了數據安全。
雖然能夠用文件共享數據實現進程間通訊,但問題是:
1.效率低(共享數據基於文件,而文件是硬盤上的數據)
2.須要本身加鎖處理



#所以咱們最好找尋一種解決方案可以兼顧:一、效率高(多個進程共享一塊內存的數據)二、幫咱們處理好鎖問題。這就是mutiprocessing模塊爲咱們提供的基於消息的IPC通訊機制:隊列和管道。
1 隊列和管道都是將數據存放於內存中
2 隊列又是基於(管道+鎖)實現的,可讓咱們從複雜的鎖問題中解脫出來,
咱們應該儘可能避免使用共享數據,儘量使用消息傳遞和隊列,避免處理複雜的同步和鎖問題,並且在進程數目增多時,每每能夠得到更好的可獲展性。
相關文章
相關標籤/搜索