【Python3學習筆記】之【Python高階——多執行緒】

Rookie8j發表於2020-10-03

多執行緒

優點

  • 實現執行緒可以把佔據長時間的程式中的任務放到後臺去處理。
  • 使用者介面可以更加吸引人,比如使用者點選了一個按鈕去觸發某些事件的處理,可以彈出一個進度條來顯示處理進度。
  • 程式執行速度更快。
  • 在一些等待的任務實現如使用者輸入、檔案讀寫和網路收發資料等,執行緒就比較有用。在這種情況下我們可以釋放一些珍貴的資源如記憶體佔用等。

注意

每個獨立的執行緒有一個程式的入口順序執行序列程式出口。但是執行緒不能獨立執行,必須依存在應用程式中,由應用程式提供多個執行緒執行控制。

每個執行緒都有他自己的一組CPU暫存器,稱為執行緒上下文,改上下文反映了執行緒上次執行該執行緒的CPU暫存器狀態。

指令指標堆疊指標暫存器是執行緒上下文的兩個重要暫存器,執行緒總是在程式得到上下文中執行的,這些地址都用於標誌 擁有執行緒 的 程式地址空間 的記憶體。

  • 執行緒可以被搶佔(中斷)
  • 在其它執行緒正在執行時,執行緒可以暫時擱置(睡眠)。這就是執行緒的退讓。

分類

執行緒可以分為:

  • 核心執行緒:由作業系統核心建立和撤銷
  • 使用者執行緒:不需要核心支援,在使用者程式中實現。

相關模組

執行緒中常用的模組為

  • _thread
  • threading
    thread 模組已被廢棄。可以用threading 模組代替。所以在python中不在使用thread模組。但是為了相容性,將thread 重新命名為了“_thread”。

開始學習執行緒

python中有有兩種方式使用執行緒:函式或者類來包裝執行緒物件。

函式式(_thread模組演示)

用_thread 模組中的start_new_thread() 函式來產生新執行緒。語法如下:

_thread.start_new_thread ( function, args[, kwargs] )

引數說明

  • function:執行緒函式
  • args-傳遞給執行緒函式的引數(必須是tuple型別)
  • kwargs:可選引數
import _thread
import time


# 為執行緒定義一個函式
def print_time(thread_name, sleep_time):
    count = 0
    while count < 5:
        time.sleep(sleep_time)
        count += 1
        print('{}:{}'.format(thread_name, time.ctime(time.time())))


# 建立兩個執行緒
try:
    _thread.start_new_thread(print_time, ('thread-1', 2))
    _thread.start_new_thread(print_time, ('thread-2', 4))
except Exception as e:
    print(e)

while 1:
    pass
thread-1:Fri Oct  2 19:11:16 2020
thread-2:Fri Oct  2 19:11:18 2020
thread-1:Fri Oct  2 19:11:18 2020
thread-1:Fri Oct  2 19:11:20 2020
thread-2:Fri Oct  2 19:11:22 2020
thread-1:Fri Oct  2 19:11:22 2020
thread-1:Fri Oct  2 19:11:24 2020
thread-2:Fri Oct  2 19:11:26 2020
thread-2:Fri Oct  2 19:11:30 2020
thread-2:Fri Oct  2 19:11:34 2020

threading 模組

python 通過兩個標準庫 _threadthreading 提供對執行緒的支援。

_thread 提供了低階別的、原始的執行緒以及一個簡單的索,它相比於threading 模組的功能還是比較有限的。

threading 模組除了包含 _thread 模組中的 多有方法外,還提供了:

  • threading.currentThread():返回當前執行緒變數。
  • threading.enumerate():返回一個包含正在執行的執行緒list。正在執行指執行緒啟動後、結束前,不包括啟動前和終止後的執行緒
  • threading.activeCount():返回正在執行的執行緒數量,與len(threading.enumerate())有相同的結果

除了使用方法外,執行緒模組同樣提供了Thread 類來處理執行緒,Thread 類提供了以下方法:

  • run():用以表示執行緒活動的方法。
  • start():啟動執行緒活動。
  • join([time]):等待至執行緒中止。這阻塞呼叫執行緒直至執行緒的join() 方法被呼叫中止(正常退出、丟擲未處理異常或者可選的超時發生)
  • isAlive():返回執行緒是否活動的。
  • getName():返回執行緒名。
  • getName():設定執行緒名。

