多執行緒

Python的多執行緒執行,不是真正的多執行緒同時執行,這是因為Python直譯器引入的GIL-全域性直譯器鎖帶來的制約。Python可以有多執行緒,但是每個執行緒執行的時候都需要等待GIL的釋放,然後獲得GIL後上鎖,在該執行緒執行的時間內,其他執行緒無法執行,因為GIL被鎖住,等該執行緒釋放GIL後,其他執行緒就可以執行了。所以,不管CPU有幾核,在某一個時間只有一個執行緒在執行,這就是為什麼Python的多執行緒有時候被詬病為假多執行緒的原因。

但是,Python多執行緒還是有其用武之地的,就是在I/O密集型的應用裡面,I/O的讀取速度是個瓶頸,可以一個I/O讀取執行緒,一個其他執行緒,這樣就可以利用等待I/O的時間內,並行的進行其他資料處理。

假如程式是計算密集型而不是I/O密集型,Python多執行緒在效能上沒有本質的提高。

Python多執行緒

建立執行緒threading.Thread

使用threading模組,可以通過建立新的threading.Thread併為其分配要執行的函式來啟動新的執行執行緒:

import threading

def Test():
  print "Hello threading!"

my_thread = threading.Thread(target=Test)

target引數引用要執行的函式(或可呼叫物件)。在Thread物件呼叫start之前,執行緒不會開始執行。

開始執行緒Thread.start()

my_thread.start() # prints 'Hello threading!'

現在my_thread已經執行並終止,start再次呼叫將產生一個RuntimeError。如果你想將你的執行緒作為守護程序執行,需要設定daemon=True,或者在呼叫之前設定my_thread.daemonTrue,這就會使的該執行緒在後臺作為守護程序靜默執行。

加入執行緒Thread.join()

如果你將一個大的任務分成幾個小任務並希望同時執行它們,但需要等待所有這些工作完成之後,繼續進行下面的任務,那麼Thread.join()就是你正在尋找的方法。

例如,假設你要下載網站的多個頁面並將其編譯為單個頁面。你可以這樣做:

import requests
from threading import Thread
from queue import Queue

q = Queue(maxsize=20)
def put_page_to_q(page_num):
    q.put(requests.get('http://some-website.com/page_%s.html' % page_num)

def compile(q):
    # magic function that needs all pages before being able to be executed
    if not q.full():
        raise ValueError
    else:
        print("Done compiling!")

threads = []
for page_num in range(20):
     t = Thread(target=requests.get, args=(page_num,))
     t.start()
     threads.append(t)

# Next, join all threads to make sure all threads are done running before
# we continue. join() is a blocking call (unless specified otherwise using 
# the kwarg blocking=False when calling join)
for t in threads:
    t.join()

# Call compile() now, since all threads have completed
compile(q)

建立自定義執行緒類

使用threading.Thread類我們可以子類化新的自定義Thread類。我們必須覆蓋子類中的run方法。

from threading import Thread
import time

class Sleepy(Thread):

    def run(self):
        time.sleep(5)
        print("Hello form Thread")

if __name__ == "__main__":
    t = Sleepy()
    t.start()      # start method automatic call Thread class run method.
    # print 'The main program continues to run in foreground.'
    t.join()
    print("The main program continues to run in the foreground.")

你看到的輸出順序時這樣的

Hello form Thread
The main program continues to run in the foreground.

但假如將t.join()註釋掉的話,輸出順序就變為了

The main program continues to run in the foreground.
Hello form Thread

這也驗證了我們上面結束的join方法時等所有的執行緒執行結束後,才會繼續下面的任務。

執行緒間通訊

假如程式碼中有多個執行緒,有一些資料需要在它們間共享,那麼我們需要在它們之間進行安全的通訊。這需要使用Queuequeue型別。

from queue import Queue
from threading import Thread

# create a data producer 
def producer(output_queue):
    while True:
        data = data_computation()
        
        output_queue.put(data)

# create a consumer
def consumer(input_queue):
    while True:
        # retrieve data (blocking)
        data = input_queue.get()

        # do something with the data

        # indicate data has been consumed
        input_queue.task_done()

使用共享佇列建立生產者和消費者執行緒,

q = Queue()
t1 = Thread(target=consumer, args=(q,))
t2 = Thread(target=producer, args=(q,))
t1.start()
t2.start()