Python多任務-進程

程序是以二進制形式存放在硬盤之上的,當啓動程序將數據(代碼)加載至內存中就稱之爲進程。
進程是一組資源(包括:代碼,顯示器,硬盤,網絡…)的統稱。線程也能實現多任務,但相對進程來說是輕量級的。
實際上進程是一個資源分配的單位,實際上操縱數據的是線程,一個進程至少一個主線程。代碼至上而下運行的時候實際上就是主線程在運行,當遇到一個Thread對象調用start方法時,就再開一個線程。當遇到一個Process對象調用start方法時就將當前進程的資源複製一份到內存的一個區域中,以一個新的進程去運行,它擁有獨立的內存單元,這就叫子進程。子進程因爲有自己的資源,所以不與父進程共享全局變量。
簡單的來說進程是資源分配的單位,而線程是操作系統調度的單位。

# -*- coding:utf-8 -*-
from multiprocessing import Process
import os
import time

nums = [11, 22]

def work1():
    """子進程要執行的代碼"""
    print("in process1 pid=%d ,nums=%s" % (os.getpid(), nums))
    for i in range(3):
        nums.append(i)
        time.sleep(1)
        print("in process1 pid=%d ,nums=%s" % (os.getpid(), nums))

def work2():
    """子進程要執行的代碼"""
    print("in process2 pid=%d ,nums=%s" % (os.getpid(), nums))

if __name__ == '__main__':
    p1 = Process(target=work1)
    p1.start()
    p1.join()

    p2 = Process(target=work2)
    p2.start()

結果:

in process1 pid=7956 ,nums=[11, 22]
in process1 pid=7956 ,nums=[11, 22, 0]
in process1 pid=7956 ,nums=[11, 22, 0, 1]
in process1 pid=7956 ,nums=[11, 22, 0, 1, 2]
in process2 pid=22332 ,nums=[11, 22]

Process語法結構如下:

Process([group [, target [, name [, args [, kwargs]]]]])

target:如果傳遞了函數的引用,可以任務這個子進程就執行這裏的代碼
args:給target指定的函數傳遞的參數,以元組的方式傳遞
kwargs:給target指定的函數傳遞命名參數
name:給進程設定一個名字,可以不設定
group:指定進程組,大多數情況下用不到
Process創建的實例對象的常用方法:

start():啓動子進程實例(創建子進程)
is_alive():判斷進程子進程是否還在活着
join([timeout]):是否等待子進程執行結束,或等待多少秒
terminate():不管任務是否完成,立即終止子進程
Process創建的實例對象的常用屬性:

name:當前進程的別名,默認爲Process-N,N爲從1開始遞增的整數
pid:當前進程的pid(進程號)

既然進程間數據不共享那麼進程間又是怎麼通信的呢
操作系統提供了很多機制實現進程間通信,可以使用文件,socket,以及內存。在內存中劃分一塊區域,一個進程寫另一個進程讀就可以完成進程間通信了。
##Queue的使用
可以使用multiprocessing模塊的Queue實現多進程之間的數據傳遞,Queue本身是一個消息列隊程序。

from multiprocessing import Process, Queue
import os, time, random

# 寫數據進程執行的代碼:
def write(q):
    for value in ['A', 'B', 'C']:
        print('Put %s to queue...' % value)
        q.put(value)
        time.sleep(random.random())

# 讀數據進程執行的代碼:
def read(q):
    while True:
        if not q.empty():
            value = q.get(True)
            print('Get %s from queue.' % value)
            time.sleep(random.random())
        else:
            break

if __name__=='__main__':
    # 父進程創建Queue,並傳給各個子進程:
    q = Queue()
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    # 啓動子進程pw,寫入:
    pw.start()    
    # 等待pw結束:
    pw.join()
    # 啓動子進程pr,讀取:
    pr.start()
    pr.join()
    # pr進程裏是死循環,無法等待其結束,只能強行終止:
    print('')
    print('所有數據都寫入並且讀完')

結果:

Put A to queue...
Put B to queue...
Put C to queue...
Get A from queue.
Get B from queue.
Get C from queue.

所有數據都寫入並且讀完

進程池

當需要創建的子進程數量不多時,可以直接利用multiprocessing中的Process動態成生多個進程,但如果是上百甚至上千個目標,手動的去創建進程的工作量巨大,此時就可以用到multiprocessing模塊提供的Pool方法。

初始化Pool時,可以指定一個最大進程數,當有新的請求提交到Pool中時,如果池還沒有滿,那麼就會創建一個新的進程用來執行該請求;但如果池中的進程數已經達到指定的最大值,那麼該請求就會等待,直到池中有進程結束,纔會用之前的進程來執行新的任務
任務數固定用Process創建子進程,不固定則用Pool來創建進程池

通過進程池(Pool)創建的子進程,主進程並不會等子進程執行完才退出,這時候就需要調用po.join堵塞主進程直到子進程執行完畢。

multiprocessing.Pool常用函數解析:

apply_async(func[, args[, kwds]]) :使用非阻塞方式調用func(並行執行,堵塞方式必須等待上一個進程退出才能執行下一個進程),args爲傳遞給func的參數列表,kwds爲傳遞給func的關鍵字參數列表;
close():關閉Pool,使其不再接受新的任務;
terminate():不管任務是否完成,立即終止;
join():主進程阻塞,等待子進程的退出, 必須在close或terminate之後使用;

子進程如果發生異常並不會產生異常信息
注意:進程池之間想要通信不能使用multiprocessing模塊的Queue,而是使用multiprocessing.Manager()中的Queue(),否則會得到一條如下的錯誤信息:

RuntimeError: Queue objects should only be shared between processes through inheritance.

示例:

# -*- coding:utf-8 -*-

# 修改import中的Queue爲Manager
from multiprocessing import Manager,Pool
import os,time,random

def reader(q):
    print("reader啓動(%s),父進程爲(%s)" % (os.getpid(), os.getppid()))
    for i in range(q.qsize()):
        print("reader從Queue獲取到消息:%s" % q.get(True))

def writer(q):
    print("writer啓動(%s),父進程爲(%s)" % (os.getpid(), os.getppid()))
    for i in "itcast":
        q.put(i)

if __name__=="__main__":
    print("(%s) start" % os.getpid())
    q = Manager().Queue()  # 使用Manager中的Queue
    po = Pool()
    po.apply_async(writer, (q,))

    time.sleep(1)  # 先讓上面的任務向Queue存入數據,然後再讓下面的任務開始從中取數據

    po.apply_async(reader, (q,))
    po.close()
    po.join()
    print("(%s) End" % os.getpid())

結果:

(21424) start
writer啓動(25556),父進程爲(21424)
reader啓動(9808),父進程爲(21424)
reader從Queue獲取到消息:i
reader從Queue獲取到消息:t
reader從Queue獲取到消息:c
reader從Queue獲取到消息:a
reader從Queue獲取到消息:s
reader從Queue獲取到消息:t
(21424) End
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章