執行緒傳遞引數方法

1.元組

threading.Thread(target=方法名,args=(引數1,引數2, ...))
import time
import threading

def song(a,b,c):
    print(a, b, c)
    for i in range(5):
        print("song")
        time.sleep(1)
if __name__ == "__main__":
    threading.Thread(target=song,args=(1,2,3)).start()

2.字典

threading.Thread(target=方法名, kwargs={"引數名": 引數1, "引數名": 引數2, ...})
threading.Thread(target=song,kwargs={"a":1,"c":3,"b":2}).start() #引數順序可以變

使用threading 模組建立執行緒

可以通過threading.Thread 繼承建立一個新的子類,並例項化後呼叫start() 方法啟動新執行緒,即它呼叫了run() 方法:

import threading
import time


class MyThread(threading.Thread):
    def __init__(self, thread_id, name, delay):
        threading.Thread.__init__(self)
        self.thread_id = thread_id
        self.name = name
        self.delay = delay

    def run(self):
        print('開始執行緒:' + self.name)
        print_time(self.name, self.delay, 5)
        print('退出執行緒:' + self.name)


def print_time(thread_name, delay, counter):
    while counter:
        time.sleep(delay)
        print('{}:{}'.format(thread_name, time.ctime(time.time())))
        counter -= 1


# 建立新執行緒
thread1 = MyThread(1, 'Thread-1', 1)
thread2 = MyThread(2, 'Thread-2', 2)

# 開始新執行緒
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print('退出主執行緒')
開始執行緒:Thread-1
開始執行緒:Thread-2
Thread-1:Sat Oct  3 10:15:20 2020
Thread-1:Sat Oct  3 10:15:21 2020Thread-2:Sat Oct  3 10:15:21 2020

Thread-1:Sat Oct  3 10:15:22 2020
Thread-2:Sat Oct  3 10:15:23 2020Thread-1:Sat Oct  3 10:15:23 2020

Thread-1:Sat Oct  3 10:15:24 2020
退出執行緒:Thread-1
Thread-2:Sat Oct  3 10:15:25 2020
Thread-2:Sat Oct  3 10:15:27 2020
Thread-2:Sat Oct  3 10:15:29 2020
退出執行緒:Thread-2
退出主執行緒

執行緒同步

如果多個執行緒共同對某個資料進行修改,則可能出現未知結果。為了保證資料的準確性,需要對多個執行緒進行同步。

使用Thread 物件的Lock 和 Rlock 可以實現簡單的執行緒同步,這兩個物件都有acquire 方法和release 方法,對於那些需要每次只允許一個執行緒操作的資料,可以將其操作放到acquire 方法和release 方法之間。如下:

多執行緒的優勢在於可以同時執行多個任務。但是當執行緒需要共享資料時,可能存在資料不同步的問題。
考慮這樣一種情況:一個列表裡的所有元素都是0,執行緒“set”從前往後把所有元素改為1,執行緒“print”則負責從前往後列印。
那麼就有可能列印的執行緒比設定的執行緒要快,將沒有改為1的列印出來,就會出現前面列印的為1,後面列印的為0的情況。為了避免這種情況,引入了鎖的概念。

鎖有兩種狀態——鎖定未鎖定。如執行緒1要訪問共享資料時,資料就必須先鎖定;這時執行緒2訪問時就會被阻塞,等到執行緒1訪問完畢,資料釋放鎖,執行緒2即可訪問。這樣處理後就可以得到執行緒同步的效果。(也就不會出現上面兩個執行緒同時列印成一行的結果)

import threading
import time


class MyThread(threading.Thread):
    def __init__(self, thread_id, name, delay):
        threading.Thread.__init__(self)
        self.thread_id = thread_id
        self.name = name
        self.delay = delay

    def run(self):
        print('開始執行緒:' + self.name)
        print_time(self.name, self.delay, 5)
        print('退出執行緒:' + self.name)


def print_time(thread_name, delay, counter):
    while counter:
        time.sleep(delay)
        # 鎖定狀態,不讓另一個執行緒同時列印
        threadingLock.acquire()
        print('{}:{}'.format(thread_name, time.ctime(time.time())))
        # 釋放鎖
        threadingLock.release()
        counter -= 1


