[Python] 多進程編程

python 多進程編程

python中的多線程其實並不是真正的多線程,如果想要充分地使用多核CPU的資源,在python中大部分情況需要使用多進程。Python提供了非常好用的多進程包multiprocessing,只需要定義一個函數,Python會完成其他所有事情。藉助這個包,可以輕鬆完成從單進程到併發執行的轉換。multiprocessing支持子進程、通信和共享數據、執行不同形式的同步,提供了Process、Queue、Pipe、Lock等組件。

1、Process

Process構造函數主要有兩個參數,target表示要運行的函數名,args表示傳遞的參數。

方法:

is_alive()、join([timeout])、run()、start()、terminate()。

run和start的區別:

start()方法
開始線程活動。
對每一個線程對象來說它只能被調用一次,它安排對象在一個另外的單獨線程中調用run()方法(而非當前所處線程)。
當該方法在同一個線程對象中被調用超過一次時,會引入RuntimeError(運行時錯誤)。
run()方法
代表了線程活動的方法。
你可以在子類中重寫此方法。標準run()方法調用了傳遞給對象的構造函數的可調對象作爲目標參數,如果有這樣的參數的話,順序和關鍵字參數分別從args和kargs取得。

所以簡單地說,start就是另外開啓一個進程來運行這個函數,run就是在當前進程下開始這個函數。

import multiprocessing


def worker(value1, value2):
    print(multiprocessing.current_process())

p1 = multiprocessing.Process(target=worker, args=(1, 2,))
p2 = multiprocessing.Process(target=worker, args=(3, 4,))
p1.run()
p2.start()
<_MainProcess(MainProcess, started)>
<Process(Process-2, started)>

daemon 後臺駐留程序

意思就是說,如果主進程運行結束,則結束子進程。

import multiprocessing
import time


def worker(value1, value2):
    time.sleep(1)
    print(multiprocessing.current_process())

p1 = multiprocessing.Process(target=worker, args=(1, 2,))
p2 = multiprocessing.Process(target=worker, args=(3, 4,))
p1.daemon = True
p2.daemon = True
p1.start()
p2.start()
print("end")
end
import multiprocessing
import time


def worker(value1, value2):
    time.sleep(1)
    print(multiprocessing.current_process())

p1 = multiprocessing.Process(target=worker, args=(1, 2,))
p2 = multiprocessing.Process(target=worker, args=(3, 4,))
p1.daemon = True
p2.daemon = True
p1.start()
p2.start()
p1.join()
p2.join()
print("end")
<Process(Process-1, started daemon)>
<Process(Process-2, started daemon)>
end

需要注意的是,如果調用的是run,則會正常輸出,因爲run是在當前進程中運行。

Lock

通過鎖來解決進程間數據同步問題。
可以通過兩種方式來使用Lock。

import multiprocessing
import time


def worker(lock, value2):
    with lock:
        print(lock)
        print(value2)
    lock.acquire()
    print(lock)
    print(value2)
    lock.release()

lock = multiprocessing.Lock()
p1 = multiprocessing.Process(target=worker, args=(lock, 2,))
p2 = multiprocessing.Process(target=worker, args=(lock, 4,))
p1.run()
p2.run()

Semaphore

信號量,和PV原語使用方法是一致的,可以用來控制對共享資源的最大連接數。

import multiprocessing
import time


def worker(s, i):
    s.acquire()
    print(multiprocessing.current_process().name + "acquire")
    time.sleep(i)
    print(multiprocessing.current_process().name + "release\n")
    s.release()

if __name__ == "__main__":
    s = multiprocessing.Semaphore(2)
    for i in range(5):
        p = multiprocessing.Process(target = worker, args=(s, i*2))
        p.start()
Process-1acquire
Process-1release

Process-2acquire
Process-3acquire
Process-2release

Process-4acquire
Process-3release

Process-5acquire
Process-4release

Process-5release

Queue 實現讀者寫者問題

Queue是多進程安全的隊列,可以使用Queue實現多進程之間的數據傳遞。put方法用以插入數據到隊列中,put方法還有兩個可選參數:blocked和timeout。如果blocked爲True(默認值),並且timeout爲正值,該方法會阻塞timeout指定的時間,直到該隊列有剩餘的空間。如果超時,會拋出Queue.Full異常。如果blocked爲False,但該Queue已滿,會立即拋出Queue.Full異常。

get方法可以從隊列讀取並且刪除一個元素。同樣,get方法有兩個可選參數:blocked和timeout。如果blocked爲True(默認值),並且timeout爲正值,那麼在等待時間內沒有取到任何元素,會拋出Queue.Empty異常。如果blocked爲False,有兩種情況存在,如果Queue有一個值可用,則立即返回該值,否則,如果隊列爲空,則立即拋出Queue.Empty異常。Queue的一段示例代碼:

import multiprocessing
import time

def writer_proc(q):
    while True:
        try:
            q.put(1, block = False)
        except:
            pass


def reader_proc(q):
    while True:
        try:
            print q.get(block = False)
        except:
            time.sleep(1)
            pass

if __name__ == "__main__":
    q = multiprocessing.Queue(3)
    writer = multiprocessing.Process(target=writer_proc, args=(q,))
    writer.start()

    reader = multiprocessing.Process(target=reader_proc, args=(q,))
    reader.start()

    reader.join()
    writer.join()

pipe 用於實現IPC(進程間通信)

Pipe方法返回(conn1, conn2)代表一個管道的兩個端。Pipe方法有duplex參數,如果duplex參數爲True(默認值),那麼這個管道是全雙工模式,也就是說conn1和conn2均可收發。duplex爲False,conn1只負責接受消息,conn2只負責發送消息。

