天天看點

thread/threading——Python多線程入門筆記

1 什麼是線程?

(1)線程不同于程式。

  • 線程不能夠獨立執行,必須依存在應用程式中,由應用程式提供多個線程執行控制;
  • 多線程類似于同時執行多個不同程式。

(2)線程不同于程序。

  • 每個獨立的程序有一個程式運作的入口、順序執行序列和程式的出口。

(3)線程的優點:

  • 可以把占據長時間的程式中的任務放到背景去處理;
  • 使用者界面可以更加吸引人,如彈出一個進度條來顯示處理的進度等;
  • 程式的運作速度加快;
  • 占用珍貴資源如記憶體占用等等更少;

(4)線程的特點:

  • 線程可以被搶占(中斷);
  • 在其他線程正在運作時,線程可以暫時擱置(也稱為睡眠) -- 這就是線程的退讓。

      

2 開始學習Python程序:

Python通過兩個标準庫_thread和threading提供對線程的支援(Python2 的為thread和threading)。

2.1 調用_thread子產品中的start_new_thread()函數來産生新線程。

文法如下:

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

參數說明:

  • function - 線程函數。
  • args - 傳遞給線程函數的參數,他必須是個tuple類型。
  • kwargs - 可選參數。
import _thread  # python2為thread
import time

# 為線程定義一個函數
def print_time(threadName, delay):
    count = 0
    while count<5:
        time.sleep(delay)
        count = count+1
        print("%s: %s" %(threadName, time.ctime(time.time())))

# 建立兩個線程
try:
    _thread.start_new_thread(print_time, ("Thread_1", 2, ))
    _thread.start_new_thread(print_time, ("Thread_2", 4, ))
except:
    print("Error")

# while 1:
#     pass

# a=0
# while a<4:
#     print("a")
#     a += 1
           

輸出結果如下:

Thread-1: Thu Jan 22 15:42:17 2009
Thread-1: Thu Jan 22 15:42:19 2009
Thread-2: Thu Jan 22 15:42:19 2009
Thread-1: Thu Jan 22 15:42:21 2009
Thread-2: Thu Jan 22 15:42:23 2009
Thread-1: Thu Jan 22 15:42:23 2009    
Thread-1: Thu Jan 22 15:42:25 2009
Thread-2: Thu Jan 22 15:42:27 2009
Thread-2: Thu Jan 22 15:42:31 2009
Thread-2: Thu Jan 22 15:42:35 2009
           

Tips:線程的結束一般依靠線程函數的自然結束;也可以線上程函數中調用thread.exit(),抛出SystemExit exception,達到退出線程的目的。

2.2 使用Threading子產品建立線程

使用Threading子產品建立線程,直接從threading.Thread繼承,然後重寫__init__方法和run方法:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
import threading
import time
 
exitFlag = 0
 
class myThread (threading.Thread):   #繼承父類threading.Thread
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter
    def run(self):                   #把要執行的代碼寫到run函數裡面 線程在建立後會直接運作run函數 
        print("Starting " + self.name)
        print_time(self.name, self.counter, 5)
        print("Exiting " + self.name)
 
def print_time(threadName, delay, counter):
    while counter:
        if exitFlag:
            (threading.Thread).exit()
        time.sleep(delay)
        print("%s: %s" % (threadName, time.ctime(time.time())))
        counter -= 1
 
# 建立新線程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
 
# 開啟線程
thread1.start()
thread2.start()
 
print("Exiting Main Thread")
           
Starting Thread-1
Starting Thread-2
Exiting Main Thread
Thread-1: Tue Feb 12 11:15:30 2019
Thread-1: Tue Feb 12 11:15:31 2019
Thread-2: Tue Feb 12 11:15:31 2019
Thread-1: Tue Feb 12 11:15:32 2019
Thread-1: Tue Feb 12 11:15:33 2019
Thread-2: Tue Feb 12 11:15:33 2019
Thread-1: Tue Feb 12 11:15:34 2019
Exiting Thread-1
Thread-2: Tue Feb 12 11:15:35 2019
Thread-2: Tue Feb 12 11:15:37 2019
Thread-2: Tue Feb 12 11:15:39 2019
Exiting Thread-2
           

threading 子產品提供的其他方法:

  • threading.currentThread(): 傳回目前的線程變量。
  • threading.enumerate(): 傳回一個包含正在運作的線程的list。正在運作指線程啟動後、結束前,不包括啟動前和終止後的線程。
  • threading.activeCount(): 傳回正在運作的線程數量,與len(threading.enumerate())有相同的結果。

