對於操作系統來說,一個任務就是一個進程(Process),比如打開一個瀏覽器就是啓動一個瀏覽器進程,打開一個記事本就啓動了一個記事本進程,打開兩個記事本就啓動了兩個記事本進程,打開一個Word就啓動了一個Word進程。
有些進程還不止同時幹一件事,比如Word,它可以同時進行打字、拼寫檢查、打印等事情。在一個進程內部,要同時幹多件事,就需要同時運行多個“子任務”,我們把進程內的這些“子任務”稱爲線程(Thread)。
進程
Python的os
模塊封裝了常見的系統調用,其中包括fork
,可以在Python程序中輕鬆創建子進程:
import os
print('Process (%s) start...' % os.getpid())
# Only works on Unix/Linux/Mac:
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 (876) start...
I (876) just created a child process (877).
I am child process (877) and my parent is 876.
複製代碼
由於Windows沒有fork
調用,上面的代碼在Windows上無法運行。由於Mac系統是基於BSD(Unix的一種)內核,所以,在Mac下運行是沒有問題的,推薦大家用Mac學Python!
multiprocessing
如果你打算編寫多進程的服務程序,Unix/Linux無疑是正確的選擇。由於Windows沒有fork
調用,難道在Windows上無法用Python編寫多進程的程序?
由於Python是跨平臺的,自然也應該提供一個跨平臺的多進程支持。multiprocessing
模塊就是跨平臺版本的多進程模塊。
multiprocessing
模塊提供了一個Process
類來代表一個進程對象,下面的例子演示了啓動一個子進程並等待其結束:
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.')
複製代碼
執行結果如下:
Parent process 928.
Process will start.
Run child process test (929)...
Process end.
複製代碼
創建子進程時,只需要傳入一個執行函數和函數的參數,創建一個Process
實例,用start()
方法啓動,這樣創建進程比fork()
還要簡單。
join()
方法可以等待子進程結束後再繼續往下運行,通常用於進程間的同步。
線程
Python的標準庫提供了兩個模塊:_thread
和threading
,_thread
是低級模塊,threading
是高級模塊,對_thread
進行了封裝。絕大多數情況下,我們只需要使用threading
這個高級模塊。
啓動一個線程就是把一個函數傳入並創建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)
複製代碼
執行結果如下:
thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
thread MainThread ended.
複製代碼
Lock
多線程和多進程最大的不同在於,多進程中,同一個變量,各自有一份拷貝存在於每個進程中,互不影響,而多線程中,所有變量都由所有線程共享,所以,任何一個變量都可以被任何一個線程修改,因此,線程之間共享數據最大的危險在於多個線程同時改一個變量,把內容給改亂了。
balance = 0
lock = threading.Lock()
def run_thread(n):
for i in range(100000):
# 先要獲取鎖:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要釋放鎖:
lock.release()
複製代碼
當多個線程同時執行lock.acquire()
時,只有一個線程能成功地獲取鎖,然後繼續執行代碼,其他線程就繼續等待直到獲得鎖爲止。
獲得鎖的線程用完後一定要釋放鎖,否則那些苦苦等待鎖的線程將永遠等待下去,成爲死線程。所以我們用try...finally
來確保鎖一定會被釋放。
ThreadLocal
import threading
# 創建全局ThreadLocal對象:
local_school = threading.local()
def process_student():
# 獲取當前線程關聯的student:
std = local_school.student
print('Hello, %s (in %s)' % (std, threading.current_thread().name))
def process_thread(name):
# 綁定ThreadLocal的student:
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()
複製代碼
執行結果:
Hello, Alice (in Thread-A)
Hello, Bob (in Thread-B)
複製代碼
全局變量local_school
就是一個ThreadLocal
對象,每個Thread
對它都可以讀寫student
屬性,但互不影響。你可以把local_school
看成全局變量,但每個屬性如local_school.student
都是線程的局部變量,可以任意讀寫而互不干擾,也不用管理鎖的問題,ThreadLocal
內部會處理。
可以理解爲全局變量local_school
是一個dict
,不但可以用local_school.student
,還可以綁定其他變量,如local_school.teacher
等等。
ThreadLocal
最常用的地方就是爲每個線程綁定一個數據庫連接,HTTP請求,用戶身份信息等,這樣一個線程的所有調用到的處理函數都可以非常方便地訪問這些資源。