Python多進(jìn)程

多進(jìn)程

什么是多任務(wù)

什么叫做多任務(wù)呢?簡單的說,就是操作系統(tǒng)(OS)可以同時運(yùn)行多個任務(wù)。如你可以一邊瀏覽網(wǎng)頁,一邊聽著歌,同時還可以使用畫板畫著畫,這個就是多任務(wù)。其實我們的操作系統(tǒng)就是多任務(wù)。
單核CPU:
1、時間片輪換
2、優(yōu)先級別調(diào)度
多核CPU:
1、并發(fā)
2、并行

Linux下fork實現(xiàn)多進(jìn)程

進(jìn)程:

???????◆編寫完的代碼,在沒有運(yùn)行的情況下,稱之為程序。
???????◆正在運(yùn)行的代碼,就成為了進(jìn)程。
???????◆進(jìn)程,除了包含代碼外,還需要運(yùn)行環(huán)境等,所以和程序是存在區(qū)別的。
???????◆進(jìn)行多進(jìn)程操作時,肯定會存在一個主進(jìn)程。

fork()方法

Python在os模塊上封裝了常用的系統(tǒng)調(diào)用,其中就包括了fork,我們可以很輕松地在Python代碼中創(chuàng)建進(jìn)程。fork() 函數(shù)可以獲得系統(tǒng)中進(jìn)程的PID ( Process ID ),返回0則為子進(jìn)程,否則就是父進(jìn)程,然后可以據(jù)此對運(yùn)行中的進(jìn)程進(jìn)行操作;但是os.fork()只能在Linux下運(yùn)行,可以使用os.getpid()獲取該進(jìn)程的進(jìn)程號,os.getppid()獲取子進(jìn)程的父進(jìn)程進(jìn)程號。

import os
pid = os.fork()
if pid == 0:
  while True:
    print("執(zhí)行子進(jìn)程!我的進(jìn)程id:%s,我的父進(jìn)程id:%s" % (os.getpid(),os.getppid()))
else:
  while True:
    print("執(zhí)行主進(jìn)程!我的進(jìn)程id:%s,我的子進(jìn)程id:%s" % (os.getpid(),pid))

#運(yùn)行結(jié)果
>>>執(zhí)行子進(jìn)程!我的進(jìn)程id:2978,我的父進(jìn)程id:2977
>>>執(zhí)行主進(jìn)程!我的進(jìn)程id:2977,我的子進(jìn)程id:2978
>>>執(zhí)行主進(jìn)程!我的進(jìn)程id:2977,我的子進(jìn)程id:2978
>>>執(zhí)行子進(jìn)程!我的進(jìn)程id:2978,我的父進(jìn)程id:2977
>>>...

#假如多進(jìn)程中有全局變量的使用
import os
pid = os.fork()
num = 0
if pid == 0:
  num +=1
  print("子進(jìn)程" + num)
else:
  num += 1
  print("父進(jìn)程" + num)
#運(yùn)行結(jié)果
>>>子進(jìn)程1
>>>父進(jìn)程1

#當(dāng)多個fork存在時的運(yùn)行結(jié)果
import os 
pid1 = os.fork()
if pid1 == 0:
  print("我是進(jìn)程1")
else:
  print("我是進(jìn)程2")
pid2 = os.fork()
if pid2 == 0:
  print("我是進(jìn)程3")
else:
  pirnt("我是進(jìn)程4")
#運(yùn)行結(jié)果
>>>我是進(jìn)程1
>>>我是進(jìn)程3
>>>我是進(jìn)程4
>>>我是進(jìn)程2
>>>我是進(jìn)程3
>>>我是進(jìn)程4

觀察運(yùn)行結(jié)果發(fā)現(xiàn):
???????◆兩個進(jìn)程的運(yùn)行的順序是無序的;
???????◆進(jìn)程之間的數(shù)據(jù)無法共享,是各自擁有的一份;
???????◆當(dāng)程序遇到fork時程序會被分成一個子進(jìn)程一個主進(jìn)程,然后再次遇到fork子進(jìn)程又被分成一個主進(jìn)程和一個子進(jìn)程,主進(jìn)程也被分成一個子進(jìn)程和主進(jìn)程,就產(chǎn)生了上面的第三個運(yùn)行結(jié)果;

fork的工作原理

函數(shù)多進(jìn)程

multiprocessing引入多進(jìn)程

fork()方法只能使用在linux系統(tǒng)下(tornado框架的高效性就是使用一個單線程通過fork()實現(xiàn)同時服務(wù)多個客戶端)
python中在linux和windows下都能使用的多線程是multiprocessing中的Process類,通過Process實現(xiàn)一個多進(jìn)程

