併發編程——多進程

---恢復內容開始---python

本節導讀:web

  • multiprocessing模塊介紹
  • process類的介紹
  • 開啓進程的兩種方式
  • join方法
  • 守護進程
  • 互斥鎖
  • 隊列
  • 生產者與消費者模型

 

一 multiprocessing模塊介紹編程

  python中的多線程沒法利用多核優點,若是想要充分地使用多核CPU的資源(os.cpu\_count\(\)查看),在python中大部分狀況須要使用多進程。windows

Python提供了multiprocessing。 multiprocessing模塊用來開啓子進程,並在子進程中執行咱們定製的任務(好比函數),該模塊與多線程模塊threading的編程接口相似。multiprocessing模塊的功能衆多:支持子進程、通訊和共享數據、執行不一樣形式的同步,>提供了Process、Queue、Pipe、Lock等組件。安全

須要再次強調的一點是:與線程不一樣,進程沒有任何共享狀態,進程修改的數據,改動僅限於該進程內。多線程

二 Process類的介紹併發

建立進程app

Process([group [, target [, name [, args [, kwargs]]]]])
#由該類實例化獲得的對象,可用來開啓一個子進程

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

參數介紹dom

#group參數未使用,值始終爲None
#target表示調用對象,即子進程要執行的任務
#args表示調用對象的位置參數元組,args=(1,2,'egon',)
#kwargs表示調用對象的字典,kwargs={'name':'egon','age':18}
#name爲子進程的名稱

方法介紹ide

p.start()  #啓動進程,並調用該子進程中的p.run() 
p.run() #進程啓動時運行的方法,正是它去調用target指定的函數,咱們自定義類的類中必定要實現該方法  
p.terminate() #強制終止進程p,不會進行任何清理操做,若是p建立了子進程,該子進程就成了殭屍進程,使用該方法須要特別當心這種狀況。若是p還保存了一個鎖那麼也將不會被釋放,進而致使死鎖
p.is_alive() #若是p仍然運行,返回True
p.join([timeout]) #主線程等待p終止(強調:是主線程處於等的狀態,而p是處於運行的狀態)。timeout是可選的超時時間。

屬性介紹

p.daemon  #默認值爲False,若是設爲True,表明p爲後臺運行的守護進程,當p的父進程終止時,p也隨之終止,而且設定爲True後,p不能建立本身的新進程,必須在p.start()以前設置
p.name #進程的名稱
p.pid # 進程的pid

 三 開啓進程的兩種方式

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

方法一

import time
import random
from multiprocessing import Process

def piao(name):
    print('%s piaoing' %name)
    time.sleep(random.randrange(1,5))
    print('%s piao end' %name)

if __name__ == '__main__':
    #實例化獲得四個對象
    p1=Process(target=piao,args=('egon',)) #必須加,號
    p2=Process(target=piao,args=('alex',))
    p3=Process(target=piao,args=('wupeqi',))
    p4=Process(target=piao,args=('yuanhao',))

    #調用對象下的方法,開啓四個進程
    p1.start()
    p2.start()
    p3.start()
    p4.start()
    print('')
直接經過process實例化

 方法二

import time
import random
from multiprocessing import Process

class Piao(Process):
    def __init__(self,name):
        super().__init__()
        self.name=name
    def run(self):
        print('%s piaoing' %self.name)

        time.sleep(random.randrange(1,5))
        print('%s piao end' %self.name)

if __name__ == '__main__':
    #實例化獲得四個對象
    p1=Piao('egon')
    p2=Piao('alex')
    p3=Piao('wupeiqi')
    p4=Piao('yuanhao')

    #調用對象下的方法,開啓四個進程
    p1.start() #start會自動調用run
    p2.start()
    p3.start()
    p4.start()
    print('')
繼承process類

四 join方法

在主進程運行過程當中若是想併發地執行其餘的任務,咱們能夠開啓子進程,此時主進程的任務與子進程的任務分兩種狀況

狀況一:在主進程的任務與子進程的任務彼此獨立的狀況下,主進程的任務先執行完畢後,主進程還須要等待子進程執行完畢,而後統一回收資源。

狀況二:若是主進程的任務在執行到某一個階段時,須要等待子進程執行完畢後才能繼續執行,就須要有一種機制可以讓主進程檢測子進程是否運行完畢,在子進程執行完畢後才繼續執行,不然一直在原地阻塞,這就是join方法的做用

from multiprocessing import Process
import time
import random
import os

def task():
    print('%s is piaoing' %os.getpid())
    time.sleep(random.randrange(1,3))
    print('%s is piao end' %os.getpid())

if __name__ == '__main__':
    p=Process(target=task)
    p.start()
    p.join() #等待p中止,才執行下一行代碼
    print('')
join

 

五 守護進程

   若是咱們有兩個任務須要併發執行,那麼開一個主進程和一個子進程分別去執行就ok了,若是子進程的任務在主進程任務結束後就沒有存在的必要了,那麼該子進程應該在開啓前就被設置成守護進程。主進程代碼運行結束,守護進程隨即終止

關於守護進程須要強調兩點:

其一:守護進程會在主進程代碼執行結束後就終止

 

其二:守護進程內沒法再開啓子進程,不然拋出異常:AssertionError: daemonic processes are not allowed to have children

開啓守護進程方法:

from multiprocessing import Process
import time
import random