threading 子產品還提供了Thread類,Thread類提供了以下方法:

  • run(): 用以表示線程活動的方法。
  • start():啟動線程活動。
  • join([time]): 等待至線程中止。這阻塞調用線程直至線程的join() 方法被調用中止-正常退出或者抛出未處理的異常-或者是可選的逾時發生。
  • isAlive(): 傳回線程是否活動的。
  • getName(): 傳回線程名。
  • setName(): 設定線程名。

2.3 線程同步

如果多個線程共同對某個資料修改,則可能出現不可預料的結果,為了保證資料的正确性,需要對多個線程進行同步,于是引入了鎖的概念。

使用Thread對象的Lock和Rlock可以實作簡單的線程同步,這兩個對象都有acquire方法和release方法,對于那些需要每次隻允許一個線程操作的資料,可以将其操作放到acquire和release方法之間。

鎖有兩種狀态——鎖定和未鎖定。每當一個線程比如"set"要通路共享資料時,必須先獲得鎖定;如果已經有别的線程比如"print"獲得鎖定了,那麼就讓線程"set"暫停,也就是同步阻塞;等到線程"print"通路完畢,釋放鎖以後,再讓線程"set"繼續。

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
import threading
import time
 
class myThread (threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter
    def run(self):
        print("Starting " + self.name)
       # 獲得鎖,成功獲得鎖定後傳回True
       # 可選的timeout參數不填時将一直阻塞直到獲得鎖定
       # 否則逾時後将傳回False
        threadLock.acquire()
        print_time(self.name, self.counter, 3)
        # 釋放鎖
        threadLock.release()
        
def print_time(threadName, delay, counter):
    while counter:
        time.sleep(delay)
        print("%s: %s" % (threadName, time.ctime(time.time())))
        counter -= 1
        
threadLock = threading.Lock()
threads = []
 
# 建立新線程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
 
# 開啟新線程
thread1.start()
thread2.start()
 
# 添加線程到線程清單
threads.append(thread1)
threads.append(thread2)
 
# 等待所有線程完成
for t in threads:
    t.join()
print("Exiting Main Thread")
           
Starting Thread-1
Starting Thread-2
Thread-1: Tue Feb 12 14:53:44 2019
Thread-1: Tue Feb 12 14:53:45 2019
Thread-1: Tue Feb 12 14:53:46 2019
Thread-2: Tue Feb 12 14:53:48 2019
Thread-2: Tue Feb 12 14:53:50 2019
Thread-2: Tue Feb 12 14:53:52 2019
Exiting Main Thread
           

2.4 線程優先級隊列( Queue)

Python的Queue子產品中提供了同步的、線程安全的隊列類,包括FIFO(先入先出)隊列Queue,LIFO(後入先出)隊列LifoQueue,和優先級隊列PriorityQueue。這些隊列都實作了鎖原語,能夠在多線程中直接使用。可以使用隊列來實作線程間的同步。

Queue子產品中的常用方法:

  • Queue.qsize() 傳回隊列的大小
  • Queue.empty() 如果隊列為空,傳回True,反之False
  • Queue.full() 如果隊列滿了,傳回True,反之False
  • Queue.full 與 maxsize 大小對應
  • 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() 實際上意味着等到隊列為空,再執行别的操作
#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
import queue  # Python2為Queue
import threading
import time
 
exitFlag = 0
 
class myThread (threading.Thread):
    def __init__(self, threadID, name, q):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.q = q
    def run(self):
        print("Starting " + self.name)
        process_data(self.name, self.q)
        print("Exiting " + self.name)
        
def process_data(threadName, q):
    while not exitFlag:
        queueLock.acquire()
        if not workQueue.empty():
            data = q.get()
            queueLock.release()
            print("%s processing %s" % (threadName, data))
        else:
            queueLock.release()
        time.sleep(1)
        
threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = queue.Queue(10)
threads = []
threadID = 1
 
# 建立新線程
for tName in threadList:
    thread = myThread(threadID, tName, workQueue)
    thread.start()
    threads.append(thread)
    threadID += 1
    
# 填充隊列
queueLock.acquire()
for word in nameList:
    workQueue.put(word)
queueLock.release()
 
# 等待隊列清空
while not workQueue.empty():
    pass
 
# 通知線程是時候退出
exitFlag = 1
 
# 等待所有線程完成
for t in threads:
    t.join()
print("Exiting Main Thread")
           
Starting Thread-1
Starting Thread-2
Starting Thread-3
Thread-3 processing One
Thread-2 processing Two
Thread-1 processing Three
Thread-3 processing Four
Thread-2 processing Five
Exiting Thread-1
Exiting Thread-2
Exiting Thread-3
Exiting Main Thread
           

Tips:這個線程優先級有點晦澀難懂,等日後有空還是得研究下才行啊。

加油~~~

一個佛系的部落格更新者,随手寫寫,看心情吧 (っ•̀ω•́)っ✎⁾⁾