# 引入模塊
from multiprocessing import Process
import time
# 定義一個函數(shù),將來定義成一個進(jìn)程
def pro1(msg):
  for i in range(5):
    print("進(jìn)程%s正在運(yùn)行" % msg )
    time.sleep(1)
  else:
    print("進(jìn)程%s已結(jié)束" % msg)
def pro2(msg):
  for i in range(10):
    print("進(jìn)程%s正在運(yùn)行" % msg )
    time.sleep(1)
  else:
    print("進(jìn)程%s已結(jié)束" % msg)

# 主進(jìn)程開始,然后啟動子進(jìn)程
if __name__ == "__main__":
  """
  1、多進(jìn)程必須需要主進(jìn)程才能執(zhí)行,python中__main__就是主進(jìn)程的位置
  2、將定義好的函數(shù)轉(zhuǎn)換成進(jìn)程,只需要將Process類實例化,必備屬性有:
    ①target:Process的一個屬性,值是要轉(zhuǎn)換成子進(jìn)程的函數(shù)名
    ②args:它必須是一個元組,保存所有要用到的參數(shù),若只有一個參數(shù),寫成(val,)的形式
  """
  print("————————主進(jìn)程啟動————————")
  bilibili = Process(target=pro1,args=("嗶哩嗶哩",),name="嗶哩嗶哩干杯")
  dnf = Process(target=pro2,args=("掉線城與勇士",),name="CNM給我出時光鞋")
  # 開啟子進(jìn)程
  print(bilibili.name)
  bilibili.start()
  # 這里可以使用一下進(jìn)程對象的terminate方法,終止一個進(jìn)程
  if bilibili.is_alive():
    bilibili.terminate()
    print("嗶哩嗶哩已結(jié)束")
  print(dnf.name)
  dnf.start()
  # 設(shè)置主進(jìn)程等待子進(jìn)程結(jié)束在結(jié)束自己本身,當(dāng)有多個進(jìn)程時
  # 等待哪個進(jìn)程就會在哪個進(jìn)程結(jié)束后向下執(zhí)行,
  bilibili.join()
  print("————————主進(jìn)程結(jié)束————————")

# 運(yùn)行結(jié)果
D:\python軟件\python.exe D:/python_test/re_test.py
————————主進(jìn)程啟動————————
嗶哩嗶哩干杯
嗶哩嗶哩已結(jié)束
CNM給我出時光鞋
————————主進(jìn)程結(jié)束————————
進(jìn)程掉線城與勇士正在運(yùn)行
進(jìn)程掉線城與勇士正在運(yùn)行
進(jìn)程掉線城與勇士正在運(yùn)行
進(jìn)程掉線城與勇士正在運(yùn)行
進(jìn)程掉線城與勇士正在運(yùn)行
進(jìn)程掉線城與勇士正在運(yùn)行
進(jìn)程掉線城與勇士正在運(yùn)行
進(jìn)程掉線城與勇士正在運(yùn)行
進(jìn)程掉線城與勇士正在運(yùn)行
進(jìn)程掉線城與勇士正在運(yùn)行
進(jìn)程掉線城與勇士已結(jié)束

Process finished with exit code 0
總結(jié):

1、主進(jìn)程是必須的
2、創(chuàng)建進(jìn)程實例時target目標(biāo)是函數(shù)名不是調(diào)用函數(shù)
3、args屬性必須是一個元組,只有一個參數(shù)需要時寫成(val,)
4、termanite()終止方法,結(jié)束進(jìn)程
5、多個子進(jìn)程存在時,設(shè)置主進(jìn)程等待哪個子進(jìn)程,就在哪個子進(jìn)程結(jié)束后繼續(xù)向下執(zhí)行主進(jìn)程

類的多進(jìn)程

創(chuàng)建一個子進(jìn)程類的步驟

1、定義一個類,繼承自Process父類
2、如果需要父類中的某些屬性使用supper().__init__()或Process.__init__()調(diào)用
3、重寫Process父類的run方法,在run方法中寫入需要執(zhí)行的進(jìn)程

from multiprocessing import Process
class MyProcess(Process):
  def __init__(self,name,msg):
    super().__init__(name=name)
    self.msg = msg
  #重寫run方法
  def run(self):
    print(self.name)
    print("子進(jìn)程%s開始運(yùn)行" % self.msg)

# 主進(jìn)程入口
if __name__ == "__main__":
  print("————————主進(jìn)程開始————————")
  # 定義出子進(jìn)程的實例,并執(zhí)行子進(jìn)程
  for i in range(5):
    m = MyProcess("bilibli","嗶哩嗶哩動畫")
    m.start()
    m.join()
  print("————————主進(jìn)程結(jié)束————————")