send和recv方法分別是發送和接受消息的方法。例如,在全雙工模式下,可以調用conn1.send發送消息,conn1.recv接收消息。如果沒有消息可接收,recv方法會一直阻塞。如果管道已經被關閉,那麼recv方法會拋出EOFError。

import multiprocessing
import time

def proc1(pipe):
    while True:
        for i in xrange(10000):
            print "send: %s" %(i)
            pipe.send(i)
            time.sleep(1)

def proc2(pipe):
    while True:
        print "proc2 rev:", pipe.recv()
        time.sleep(1)

def proc3(pipe):
    while True:
        print "PROC3 rev:", pipe.recv()
        time.sleep(1)

if __name__ == "__main__":
    pipe = multiprocessing.Pipe()
    p1 = multiprocessing.Process(target=proc1, args=(pipe[0],))
    p2 = multiprocessing.Process(target=proc2, args=(pipe[1],))
    #p3 = multiprocessing.Process(target=proc3, args=(pipe[1],))

    p1.start()
    p2.start()
    #p3.start()

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

Pool 進程池

在利用Python進行系統管理的時候,特別是同時操作多個文件目錄,或者遠程控制多臺主機,並行操作可以節約大量的時間。當被操作對象數目不大時,可以直接利用multiprocessing中的Process動態成生多個進程,十幾個還好,但如果是上百個,上千個目標,手動的去限制進程數量卻又太過繁瑣,此時可以發揮進程池的功效。
Pool可以提供指定數量的進程,供用戶調用,當有新的請求提交到pool中時,如果池還沒有滿,那麼就會創建一個新的進程用來執行該請求;但如果池中的進程數已經達到規定最大值,那麼該請求就會等待,直到池中有進程結束,纔會創建新的進程來它。

函數解釋:

  • apply_async(func[, args[, kwds[, callback]]]) 它是非阻塞,apply(func[, args[, kwds]])是阻塞的(理解區別,看例1例2結果區別)
  • close() 關閉pool,使其不在接受新的任務。
  • terminate() 結束工作進程,不在處理未完成的任務。
  • join() 主進程阻塞,等待子進程的退出, join方法要在close或terminate之後使用。

非阻塞

#coding: utf-8
import multiprocessing
import time

def func(msg):
    print "msg:", msg
    time.sleep(3)
    print "end"

if __name__ == "__main__":
    pool = multiprocessing.Pool(processes = 3)
    for i in xrange(4):
        msg = "hello %d" %(i)
        pool.apply_async(func, (msg, ))   #維持執行的進程總數爲processes,當一個進程執行完畢後會添加新的進程進去

    print "Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~"
    pool.close()
    pool.join()   #調用join之前,先調用close函數,否則會出錯。執行完close後不會有新的進程加入到pool,join函數等待所有子進程結束
    print "Sub-process(es) done."
mMsg: hark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~ello 0

msg: hello 1
msg: hello 2
end
msg: hello 3
end
end
end
Sub-process(es) done.

就算是說,主進程不需要等待子進程運行完,主進程可以獨立完成自己的任務。

阻塞

#coding: utf-8
import multiprocessing
import time

def func(msg):
    print "msg:", msg
    time.sleep(3)
    print "end"

if __name__ == "__main__":
    pool = multiprocessing.Pool(processes = 3)
    for i in xrange(4):
        msg = "hello %d" %(i)
        pool.apply(func, (msg, ))   #維持執行的進程總數爲processes,當一個進程執行完畢後會添加新的進程進去

    print "Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~"
    pool.close()
    pool.join()   #調用join之前,先調用close函數,否則會出錯。執行完close後不會有新的進程加入到pool,join函數等待所有子進程結束
    print "Sub-process(es) done."
msg: hello 0
end
msg: hello 1
end
msg: hello 2
end
msg: hello 3
end
Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~
Sub-process(es) done.

主進程必須等待子進程運行完才能夠繼續執行自己的代碼。

進程池中的進程間數據共享

import multiprocessing
import time


def worker(array, value):
    with lock:
        array.append(value)


def init(l):
    global lock
    lock = l

manager = multiprocessing.Manager()
globalArray = manager.list()
l = multiprocessing.Lock()
print(time.clock())
pool = multiprocessing.Pool(5, initializer=init, initargs=(l,))
for i in range(10):
    pool.apply_async(worker, args=(globalArray, i,))
pool.close()
pool.join()
print(len(globalArray))

值得注意的是,manager中是需要加鎖的,具體原因如下:

import multiprocessing
import time


def worker(array, value):
    with lock:
        time.sleep(1 - value / 10)
        array.append(value)


def init(l):
    global lock
    lock = l

manager = multiprocessing.Manager()
globalArray = manager.list()
l = multiprocessing.Lock()
pool = multiprocessing.Pool(5, initializer=init, initargs=(l,))
for i in range(10):
    pool.apply_async(worker, args=(globalArray, i,))
pool.close()
pool.join()
print(globalArray)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
import multiprocessing
import time


def worker(array, value):
    time.sleep(1 - value / 10)
    array.append(value)


def init(l):
    global lock
    lock = l

manager = multiprocessing.Manager()
globalArray = manager.list()
l = multiprocessing.Lock()
pool = multiprocessing.Pool(5, initializer=init, initargs=(l,))
for i in range(10):
    pool.apply_async(worker, args=(globalArray, i,))
pool.close()
pool.join()
print(globalArray)
[1, 0, 2, 3, 4, 5, 6, 7, 8, 9]

可以發現兩種做法的結果是不一樣的,所以是需要進行加鎖的。

文中部分內容引用python多進程編程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章