# 建立執行緒同步鎖例項
threadingLock = threading.Lock()
# 建立新執行緒
thread1 = MyThread(1, 'Thread-1', 1)
thread2 = MyThread(2, 'Thread-2', 2)
# 執行緒列表
threads = [thread1, thread2]
# 開始所有執行緒
for t in threads:
    t.start()
# 等待執行緒結束
for t in threads:
    t.join()

print('退出主執行緒')
開始執行緒:Thread-1
開始執行緒:Thread-2
Thread-1:Sat Oct  3 10:32:16 2020
Thread-1:Sat Oct  3 10:32:17 2020
Thread-2:Sat Oct  3 10:32:17 2020
Thread-1:Sat Oct  3 10:32:18 2020
Thread-2:Sat Oct  3 10:32:19 2020
Thread-1:Sat Oct  3 10:32:19 2020
Thread-1:Sat Oct  3 10:32:20 2020
退出執行緒:Thread-1
Thread-2:Sat Oct  3 10:32:21 2020
Thread-2:Sat Oct  3 10:32:23 2020
Thread-2:Sat Oct  3 10:32:25 2020
退出執行緒:Thread-2
退出主執行緒

執行緒優先順序佇列(Queue)

python 的Queue 模組中提供了同步的、執行緒安全的佇列類,包括**FIFO(先進先出)**佇列Queue,LIFO(後進先出)佇列LifoQueue,和優先順序佇列PriorityQueue。

這些佇列都實現了鎖源語,能夠線上程中直接使用,可以使用佇列來實現執行緒間的同步。

Queue 模組中的常用方法:

  • Queue.qsize():返回佇列的大小。
  • Queue.empty():如果佇列為空,返回True,反之False。
  • Queue.full():如果佇列滿了,返回True,反之False。
  • Queue.get([block[,timeout]]):獲取佇列,timeout 等待時間
  • Queue.get_nowait():相當於Queue.get(False)。
  • Queue.put(item):寫入佇列,timeout等待時間。
  • Queue.put_nowait(item):相當於Queue.put(item,False)
  • Queue.task_done():在完成一項工作後,Queue.task_done 函式向任務已完成的佇列傳送一個訊號。
  • Queue.join():實際上意味著等到佇列為空,再執行別的操作。
import queue
import threading
import time

exitFlag = 0
# 執行緒鎖例項
queueLock = threading.Lock()

# 創見一個佇列,最大容量為10
workQueue = queue.Queue(10)


# 建立執行緒子類
class MyThread(threading.Thread):
    def __init__(self, thread_id, name, q):
        threading.Thread.__init__(self)
        self.thread_id = thread_id
        self.name = name
        self.q = q

    def run(self):
        print('開始執行緒' + self.name)
        process_data(self.name, self.q)
        print('結束執行緒', self.name)


def process_data(thread_name, q):
    while not exitFlag:
        queueLock.acquire()
        # 佇列如果不為空
        if not workQueue.empty():
            # 列印一個佇列中的元素
            data = q.get()
            queueLock.release()
            print('{} processing {}'.format(thread_name, data))
        else:
            queueLock.release()
        time.sleep(1)


threadList = ['Thread-1', 'Thread-2', 'Thread-3']
nameList = ['One', 'Two', 'Three', 'Four', 'Five']
# 已經開始的執行緒列表
threads = []
threadID = 1

# 建立執行緒
for threadName in threadList:
    thread = MyThread(threadID, threadName, workQueue)
    thread.start()
    threads.append(thread)
    threadID += 1

# 填充佇列
queueLock.acquire()
# 將nameList中的元素新增到佇列
for word in nameList:
    workQueue.put(word)
queueLock.release()

# 等待佇列清空
while not workQueue.empty():
    pass

# 通知執行緒退出
exitFlag = 1

# 等待所有執行緒完成
for t in threads:
    t.join()
print('退出程式')
開始執行緒Thread-1
開始執行緒Thread-2
開始執行緒Thread-3
Thread-2 processing One
Thread-3 processing Two
Thread-1 processing Three
Thread-2 processing Four
Thread-1 processing Five
結束執行緒 Thread-3
結束執行緒 Thread-2
結束執行緒 Thread-1
退出程式

相關文章