#運(yùn)行結(jié)果
D:\python軟件\python.exe D:/python_test/re_test.py
————————主進(jìn)程開始————————
bilibli
子進(jìn)程嗶哩嗶哩動畫開始運(yùn)行
bilibli
子進(jìn)程嗶哩嗶哩動畫開始運(yùn)行
bilibli
子進(jìn)程嗶哩嗶哩動畫開始運(yùn)行
bilibli
子進(jìn)程嗶哩嗶哩動畫開始運(yùn)行
bilibli
子進(jìn)程嗶哩嗶哩動畫開始運(yùn)行
————————主進(jìn)程結(jié)束————————

Process finished with exit code 0

進(jìn)程池申請異步

進(jìn)程池概念

當(dāng)我們有很多大量的進(jìn)程需要執(zhí)行時(例如多個用戶訪問服務(wù)器,每個用戶都是一個進(jìn)程);
此時若繼續(xù)使用Process創(chuàng)建多進(jìn)程,需要多次創(chuàng)建,大大加重了內(nèi)存的使用和代碼量;
使用進(jìn)程池Pool保存進(jìn)程,能提前創(chuàng)建規(guī)定數(shù)量的進(jìn)程,通過遍歷一次得到多個進(jìn)程;
達(dá)到并發(fā)并行的效果

定義一個簡單的進(jìn)程池
"""
按照順序用進(jìn)程池申請異步的方法——apply_async()
"""
#引入需要的模塊
from multiprocessing import Pool
# 定義進(jìn)程對象的目標(biāo)函數(shù)
def run(msg):
  print("進(jìn)程%s運(yùn)行了" %msg)
#主進(jìn)程入口
if __name__ == "__main__":
  print("主進(jìn)程開始")
  # 定義一個進(jìn)程池,池中保存3個進(jìn)程
  p = Pool(3)
  # 遍歷進(jìn)程池獲取一次獲取3個進(jìn)程
  for i in range(10):
    #進(jìn)程池實例的apply_async()方法申請異步,實現(xiàn)并發(fā)并行,參數(shù)有target,args,name等
    p.apply_async(run,("開啟session",))

  #執(zhí)行完畢后一定要關(guān)閉進(jìn)程池
  p.close()
  #這里是重點(diǎn),進(jìn)程池中子進(jìn)程類似于守護(hù)進(jìn)程,
  #當(dāng)主進(jìn)程結(jié)束后,子進(jìn)程自動結(jié)束,所以必須設(shè)置主進(jìn)程等待
  p.join()
  print("主進(jìn)程結(jié)束")

#運(yùn)行結(jié)果
D:\python軟件\python.exe D:/_web_project/tetetetetet.py
主進(jìn)程開始
進(jìn)程開啟session運(yùn)行了
進(jìn)程開啟session運(yùn)行了
進(jìn)程開啟session運(yùn)行了
進(jìn)程開啟session運(yùn)行了
進(jìn)程開啟session運(yùn)行了
進(jìn)程開啟session運(yùn)行了
進(jìn)程開啟session運(yùn)行了
進(jìn)程開啟session運(yùn)行了
進(jìn)程開啟session運(yùn)行了
進(jìn)程開啟session運(yùn)行了
主進(jìn)程結(jié)束

Process finished with exit code 0
總結(jié)重點(diǎn)

1、定義進(jìn)程池實例時要設(shè)定池中一次性存放幾個子進(jìn)程;
2、執(zhí)行結(jié)果是一次出現(xiàn)3個然后出現(xiàn)3次,最后一次只出現(xiàn)了一個;
3、子進(jìn)程執(zhí)行完畢后,必須關(guān)閉進(jìn)程池;
4、主進(jìn)程必須等待進(jìn)程池中的子進(jìn)程執(zhí)行完畢后再結(jié)束?!臼刈o(hù)進(jìn)程:跟隨主進(jìn)程的結(jié)束而結(jié)束】

進(jìn)程中的數(shù)據(jù)傳遞和數(shù)據(jù)共享

在上面的案例中可以看出,進(jìn)程間的數(shù)據(jù)都是單獨(dú)的一份,不會發(fā)生影響;但是進(jìn)程之間的數(shù)據(jù)并不是絕對隔離的,可以實現(xiàn)使用Queue進(jìn)行進(jìn)程的數(shù)據(jù)傳遞,使用Manager實現(xiàn)進(jìn)程間的數(shù)據(jù)共享。

Queue數(shù)據(jù)傳遞

Queue是一種特殊的先進(jìn)先出的線性數(shù)據(jù)表,一般的數(shù)據(jù)隊列都有大小限制,等待超時時間需要自己定義,常用方法有:
?????◆put()放入數(shù)據(jù)到隊列中
?????◆get()從隊列中取出數(shù)據(jù)
?????◆empty()清空隊列

