1. thread模組
- python是支援多執行緒的, 主要是通過thread和threading這兩個模組來實現的。
- python的thread模組是比較底層的模組(或者說輕量級),python的threading模組是對thread做了一些包裝的,可以更加方便的被使用。
簡要的看一下thread模組中含函式和常量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import thread thread.LockType #鎖物件的一種, 用於執行緒的同步 thread.error #執行緒的異常 thread.start_new_thread(function, args[, kwargs]) #建立一個新的執行緒 function : 執行緒執行函式 args : 執行緒執行函式的引數, 類似為tuple, kwargs : 是一個字典 返回值: 返回執行緒的識別符號 thread.exit() #執行緒退出函式 thread.allocate_lock() #生成一個未鎖狀態的鎖物件 返回值: 返回一個鎖物件 |
鎖物件的方法
1 2 3 4 5 6 7 8 9 |
lock.acquire([waitflag]) #獲取鎖 無引數時, 無條件獲取鎖, 無法獲取時, 會被阻塞, 知道可以鎖被釋放 有引數時, waitflag = 0 時,表示只有在不需要等待的情況下才獲取鎖, 非零情況與上面相同 返回值 : 獲得鎖成功返回True, 獲得鎖失敗返回False lock.release() #釋放鎖 lock.locked() #獲取當前鎖的狀態 返回值 : 如果鎖已經被某個執行緒獲取,返回True, 否則為False |
1.1. thread多執行緒
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#!/usr/bin/env python # -*- coding:utf-8 -*- import thread import time def print_time(thread_name, delay) : count = 0 while count < 5 : time.sleep(delay) count += 1 print "%s : %s" % (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 : print "Error: unable to start the thread" while True : pass |
2. threading模組
python的threading模組是對thread做了一些包裝的,可以更加方便的被使用。經常和Queue結合使用,Queue模組中提供了同步的、執行緒安全的佇列類,包括FIFO(先入先出)佇列Queue,LIFO(後入先出)佇列LifoQueue,和優先順序佇列PriorityQueue。這些佇列都實現了鎖原語,能夠在多執行緒中直接使用。可以使用佇列來實現執行緒間的同步
2.1. 常用函式和物件
1 2 3 4 5 6 7 8 9 10 11 |
#函式 threading.active_count() #返回當前執行緒物件Thread的個數 threading.enumerate() #返回當前執行的執行緒物件Thread(包括後臺的)的list threading.Condition() #返回條件變數物件的工廠函式, 主要使用者執行緒的併發 threading.current_thread() #返回當前的執行緒物件Thread, 文件後面解釋沒看懂 threading.Lock() #返回一個新的鎖物件, 是在thread模組的基礎上實現的 與acquire()和release()結合使用 #類 threading.Thread #一個表示執行緒控制的類, 這個類常被繼承 thraeding.Timer #定時器,執行緒在一定時間後執行 threading.ThreadError #引發中各種執行緒相關異常 |
2.1.1. Thread物件
一般來說,使用執行緒有兩種模式, 一種是建立執行緒要執行的函式, 把這個函式傳遞進Thread物件裡,讓它來執行. 另一種是直接從Thread繼承,建立一個新的class,把執行緒執行的程式碼放到這個新的class裡。
常用兩種方式執行執行緒(執行緒中包含name屬性) :
- 在建構函式中傳入用於執行緒執行的函式(這種方式更加靈活)
- 在子類中重寫threading.Thread基類中run()方法(只重寫__init__()和run()方法)
建立執行緒物件後, 通過呼叫start()函式執行執行緒, 然後會自動呼叫run()方法.
通過設定`daemon`屬性, 可以將執行緒設定為守護執行緒
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
threading.Thread(group = None, target = None, name = None, args = () kwars = {}) group : 應該為None target : 可以傳入一個函式用於run()方法呼叫, name : 執行緒名 預設使用"Thread-N" args : 元組, 表示傳入target函式的引數 kwargs : 字典, 傳入target函式中關鍵字引數 屬性: name #執行緒表示, 沒有任何語義 doemon #布林值, 如果是守護執行緒為True, 不是為False, 主執行緒不是守護執行緒, 預設threading.Thread.damon = False 類方法: run() #用以表示執行緒活動的方法。 start() #啟動執行緒活動。 join([time]) #等待至執行緒中止。這阻塞呼叫執行緒直至執行緒的join() 方法被呼叫中止-正常退出或者丟擲未處理的異常-或者是可選的超時發生。 isAlive(): 返回執行緒是否活動的。 getName(): 返回執行緒名。 setName(): 設定執行緒名。 |
範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#!/usr/bin/env python # -*- coding:utf-8 -*- import threading import time def test_thread(count) : while count > 0 : print "count = %d" % count count = count - 1 time.sleep(1) def main() : my_thread = threading.Thread(target = test_thread, args = (10, )) my_thread.start() my_thread.join() if __name__ == '__main__': main() |
2.2. 常用多執行緒寫法
- 固定執行緒執行的函式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
#!/usr/bin/env python # -*- coding:utf-8 -*- import threading, thread import time class MyThread(threading.Thread): """docstring for MyThread""" def __init__(self, thread_id, name, counter) : super(MyThread, self).__init__() #呼叫父類的建構函式 self.thread_id = thread_id self.name = name self.counter = counter def run(self) : print "Starting " + self.name print_time(self.name, self.counter, 5) print "Exiting " + self.name def print_time(thread_name, delay, counter) : while counter : time.sleep(delay) print "%s %s" % (thread_name, time.ctime(time.time())) counter -= 1 def main(): #建立新的執行緒 thread1 = MyThread(1, "Thread-1", 1) thread2 = MyThread(2, "Thread-2", 2) #開啟執行緒 thread1.start() thread2.start() thread1.join() thread2.join() print "Exiting Main Thread" if __name__ == '__main__': main() |
- 外部傳入執行緒執行的函式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#/usr/bin/env python # -*- coding: utf-8 -*- import threading import time class MyThread(threading.Thread): """ 屬性: target: 傳入外部函式, 使用者執行緒呼叫 args: 函式引數 """ def __init__(self, target, args): super(MyThread, self).__init__() #呼叫父類的建構函式 self.target = target self.args = args def run(self) : self.target(self.args) def print_time(counter) : while counter : print "counter = %d" % counter counter -= 1 time.sleep(1) def main() : my_thread = MyThread(print_time, 10) my_thread.start() my_thread.join() if __name__ == '__main__': main() |
2.3. 生產者消費者問題
試著用python寫了一個生產者消費者問題(偽生產者消費者), 只是使用簡單的鎖, 感覺有點不太對, 下面另一個程式會寫出正確的生產者消費者問題
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
#!/usr/bin/env python # -*- coding:utf-8 -*- import thread, threading import urllib2 import time, random import Queue share_queue = Queue.Queue() #共享佇列 my_lock = thread.allocate_lock() class Producer(threading.Thread) : def run(self) : products = range(5) global share_queue while True : num = random.choice(products) my_lock.acquire() share_queue.put(num) print "Produce : ", num my_lock.release() time.sleep(random.random()) class Consumer(threading.Thread) : def run(self) : global share_queue while True: my_lock.acquire() if share_queue.empty() : #這裡沒有使用訊號量機制進行阻塞等待, print "Queue is Empty..." my_lock.release() time.sleep(random.random()) continue num = share_queue.get() print "Consumer : ", num my_lock.release() time.sleep(random.random()) def main() : producer = Producer() consumer = Consumer() producer.start() consumer.start() if __name__ == '__main__': main() |
殺死多執行緒程式方法: 使用control + z掛起程式(程式依然在後臺, 可以使用ps aux檢視), 獲得程式的程式號, 然後使用kill -9 程式號殺死程式
參考一篇帖子解決了上述問題,重寫了生產者消費者問題程式, 參考連結慣例放在最後.
使用了wait()和notify()解決
當然最簡答的方法是直接使用Queue,Queue封裝了Condition的行為, 如wait(), notify(), acquire(), 沒看文件就這樣, 使用了Queue竟然不知道封裝了這些函式, 繼續滾去看文件了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
#!/usr/bin/env python # -*- coding:utf-8 -*- import threading import random, time, Queue MAX_SIZE = 5 SHARE_Q = [] #模擬共享佇列 CONDITION = threading.Condition() class Producer(threading.Thread) : def run(self) : products = range(5) global SHARE_Q while True : CONDITION.acquire() if len(SHARE_Q) == 5 : print "Queue is full.." CONDITION.wait() print "Consumer have comsumed something" product = random.choice(products) SHARE_Q.append(product) print "Producer : ", product CONDITION.notify() CONDITION.release() time.sleep(random.random()) class Consumer(threading.Thread) : def run(self) : global SHARE_Q while True: CONDITION.acquire() if not SHARE_Q : print "Queue is Empty..." CONDITION.wait() print "Producer have producted something" product = SHARE_Q.pop(0) print "Consumer :", product CONDITION.notify() CONDITION.release() time.sleep(random.random()) def main() : producer = Producer() consumer = Consumer() producer.start() consumer.start() if __name__ == '__main__': main() |
2.4.簡單鎖
如果只是簡單的加鎖解鎖可以直接使用threading.Lock()生成鎖物件, 然後使用acquire()和release()方法
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
#!/usr/bin/env python # -*- coding:utf-8 -*- import threading import time class MyThread(threading.Thread) : def __init__(self, thread_id, name, counter) : threading.Thread.__init__(self) self.thread_id = thread_id self.name = name self.counter = counter def run(self) : #重寫run方法, 新增執行緒執行邏輯, start函式執行會自動執行 print "Starting " + self.name threadLock.acquire() #獲取所 print_time(self.name, self.counter, 3) threadLock.release() #釋放鎖 def print_time(thread_name, delay, counter) : while counter : time.sleep(delay) print "%s %s" % (thread_name, 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() for t in threads : t.join() #等待執行緒直到終止 print "Exiting Main Thread" |
2.5. Condition
如果是向生產者消費者類似的情形, 使用Condition類 或者直接使用Queue模組
Condition
條件變數中有acquire()和release方法用來呼叫鎖的方法, 有wait(), notify(), notifyAll()方法, 後面是三個方法必須在獲取鎖的情況下呼叫, 否則產生RuntimeError錯誤.
- 當一個執行緒獲得鎖後, 發現沒有期望的資源或者狀態, 就會呼叫wait()阻塞, 並釋放已經獲得鎖, 知道期望的資源或者狀態發生改變
- 當一個執行緒獲得鎖, 改變了資源或者狀態, 就會呼叫notify()和notifyAll()去通知其他執行緒,
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#官方文件中提供的生產者消費者模型 # Consume one item cv.acquire() while not an_item_is_available(): cv.wait() get_an_available_item() cv.release() # Produce one item cv.acquire() make_an_item_available() cv.notify() cv.release() |
1 2 3 4 5 6 7 8 9 10 11 |
#threading.Condition類 thread.Condition([lock]) 可選引數lock: 必須是Lock或者RLock物件, 並被作為underlying鎖(悲觀鎖?), 否則, 會建立一個新的RLock物件作為underlying鎖 類方法: acquire() #獲得鎖 release() #釋放鎖 wait([timeout]) #持續等待直到被notify()或者notifyAll()通知或者超時(必須先獲得鎖), #wait()所做操作, 先釋放獲得的鎖, 然後阻塞, 知道被notify或者notifyAll喚醒或者超時, 一旦被喚醒或者超時, 會重新獲取鎖(應該說搶鎖), 然後返回 notify() #喚醒一個wait()阻塞的執行緒. notify_all()或者notifyAll() #喚醒所有阻塞的執行緒 |
參考程式可以檢視上面的生產者消費者程式