精通Python——多線程編程學習筆記

在 Python 中,你可以啓動一個線程,但卻無法停止它。

目錄

1 介紹

2 多線程模塊

        2.1 Thread                2.2 Thraading                2.3 Queue

3 多線程實踐

1)同步原語              2)鎖                              3)信號量

4 線程的替代方案


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核心編程》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章