在 Python 中,你可以啓動一個線程,但卻無法停止它。
目錄
2.1 Thread 2.2 Thraading 2.3 Queue
1 介紹
在多線程(multithreaded,MT)編程出現之前,計算機程序的執行是由單個步驟序列組成的,該序列在主機的 CPU 中按照同步順序執行。讓這些獨立的任務同時運行,就是多線程編程。多線程本質上是異步的。
使用多線程編程,以及類似 Queue 的共享數據結構,這個編程任務可以規劃成幾個執行特定函數的線程:
• UserRequestThread:負責讀取客戶端輸入,該輸入可能來自 I/O 通道。程序將創建多個線程,每個客戶端一個,客戶端的請求將會被放入隊列中。
• RequestProcessor:該線程負責從隊列中獲取請求並進行處理,爲第3 個線程提供輸出。
• ReplyThread:負責向用戶輸出,將結果傳回給用戶(如果是網絡應用),或者把數據寫到本地文件系統或數據庫中。
1)進程
計算機程序只是存儲在磁盤上的可執行二進制(或其他類型)文件。只有把它們加載到內存中並被操作系統調用,才擁有其生命期。進程(有時稱爲重量級進程)則是一個執行中的程序。
2)線程
線程(有時候稱爲輕量級進程)與進程類似,不過它們是在同一個進程下執行的,並共享相同的上下文。線程包括開始、執行順序和結束三部分。它有一個指令指針,用於記錄當前運行的上下文。當其他線程運行時,它可以被搶佔(中斷)和臨時掛起(也稱爲睡眠)——這種做法叫做讓步(yielding)。
一個進程中的各個線程與主線程共享同一片數據空間,因此相比於獨立的進程而言,線程間的信息共享和通信更加容易。線程一般是以併發方式執行的,使得多任務間的協作成爲可能。
3)Python中的線程
Python 代碼的執行是由 Python 虛擬機(又名解釋器主循環)進行控制的。在主循環中同時只能有一個控制線程在執行,對 Python 虛擬機的訪問是由全局解釋器鎖(GIL)控制的。這個鎖就是用來保證同時只能有一個線程運行的。
在多線程環境中,Python 虛擬機將按照下面所述的方式執行。
1.設置 GIL。
2.切換進一個線程去運行。
3.執行下面操作之一。
a.指定數量的字節碼指令。
b.線程主動讓出控制權(可以調用 time.sleep(0)來完成)。
4.把線程設置回睡眠狀態(切換出線程)。
5.解鎖 GIL。
6.重複上述步驟。
當一個線程完成函數的執行時,它就會退出。另外,還可以通過調用諸如 thread.exit()之類的退出函數,或者 sys.exit()之類的退出 Python 進程的標準方法,亦或者拋出 SystemExit異常,來使線程退出。不過,你不能直接“終止”一個線程。
2 多線程模塊
thread 模塊提供了基本的線程和鎖定支持;而 threading 模塊提供了更高級別、功能更全面的線程管理。使用 Queue 模塊,用戶
可以創建一個隊列數據結構,用於在多線程之間進行共享。
2.1 Thread
******************************注:避免使用 thread 模塊,推薦使用更高級別的 threading 模塊。******************************
thread 模塊提供了除了派生線程外,還提供了基本的同步數據結構,稱爲鎖對象(lock object,也叫原語鎖、簡單鎖、互斥鎖、互斥和二進制信號量)。
thread 2.0 模塊和鎖對象:
https://docs.python.org/3.6/library/_thread.html#module-_thread
核心函數是 start_new_thread()。它的參數包括函數(對象)、函數的參數以及可選的關鍵字參數。(要執行的函數不需要參數,也需要傳遞一個空元組。)將專門派生新的線程來調用這個函數。
簡單多線程機制:
2.2 Thraading
threading 模塊的對象:
threading 模塊的 Thread 類是主要的執行對象:
import threading
from time import sleep,ctime
loops =[4,2]
def loop(nloop,nsec):
print('Start loop',nloop,' at: ',ctime())
sleep(nsec)
print('End loop',nloop,' at: ',ctime())
def main():
print('Project starting at :',ctime())
threads =[]
nloops = range(len(loops))
for i in nloops:
t = threading.Thread(target=loop,args=(i,loops[i]))
threads.append(t)
for i in nloops:
threads[i].start() #開始所有的線程
for i in nloops:
threads[i].join() #等待所有的線程結速
print('All Done at: ',ctime())
if __name__ == '__main__':
main()
實例化 Thread(調用 Thread())和調用 thread.start_new_thread()的最大區別是新線程不會立即開始執行。這是一個非常有用的同步功能。通過調用每個線程的 start()方法讓它們開始執行,相比於管理一組鎖(分配、獲取、釋放、檢查鎖狀態等)而言,調用 join()方法即可。
子類化的 Thread:
import threading
from time import ctime,sleep
class MyThread(threading.Thread): #子類化
def __init__(self,func,args,name=''):
threading.Thread.__init__(self)
self.name = name
self.func =func
self.args =args
def getResult(self):
return self.res
def run(self):
print('Starting',self.name,' at: ',ctime())
self.res = self.func(*self.args)
print(self.name,' finished at: ',ctime())
def loop(nloop,nsec):
print('Start loop',nloop,' at: ',ctime())
sleep(nsec)
print('End loop',nloop,' at: ',ctime())
def main():
loops =(1,2)
print('Project starting at :',ctime())
threads =[]
nloops = range(len(loops))
for i in nloops:
t = MyThread(loop,(i,loops[i]),loop.__name__)
threads.append(t)
for i in nloops:
threads[i].start() #開始所有的線程
for i in nloops:
threads[i].join() #等待所有的線程結速
print('All Done at: ',ctime())
if __name__ == '__main__':
main()
2.3 Queue
在生產者-消費者模型中,生產商品的時間是不確定的,同樣消費者消費生產者生產的商品的時間也是不確定的。
使用 Queue 模塊來提供線程間通信的機制,從而讓線程之間可以互相分享數據。具體而言,就是創建一個隊列,讓生產者(線程)在其中放入新的商品,而消費者(線程)消費這些商品。
import queue
import time,threading
q=queue.Queue(5)
def product(name):
count=1
while True:
q.put('書籍{}'.format(count))
print ('{}生產第{}本書籍'.format(name,count))
print('Size now ',q.qsize())
count+=1
time.sleep(4)
def consume(name):
while True:
print ('{}購買了第{}本書籍'.format(name,q.get()))
print('Size now ',q.qsize())
time.sleep(2)
q.task_done()
3 多線程實踐
1)同步原語
多線程編程中一個非常重要的方面:同步。在多線程代碼中,總會有一些特定的函數或代碼塊不希望(或不應該)被多個線程同時執行。
當 任 意 數 量 的 線 程 可 以 訪 問 臨 界 區 的 代 碼,但在給定的時刻只有一個線程可以通過時,就是使用同步的時候了。
其中兩種類型的同步原語:鎖/互斥,以及信號量
2)鎖
當多線程爭奪鎖時,允許第一個獲得鎖的線程進入臨界區,並執行代碼。所有之後到達的線程將被阻塞,直到第一個線程執行結束,退出臨界區,並釋放鎖。此時,其他等待的線程可以獲得鎖並進入臨界區。
方案一:調用鎖的 acquire()和 release()
def loop(nsec):
myname = currentThread().name
lock.acquire()
remaining.add(myname)
lock.release()
sleep(nsec)
lock.acquire()
remaining.remove(myname)
lock.release()
方案二:使用上下文管理
使用 with 語句,此時每個對象的上下文管理器負責在進入該套件之前調用 acquire()並在完成執行之後調用 release()。
def loop(nsec):
myname = currentThread().name
with lock:
remaining.add(myname)
sleep(nsec)
with lock:
remaining.remove(myname)
3)信號量
情況更加複雜時,可能需要一個更強大的同步原語來代替鎖。信號量是最古老的同步原語之一。它是一個計數器,當資源消耗時遞減,當資源釋放時遞增。
from threading import BoundedSemaphore,Lock,Thread
lock = Lock()
MAX =5
candy = BoundedSemaphore(MAX)
def buy():
lock.acquire()
try:
candy.release()
except ValueError:
print('Full')
else:
print('OK')
lock.realease()
def sell():
lock.acquire()
if candy.acquire(False):
print('OK')
else:
print('Empty')
lock.realease()
4 線程的替代方案
subprocess 模塊
這是派生進程的主要替代方案,可以單純地執行任務,或者通過標準文件(stdin、stdout、stderr)進行進程間通信。
multiprocessing 模塊
允許爲多核或多 CPU 派生進程,其接口與 threading模塊非常相似。該模塊同樣也包括在共享任務的進程間傳輸數據的多種方式。
concurrent.futures 模塊
這是一個新的高級庫,它只在“任務”級別進行操作,也就是說,你不再需要過分關注同步和線程/進程的管理了。
參考文獻:《Python核心編程》