from multiprocessing import Process,Queue
#子進(jìn)程
def put_pro(q,n):
  #向隊列添加數(shù)據(jù)
  q.put("第%s個進(jìn)程添加了一個數(shù)據(jù)" % n)
#主進(jìn)程
def get_pro(q):
  print("————————————start————————————")
  for i in range(5):
    p = Process(target=put_pro,args=(q,i+1))
    p.start()
    #是否收到子進(jìn)程的數(shù)據(jù)
    print(q.get())
    p.join()
  print("————————————end—————————————")
#程序入口
if __name__ == "__main__":
  #定義一個隊列實例
  q = Queue()
  #啟用多進(jìn)程
  get_pro(q)

>>>運(yùn)行結(jié)果
D:\python軟件\python.exe D:/_web_project/tetetetetet.py
————————————start————————————
第1個進(jìn)程添加了一個數(shù)據(jù)
第2個進(jìn)程添加了一個數(shù)據(jù)
第3個進(jìn)程添加了一個數(shù)據(jù)
第4個進(jìn)程添加了一個數(shù)據(jù)
第5個進(jìn)程添加了一個數(shù)據(jù)
————————————end—————————————

Process finished with exit code 0
Manager實現(xiàn)進(jìn)程間數(shù)據(jù)共享

from multiprocessing import Manager 數(shù)據(jù)管理器類,一個管理器類可以創(chuàng)建用于進(jìn)程間數(shù)據(jù)共享的各種對象,例如:dict,list,queue,Array,Lock等,有了管理器就可以根據(jù)需要定義數(shù)據(jù)類型,減少局限性

from multiprocessing import Process,Manager
# 定義兩個子進(jìn)程,分別向全局變量中添加數(shù)據(jù)
def add_one(d,ls,n):
  d["name%s" %n] = "進(jìn)程一%s" % n
  ls.append(n)
  print("進(jìn)程1執(zhí)行了",d,ls)
def add_two(d,ls,n):
  d["name%s" %n] = "進(jìn)程二%s" % n
  ls.append(n)
  print("進(jìn)程2執(zhí)行了",d,ls)
def run(d,ls):
  for i in range(3):
    p1 = Process(target=add_one,args=(d,ls,i))
    p1.start()
    p1.join()
    p2 = Process(target=add_two,args=(d,ls,i))
    p2.start()
    p2.join()
  #輸出執(zhí)行完畢的全局變量
  print(d,ls)
if __name__ == "__main__":
  #定義幾個全局的Manager變量
  d = Manager().dict()
  ls = Manager().list()
  run(d,ls)

>>>運(yùn)行結(jié)果
D:\python軟件\python.exe D:/_web_project/tetetetetet.py
進(jìn)程1執(zhí)行了 {'name0': '進(jìn)程一0'} [0]
進(jìn)程2執(zhí)行了 {'name0': '進(jìn)程二0'} [0, 0]
進(jìn)程1執(zhí)行了 {'name0': '進(jìn)程二0', 'name1': '進(jìn)程一1'} [0, 0, 1]
進(jìn)程2執(zhí)行了 {'name0': '進(jìn)程二0', 'name1': '進(jìn)程二1'} [0, 0, 1, 1]
進(jìn)程1執(zhí)行了 {'name0': '進(jìn)程二0', 'name1': '進(jìn)程二1', 'name2': '進(jìn)程一2'} [0, 0, 1, 1, 2]
進(jìn)程2執(zhí)行了 {'name0': '進(jìn)程二0', 'name1': '進(jìn)程二1', 'name2': '進(jìn)程二2'} [0, 0, 1, 1, 2, 2]
{'name0': '進(jìn)程二0', 'name1': '進(jìn)程二1', 'name2': '進(jìn)程二2'} [0, 0, 1, 1, 2, 2]
Process finished with exit code 0
————————————————————————————————————————————————————————————————————
#如果將程序中的管理器字典和管理器列表變成{}和[],運(yùn)行結(jié)果為
進(jìn)程1執(zhí)行了 {'name0': '進(jìn)程一0'} [0]
進(jìn)程2執(zhí)行了 {'name0': '進(jìn)程二0'} [0]
進(jìn)程

1執(zhí)行了 {'name1': '進(jìn)程一1'} [1]
進(jìn)程2執(zhí)行了 {'name1': '進(jìn)程二1'} [1]
進(jìn)程1執(zhí)行了 {'name2': '進(jìn)程一2'} [2]
進(jìn)程2執(zhí)行了 {'name2': '進(jìn)程二2'} [2]
{} []

python的多進(jìn)程操作大概就是這樣,對于python來說由于cpython解釋器全局鎖的存在,python的多線程并不能發(fā)揮它的威力,熟練掌握多進(jìn)程是很重要的

來自P站畫師:wlop
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容