Python學習之程式和執行緒

stonezhu發表於2018-06-24

Python學習目錄

  1. 在Mac下使用Python3
  2. Python學習之資料型別
  3. Python學習之函式
  4. Python學習之高階特性
  5. Python學習之函數語言程式設計
  6. Python學習之模組
  7. Python學習之物件導向程式設計
  8. Python學習之物件導向高階程式設計
  9. Python學習之錯誤除錯和測試
  10. Python學習之IO程式設計
  11. Python學習之程式和執行緒
  12. Python學習之正則
  13. Python學習之常用模組
  14. Python學習之網路程式設計

對於作業系統來說,一個任務就是一個程式(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的標準庫提供了兩個模組:_threadthreading_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請求,使用者身份資訊等,這樣一個執行緒的所有呼叫到的處理函式都可以非常方便地訪問這些資源。

下一篇:Python學習之正則

相關文章