Python的進程

進程

說明:本文是基於Py2.X環境,

Python實現多進程的方式主要有兩種:一種方法是使用os模塊中的fork方法; 另一種是使用multiprocessing模塊。這兩種方法的區別在於前者僅適用於Unix/Linux操作操作。對win是不支持的,而後者則是跨平臺的實現方式。

使用os模塊中的fork方式實現多進程。

Unix/Linux操作系統提供了一個fork()系統調用,它非常特殊。普通的函數調用,調用一次,返回一次,但是fork()調用一次,返回兩次,因爲操作系統自動把當前進程(稱爲父進程)複製了一份(稱爲子進程),然後,分別在父進程和子進程內返回。

子進程永遠返回0,而父進程返回子進程的ID。這樣做的理由是,一個父進程可以fork出很多子進程,所以,父進程要記下每個子進程的ID,而子進程只需要調用getppid()就可以拿到父進程的ID。

Python的os模塊封裝了常見的系統調用,其中就包括fork,可以在Python程序中輕鬆創建子進程:

import os
print 'Process (%s) start...' % os.getpid()
pid = os.fork()
if pid == 0:
    print 'I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid())
else:
    print 'I (%s) just created a child process (%s).' % (os.getpid(), pid)
得到:
Process (2450) start...
I (2450) just created a child process (2451).
I am child process (2451) and my parent is 2450.

使用Multiprocessing查模塊創建多進程。

multiprocessing模塊提供了一個Process類來描述一個進程對象,創建子進程時,只需要傳入一個執行函數和函數的參數即可完成一個Process實例的創建,用start()方法啓動進程,用join()方法實現進程間的同步。join()方法可以等待子進程結束後再繼續往下運行,通常用於進程間的同步。

# -*- coding:utf-8 -*-
from multiprocessing import Process
import os
# 子進程要執行的代碼
def run_proc(name):
    print 'Run child process %s (%s)...' % (name, os.getpid())
if __name__ == '__main__':
    print 'Parent process %s.' % os.getpid()
    p = Process(target=run_proc, args=('test',))
    print 'Process will start.'
    p.start()
    p.join()
    print 'Process end.'
得到:
Parent process 2533.
Process will start.
Run child process test (2534)...
Process end.

multiprocessing模塊提供了一個pool類來代表進程池對象

Pool可以提供指定數量的進程供用戶調用,默認大小是cpu的核數,當有新的請求提交到pool中時,如果池還沒有滿,那麼就會創建一個新的進程用來執行該請求,但如果池的進程數已經達到規定最大值,那麼該請求就會等待,直到池中有進程結束纔會創建新的進程來處理它。

# -*- coding:utf-8 -*-
from multiprocessing import Pool
import os, time, random
def long_time_task(name):
    print 'Run task %s (%s)...' % (name, os.getpid())
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print 'Task %s runs %0.2f seconds.' % (name, (end - start))
if __name__ == '__main__':
    print 'Parent process %s.' % os.getpid()
    p = Pool()
    for i in range(5):
        p.apply_async(long_time_task, args=(i,))
    print 'Waiting for all subprocesses done...'
    p.close()
    p.join()
    print 'All subprocesses done.'
得到: 
Parent process 2541.
Waiting for all subprocesses done...
Run task 0 (2543)...
Run task 1 (2544)...
Run task 2 (2545)...
Run task 3 (2546)...
Task 0 runs 0.02 seconds.
Run task 4 (2543)...
Task 2 runs 0.60 seconds.
Task 4 runs 1.18 seconds.
Task 3 runs 1.26 seconds.
Task 1 runs 1.66 seconds.
All subprocesses done.

對Pool對象調用join()方法會等待所有子進程執行完畢,調用join()之前必須先調用close(),調用close()之後就不能繼續添加新的Process了。

進程間的通信

Process之間肯定是需要通信的,操作系統提供了很多機制來實現進程間的通信。Python的multiprocessing模塊包裝了底層的機制,提供了Queue、Pipes等多種方式來交換數據。兩者的區別在於Pipe常用於兩個進程間的通訊而Queue用於多個進程間實現通訊。

Queue通訊

Queue是多進程安全的隊列,可以使用Queue實現多進程之間的數據傳輸,有兩個方法:put和get進行Queue操作。

  • put方法用以插入數據隊列中它可以有兩個可選參數:blocked和timeout,如果blocked爲True(默認值)並且timeout是正值,該方法會阻塞timeout指定的時間,直到該隊列有剩餘空間,如果超時,會拋出Queue.Full異常,如果blocked爲False,但該Queue已滿,則會立即拋出Queue.Full異常。
  • Get方法用以從隊列讀取並且刪除一個元素。它可以有兩個可選參數:blocked和timeout,如果blocked爲True(默認值)並且timeout是正值,那麼在等待時間內沒有取到任何元素會拋出Queue.Empty異常,如果blocked爲False,分兩種情況:如果Queue有一個值木口月禾,則立即返回該值,否則如果隊列爲空,則立即拋出Queuq.Empty異常。
# -*- coding:utf-8 -*-
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:
        value = q.get(True)
        print 'Get %s from queue.' % value
if __name__ == '__main__':
    # 父進程創建Queue,並傳給各個子進程:
    q = Queue()
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    # 啓動子進程pw,寫入:
    pw.start()
    # 啓動子進程pr,讀取:
    pr.start()
    # 等待pw結束:
    pw.join()
    # pr進程裏是死循環,無法等待其結束,只能強行終止:
    pr.terminate()
得到:
Put A to queue...
Get A from queue.
Put B to queue...
Get B from queue.
Put C to queue...
Get C from queue.
Pipes通訊

Pipe常用來在兩個進程間進行通信,兩個進程分別位於管道的兩端。

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 random
import time, os
def proc_send(pipe, urls):
    for url in urls:
        print "process(%s) send:%s" % (os.getpid(), url)
        pipe.send(url)
        time.sleep(random.random())
def proc_recv(pipe):
    while True:
        print "Process(%s) rev:%s" % (os.getpid(), pipe.recv())
        time.sleep(random.random())
if __name__ == "__main__":
    pipe = multiprocessing.Pipe()
    p1 = multiprocessing.Process(target=proc_send,args=(pipe[0],['url_'+str(i) for i in range(10)]))
    p2 = multiprocessing.Process(target=proc_recv,args=(pipe[1],))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
得到:
process(1134) send:url_0
Process(1135) rev:url_0
process(1134) send:url_1
Process(1135) rev:url_1
process(1134) send:url_2
Process(1135) rev:url_2
process(1134) send:url_3
Process(1135) rev:url_3
process(1134) send:url_4
Process(1135) rev:url_4
process(1134) send:url_5
Process(1135) rev:url_5
process(1134) send:url_6
Process(1135) rev:url_6
process(1134) send:url_7
Process(1135) rev:url_7
process(1134) send:url_8
Process(1135) rev:url_8
process(1134) send:url_9
Process(1135) rev:url_9

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