多進程
創建子進程
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))
python創建子進程是封裝了系統的fork調用。
python中創建跨平臺的多進程應用,使用multiprocessing模塊。
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('child process will start.')
p.start()
p.join()
print('child process end.')
創建子進程,只需要傳入一個執行函數和函數的參數,創建一個process實例用start()方法啓動,比fork()簡單。join()可以等待子進程結束後再繼續往下運行,用於進程間的同步。
Pool -- 進程池
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 run %0.2f seconds.' % (name, (end - start)))
if __name__ == '__main__':
print("Parent process %s.' % os.getpid())
p = Pool(4)
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.')
Pool對象調用join()會等待所有子進程執行完畢,調用join()之前必須先調用close(),調用close()之後就不能繼續添加新的process了。
subprocess -- 啓動一個子進程,控制其輸入和輸出。
子進程需要輸入則使用communicate()方法
進程間通信 -- Queue Pipes
在父進程中創建兩個子進程,一個向queue寫,一個從queuq讀。
from multiprocessing import Process, Queue
import os, time, random
def write(q):
print("Process to write: %s' % os.getpid())
for value in ['A', 'B', 'C']:
print('put %s to queue...' % value)
q.put(value)
time.sleep(random.random())
def read(q):
print('Process to read: %s' % os.getpid())
while True:
value = q.get(True)
print('Get %s from queue.' % value)
if __name__ == '__main__':
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
pw.start()
pr.start()
pw.join()
pr.terminate()
多線程
多線程模塊 _thread
和threading
,threading
是高級模塊,對_thread
進行了封裝。
啓動一個線程就是把一個函數傳入並創建Thread
實例,然後調用start()
開始執行:
import time, threading# 新線程執行的代碼:def loop(): print('thread %s is running...' % threading.current_thread().name) n = 0 while n < 5: n = n + 1 print('thread %s >>> %s' % (threading.current_thread().name, n)) time.sleep(1) print('thread %s ended.' % threading.current_thread().name) print('thread %s is running...' % threading.current_thread().name) t = threading.Thread(target=loop, name='LoopThread') t.start() t.join() print('thread %s ended.' % threading.current_thread().name)
Lock
線程鎖 --
線程是共享進程數據的,爲了保證數據的安全性,必須要使用線程鎖。
import time, threading balance = 0 lock=threading.Lock() def change_it(n): global balance balance = balance+n balance = balance - n def run_thread(n): for i in range(100000): # 先要獲取鎖 lock.acquire() try: change_it(n) # 一定要釋放鎖 finally: lock.release() t1=threading.Thread(target=run_thread,args=(5,)) t2=threading.Thread(target=run_thread,args=(8,)) t1.start() t2.start() t1.join() t2.join() print(balance)
多個線程同時執行lock.acquire()
時,只有一個線程能成功地獲取鎖,然後繼續執行代碼,其他線程就繼續等待直到獲得鎖爲止。
死鎖 -- 由於可以存在多個鎖,不同的線程持有不同的鎖,並試圖獲取對方持有的鎖時,可能會造成死鎖
GIL鎖 -- Global Interpreter Lock 任何Python線程執行前,必須先獲得GIL鎖,然後,每執行100條字節碼,解釋器就自動釋放GIL鎖,讓別的線程有機會執行。這個GIL全局鎖實際上把所有線程的執行代碼都給上了鎖,所以,多線程在Python中只能交替執行。
解決GIL鎖的問題 -- 創建多進程。
ThreadLocal
問題的拋出:線程的全局變量和局部變量。局部變量只有屬於自己的線程才能看到,全局變量的使用必須要加鎖。局部變量的傳遞很麻煩。
解決上述問題使用 -- ThreadLocal
import threading local_school = threading.local() def process_student(): std = local_school.student print("hello, %s (in %s)" % (std, threading.current_thread().name)) def process_thread(name): local_school.student = name process_student() t1 = threading.Thread(target=process_thread, args=("alice",), name='Thread-A') t2 = threading.Thread(target=process_thread,args=("bob",), name='Thread-B') t1.start() t2.start() t1.join() t2.join()
全局變量local_school
就是一個ThreadLocal
對象,每個Thread
對它都可以讀寫student
屬性,但互不影響。你可以把local_school
看成全局變量,但每個屬性如local_school.student
都是線程的局部變量,可以任意讀寫而互不干擾,也不用管理鎖的問題,ThreadLocal
內部會處理。
ThreadLocal
最常用的地方就是爲每個線程綁定一個數據庫連接,HTTP請求,用戶身份信息等,這樣一個線程的所有調用到的處理函數都可以非常方便地訪問這些資源。
異步IO
充分利用操作系統提供的異步IO支持,就可以用單進程單線程模型來執行多任務,這種全新的模型稱爲事件驅動模型
Python中,單進程的異步編程模型稱爲協程,有了協程的支持,就可以基於事件驅動編寫高效的多任務程序。
分佈式進程
線程和進程優選進程,進程更穩定進程可以在各個不同的機器上,線程只能在同一個機器上。
multiprocessing
模塊的managers
子模塊支持把多進程分佈到多臺機器上。
一個服務進程可以作爲調度者,將任務分佈到其他多個進程中,依靠網絡通信。
通過消息隊列Queue基於managers模塊,讓其他機器的進程訪問Queue
服務進程負責啓動Queue
,把Queue
註冊到網絡上,然後往Queue
裏面寫入任務:
# task_master.py import random, time, queue from multiprocessing.manager import BaseManager # 發送任務的隊列: tast_queue = queue.Queue() # 接收結果的隊列 result_queue = queue.Queue() # 從BaseManager繼承的QueueManager: class QueueManager(BaseManager): pass # 把兩個Queue都註冊到網絡上, callable參數關聯了Queue對象: QueueManager.register('get_task_queue', callable=lambda:task_queue) QueueManager.register('get_result_queue', callable=lambda: result_queue) # 綁定端口5000, 設置驗證碼'abc': manager = QueueManager(address=('', 5000), authkey=b'abc') # 啓動Queue: manager.start() # 獲得通過網絡訪問的Queue對象: task = manager.get_task_queue() result = manager.get_result_queue() # 放幾個任務進去: for i in range(10): n = random.randint(0, 10000) print('put task %d...' % n) task.put(n) # 從result隊列讀取結果: print('Try get results...') for i in range(10): r = result.get(timeout=10) print('Result: %s' % r) # 關閉: manager.shutdown() print('master exit.')
分佈式多進程環境下,添加任務到Queue不可以直接對原始的task_queue進行操作,必須通過manager.get_task_queue()
獲得的Queue
接口添加。
# task_worker.py import time, sys, queue from multiprocessing.managers import BaseManager # 創建類似的QueueManager: class QueueManager(BaseManager): pass # 由於這個QueueManager只從網絡上獲取Queue,所以註冊時只提供名字: QueueManager.register('get_task_queue') QueueManager.register('get_result_queue') # 連接到服務器,也就是運行task_master.py的機器: server_addr = '127.0.0.1' print('Connect to server %s...' % server_addr) # 端口和驗證碼注意保持與task_master.py設置的完全一致: m = QueueManager(address=(server_addr, 5000), authkey=b'abc') # 從網絡連接: m.connect() # 獲取Queue的對象: task = m.get_task_queue() result = m.get_result_queue() # 從task隊列取任務,並把結果寫入result隊列: for i in range(10): try: n = task.get(timeout=1) print('run task %d * %d...' % (n, n)) r = '%d * %d = %d' % (n, n, n*n) time.sleep(1) result.put(r) except Queue.Empty: print('task queue is empty.') # 處理結束: print('worker exit.')
Queue
能通過網絡訪問,就是通過QueueManager
實現的。由於QueueManager
管理的不止一個Queue
,所以,要給每個Queue
的網絡調用接口起個名字,比如get_task_queue
。
authkey
有什麼用?這是爲了保證兩臺機器正常通信,不被其他機器惡意干擾。如果task_worker.py
的authkey
和task_master.py
的authkey
不一致,肯定連接不上。