原文: "http://codingpy.com/article/python 201 a tutorial on threads/" 創建多線程 創建多線程主要有2種方式。 使用threading.Thread函數 繼承threading類 1. 使用threading.Thread函數 impo ...
原文:http://codingpy.com/article/python-201-a-tutorial-on-threads/
創建多線程
創建多線程主要有2種方式。
- 使用threading.Thread函數
- 繼承threading類
1. 使用threading.Thread函數
import threading
def tom(number):
print threading.currentThread().getName()
print number
if __name__ == "__main__":
number = ["zero", "one", "two", "three", "four"]
sex = ["man", "woman"]
for i in range(5):
th = threading.Thread(target=tom, args=(number[i],))
# th.setName('mythread')
# print th.getName()
th.start()
說明:Thread()函數有2個參數,一個是target,內容為子線程要執行的函數名稱;另一個是args,內容為需要傳遞的參數。Args 參數看起來有些奇怪,那是因為我們需要傳遞一個序列給tom函數,但它只接受一個變數,所以我們把逗號放在尾部來創建只有一個參數的序列。創建完子線程,將會返回一個對象,調用對象的start方法,可以啟動子線程。
當你運行以上這段代碼,會得到以下輸出:
Thread-1
zero
Thread-2
one
Thread-3
two
Thread-4
three
Thread-5
four
線程對象的方法:
Start() 開始線程的執行
Run() 定義線程的功能的函數
Join(timeout=None) 程式掛起,直到線程結束;如果給了timeout,則最多阻塞timeout秒
getName() 返回線程的名字
setName() 設置線程的名字
isAlive() 布爾標誌,表示這個線程是否還在運行
isDaemon() 返回線程的daemon標誌
setDaemon(daemonic) 把線程的daemon標誌設為daemonic(一定要在start()函數前調用)
t.setDaemon(True) 把父線程設置為守護線程,當父進程結束時,子進程也結束
2. 繼承threading類
import threading
class mythread(threading.Thread):
def __init__(self,number):
threading.Thread.__init__(self)
self.number = number
def run(self):
print threading.current_thread().getName()
print self.number
if __name__ == "__main__":
for i in range(5):
th = mythread(i)
th.start()
當你運行以上這段代碼,會得到以下輸出:
Thread-1
0
Thread-2
1
Thread-3
2
Thread-4
3
Thread-5
4
當然,通常情況下你不會希望輸出列印到標準輸出。如果不幸真的這麼做了,那麼最終的顯示效果將會非常混亂。你應該使用 Python 的 logging 模塊。它是線程安全的,並且表現出色。讓我們用 logging 模塊修改上面的例子並且給我們的線程命名。代碼如下:
import threading
import logging
def get_logger():
#創建一個被設置為調試級別的日誌記錄器
logger = logging.getLogger("mylogger")
logger.setLevel(logging.DEBUG)
#設置每行日誌的格式。格式包括時間戳、線程名、日誌記錄級別以及日誌信息
fh = logging.FileHandler("threading.log")
fmt = '%(asctime)s - %(threadName)s - %(levelname)s - %(message)s'
formatter = logging.Formatter(fmt)
fh.setFormatter(formatter)
logger.addHandler(fh)
return logger
def tom(number, logger):
logger.debug(number)
if __name__ == "__main__":
logger = get_logger()
number = ["zero", "one", "two", "three", "four"]
sex = ["man", "woman"]
for i in range(5):
th = threading.Thread(target=tom, args=(number[i],logger))
# th.setName('mythread')
# print th.getName()
th.start()
通過繼承的方法:
import threading
import logging
class mythread(threading.Thread):
def __init__(self,number,logger):
threading.Thread.__init__(self)
self.number = number
self.logger = logger
def run(self):
self.logger.debug("calling-thread")
tom(self.number, self.logger)
def get_logger():
logger = logging.getLogger("mylogger")
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler("threading.log")
fmt = '%(asctime)s - %(threadName)s - %(levelname)s - %(message)s'
formatter = logging.Formatter(fmt)
fh.setFormatter(formatter)
logger.addHandler(fh)
return logger
def tom(number, logger):
if __name__ == "__main__":
logger = get_logger()
for i in range(5):
th = mythread(i, logger)
th.start()
在 tom 函數中,我們把 print 語句換成 logging 語句。你會註發現,在創建線程時,我們給 doubler 函數傳入了 logger 對象。這樣做的原因是,如果在每個線程中實例化 logging 對象,那麼將會產生多個 logging 單例(singleton),並且日誌中將會有很多重覆的內容
線程鎖與線程同步
由於物理上得限制,各CPU廠商在核心頻率上的比賽已經被多核所取代。為了更有效的利用多核處理器的性能,就出現了多線程的編程方式,而隨之帶來的就是線程間數據一致性和狀態同步的困難。解決多線程之間數據完整性和狀態同步的最簡單方法自然就是加鎖。鎖由 Python 的 threading 模塊提供,並且它最多被一個線程所持有。當一個線程試圖獲取一個已經鎖在資源上的鎖時,該線程通常會暫停運行,直到這個鎖被釋放。
有兩種方式為線程加鎖:
try...finally
with
代碼如下:
import threading
import logging
lock = threading.Lock()
class mythread(threading.Thread):
def __init__(self,number,logger):
threading.Thread.__init__(self)
self.number = number
self.logger = logger
def run(self):
lock.acquire()
try:
self.logger.debug("calling-thread")
tom(self.number, self.logger)
finally:
lock.release()
def get_logger():
logger = logging.getLogger("mylogger")
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler("threading.log")
fmt = '%(asctime)s - %(threadName)s - %(levelname)s - %(message)s'
formatter = logging.Formatter(fmt)
fh.setFormatter(formatter)
logger.addHandler(fh)
return logger
def tom(number, logger):
with lock:
logger.debug(number)
if __name__ == "__main__":
logger = get_logger()
for i in range(5):
with lock:
th = mythread(i, logger)
th.start()
當你真正運行這段代碼時,你會發現它只是掛起了。究其原因,是因為我們只告訴 threading 模塊獲取鎖。所以當我們調用第一個函數時,它發現鎖已經被獲取,隨後便把自己掛起了,直到鎖被釋放,然而這將永遠不會發生。
真正的解決辦法是使用重入鎖(Re-Entrant Lock)。threading 模塊提供的解決辦法是使用 RLock 函數。即把 lock = threading.lock() 替換為 lock = threading.RLock(),然後重新運行代碼,現在代碼就可以正常運行了。
線程通信
某些情況下,你會希望線程之間互相通信。就像先前提到的,你可以通過創建 Event 對象達到這個目的。但更常用的方法是使用隊列(Queue)。在我們的例子中,這兩種方式都會有所涉及。下麵讓我們看看到底是什麼樣子的:
import threading
import Queue
def creator(data, q):
"""
生成用於消費的數據,等待消費者完成處理
"""
print('Creating data and putting it on the queue')
for item in data:
evt = threading.Event()
q.put((item, evt))
print('Waiting for data to be doubled')
evt.wait()
def my_consumer(q):
"""
消費部分數據,並做處理
這裡所做的只是將輸入翻一倍
"""
while True:
data, evt = q.get()
print('data found to be processed: {}'.format(data))
processed = data * 2
print(processed)
evt.set()
q.task_done()
if __name__ == '__main__':
q = Queue()
data = [5, 10, 13, -1]
thread_one = threading.Thread(target=creator, args=(data, q))
thread_two = threading.Thread(target=my_consumer, args=(q,))
thread_one.start()
thread_two.start()
q.join()
讓我們掰開揉碎分析一下。首先,我們有一個創建者(creator)函數(亦稱作生產者(producer)),我們用它來創建想要操作(或者消費)的數據。然後用另外一個函數 my_consumer 來處理剛纔創建出來的數據。Creator 函數使用 Queue 的 put 方法向隊列中插入數據,消費者將會持續不斷的檢測有沒有更多的數據,當發現有數據時就會處理數據。Queue 對象處理所有的獲取鎖和釋放鎖的過程,這些不用我們太關心。
在這個例子中,先創建一個列表,然後創建兩個線程,一個用作生產者,一個作為消費者。你會發現,我們給兩個線程都傳遞了 Queue 對象,這兩個線程隱藏了關於鎖處理的細節。隊列實現了數據從第一個線程到第二個線程的傳遞。當第一個線程把數據放入隊列時,同時也傳遞一個 Event 事件,緊接著掛起自己,等待該事件結束。在消費者側,也就是第二個線程,則做數據處理工作。當完成數據處理後就會調用 Event 事件的 set 方法,通知第一個線程已經把數據處理完畢了,可以繼續生產了。
最後一行代碼調用了 Queue 對象的 join 方法,它會告知 Queue 等待所有線程結束。當第一個線程把所有數據都放到隊列中,它也就運行結束了。