def task(name):
    print('%s is piaoing' %name)
    time.sleep(random.randrange(1,3))
    print('%s is piao end' %name)


if __name__ == '__main__':
    p=Process(target=task,args=('egon',))
    p.daemon=True #必定要在p.start()前設置,設置p爲守護進程,禁止p建立子進程,而且父進程代碼執行結束,p即終止運行
    p.start()
    print('') #只要終端打印出這一行內容,那麼守護進程p也就跟着結束掉了
Daemon

六 互斥鎖

  進程之間數據隔離,可是共享一套文件系統,於是能夠經過文件來實現進程直接的通訊,而多個進程同時操做同一數據,必然會致使數據錯亂,這時候就要經過加互斥鎖來解決。

#由併發變成了串行,犧牲了運行效率,但避免了競爭
from multiprocessing import Process,Lock
import os,time
def work(lock):
    lock.acquire() #加鎖
    print('%s is running' %os.getpid())
    time.sleep(2)
    print('%s is done' %os.getpid())
    lock.release() #釋放鎖
if __name__ == '__main__':
    lock=Lock()
    for i in range(3):
        p=Process(target=work,args=(lock,))
        p.start()
lock

 總結:

加鎖能夠保證多個進程修改同一塊數據時,同一時間只能有一個任務能夠進行修改,即串行地修改,沒錯,速度是慢了,但犧牲了速度卻保證了數據安全。

雖然能夠用文件共享數據實現進程間通訊,但問題是:

一、效率低(共享數據基於文件,而文件是硬盤上的數據)

二、須要本身加鎖處理

所以咱們最好找尋一種解決方案可以兼顧:

一、效率高(多個進程共享一塊內存的數據)

二、幫咱們處理好鎖問題。

這就是mutiprocessing模塊爲咱們提供的基於消息的IPC通訊機制:隊列和管道。

隊列和管道都是將數據存放於內存中,而隊列又是基於(管道+鎖)實現的,可讓咱們從複雜的鎖問題中解脫出來,於是隊列纔是進程間通訊的最佳選擇。

咱們應該儘可能避免使用共享數據,儘量使用消息傳遞和隊列,避免處理複雜的同步和鎖問題,並且在進程數目增多時,每每能夠得到更好的可獲展性。

 

七 隊列

進程彼此之間互相隔離,要實現進程間通訊(IPC),multiprocessing模塊支持兩種形式:隊列和管道,這兩種方式都是使用消息傳遞的

隊列的建立

 

from multiprocessing import Queue
q =Queue([maxsize])
#建立共享的進程隊列,Queue是多進程安全的隊列,可使用Queue實現多進程之間的數據傳遞。

 

參數介紹

maxsize是隊列中容許最大項數,省略則無大小限制。
但須要明確:
    1、隊列內存放的是消息而非大數據
    二、隊列佔用的是內存空間,於是maxsize即使是無大小限制也受限於內存大小

隊列的使用

from multiprocessing import Process,Queue

q=Queue(3)

#put ,get ,put_nowait,get_nowait,full,empty
q.put(1)   #用以插入數據到隊列中。
q.put(2)
q.put(3)
print(q.full()) #滿了
# q.put(4) #再放就阻塞住了

print(q.get())     #能夠從隊列讀取而且刪除一個元素。

print(q.get())
print(q.get())
print(q.empty()) #空了
# print(q.get()) #再取就阻塞住了
隊列使用

 

八 生產者與消費者模型

爲何要使用生產者消費者模型

生產者指的是生產數據的任務,消費者指的是處理數據的任務,在併發編程中,若是生產者處理速度很快,而消費者處理速度很慢,那麼生產者就必須等待消費者處理完,才能繼續生產數據。一樣的道理,若是消費者的處理能力大於生產者,那麼消費者就必須等待生產者。爲了解決這個問題因而引入了生產者和消費者模式。

什麼是生產者和消費者模式

生產者消費者模式是經過一個容器來解決生產者和消費者的強耦合問題。生產者和消費者彼此之間不直接通信,而經過阻塞隊列來進行通信,因此生產者生產完數據以後不用等待消費者處理,直接扔給阻塞隊列,消費者不找生產者要數據,而是直接從阻塞隊列裏取,阻塞隊列就至關於一個緩衝區,平衡了生產者和消費者的處理能力。

這個阻塞隊列就是用來給生產者和消費者解耦的

Queue([maxsize]):建立共享的進程隊列,Queue是多進程安全的隊列,可使用Queue實現多進程之間的數據傳遞。

參數介紹:

maxsize是隊列中容許最大項數,省略則無大小限制。
但須要明確:
    一、隊列內存放的是消息而非大數據
    二、隊列佔用的是內存空間,於是maxsize即使是無大小限制也受限於內存大小

主要方法介紹:

q.put方法用以插入數據到隊列中。
q.get方法能夠從隊列讀取而且刪除一個元素。

隊列的使用

from multiprocessing import Process,Queue

q=Queue(3)

#put ,get ,put_nowait,get_nowait,full,empty
q.put(1)
q.put(2)
q.put(3)
print(q.full()) #滿了
# q.put(4) #再放就阻塞住了

print(q.get())
print(q.get())
print(q.get())
print(q.empty()) #空了
# print(q.get()) #再取就阻塞住了
相關文章
相關標籤/搜索