python通過兩個標準庫(thread, threading)提供了對多線程的支持
thread模塊
import time
import thread
def runner(arg):
for i in range(6):
print str(i)+':'+arg
time.sleep(1)
#結束當前線程
thread.exit_thread() #等同於thread.exit()
#啓動一個線程,第一個參數爲函數名,
#第二個參數爲一個tuple類型,是傳給函數的參數
thread.start_new_thread(runner, ('hello world',)) #等同於thread.start_new(runner, ('hello world'))
#創建一個鎖,鎖用於線程同步,通常控制對共享資源的訪問
lock = thread.allocate_lock() #等同於thread.allocate()
num = 0
#獲得鎖,成功返回True,失敗返回False
if lock.acquire():
num += 1
#釋放鎖
lock.release()
#thread模塊提供的線程都將在主線程結束後同時結束,因此將主線程延遲結束
time.sleep(10)
print 'num:'+str(num)
threading.Thread類的常用方法1.在自己的線程類的__init__裏調用threading.Thread.__init__(self, name=threadname),threadname爲線程的名字。
2.run(),通常需要重寫,編寫代碼實現做需要的功能。
3.getName(),獲得線程對象名稱。
4.setName(),設置線程對象名稱。
5.start(),啓動線程。
6.join([timeout]),等待另一線程結束後再運行。
7.setDaemon(bool),設置子線程是否隨主線程一起結束,必須在start()之前調用。默認爲False。
8.isDaemon(),判斷線程是否隨主線程一起結束。
9.isAlive(),檢查線程是否在運行中。
要想創建一個線程對象,只要繼承類threading.Thread,然後在__init__裏邊調用threading.Thread.__init__()方法即可。重寫run()方法,將要實現的功能放到此方法中即可。
class runner(threading.Thread):
def __init__(self, name):
threading.Thread.__init__(self)
self.name = name
self.thread_stop = False
def run(self):
while not self.thread_stop:
print str(self.name)+':'+'hello world'
time.sleep(1)
def stop(self):
self.thread_stop = True
def test():
t = runner('thread')
t.start()
time.sleep(10)
t.stop()
if __name__ == '__main__':
test()
線程同步(鎖)
最簡單的同步機制就是鎖。鎖對象由threading.RLock類創建。
線程可以使用鎖的acquire()方法獲得鎖,這樣鎖就進入locked狀態。每次只有一個線程可以獲得鎖。
如果當另一個線程試圖獲得這個鎖的時候,就會被系統變爲blocked狀態,直到那個擁有鎖的線程調用鎖的release()方法來釋放鎖,這樣鎖就會進入unlocked狀態。blocked狀態的線程就會收到一個通知,並有權利獲得鎖。
如果多個線程處於blocked狀態,所有線程都會先解除blocked狀態,然後系統選擇一個線程來獲得鎖,其他的線程繼續blocked。
python的threading module是在建立在thread module基礎之上的一個module,
在thread module中,python提供了用戶級的線程同步工具Lock對象。
而在threading module中,python又提供了Lock對象的變種: RLock對象。
RLock對象內部維護着一個Lock對象,它是一種可重入的對象。
對於Lock對象而言,如果一個線程連續兩次進行acquire操作,那麼由於第一次acquire之後沒有release,第二次acquire將掛起線程。
這會導致Lock對象永遠不會release,使得線程死鎖。
RLock對象允許一個線程多次對其進行acquire操作,因爲在其內部通過一個counter變量維護着線程acquire的次數。
而且每一次的acquire操作必須有一個release操作與之對應,在所有的release操作完成之後,別的線程才能申請該RLock對象。
我們把修改共享數據的代碼稱爲"臨界區"。必須將所有"臨界區"都封閉在同一個鎖對象的acquire和release之間。
import time
import threading
num=0
lock = threading.RLock()
class runner(threading.Thread):
def __init__(self, name):
threading.Thread.__init__(self)
self.name = name
def run(self):
global num
while True:
if num >= 6: break
if lock.acquire():
print "Thread(%s) locked, Number: %d" % (self.name, num)
time.sleep(1)
lock.release()
print "Thread(%s) released, Number: %d" % (self.name, num)
time.sleep(1)
num += 1
def test():
t1 = runner('thread1')
t2 = runner('thread2')
t1.start()
t2.start()
if __name__== '__main__':
test()
線程同步(條件變量)鎖只能提供最基本的同步。假如只在發生某些事件時才訪問一個"臨界區",這時需要使用條件變量Condition。
Condition(條件變量)通常與一個鎖關聯。需要在多個Contidion中共享一個鎖時,可以傳遞一個Lock/RLock實例給構造方法,否則它將自己生成一個RLock實例。
在Condition對象上,當然也可以調用acquire和release操作,因爲內部的Lock對象本身就支持這些操作。
但是Condition的價值在於其提供的wait和notify的語義。
條件變量的工作原理?
首先一個線程成功獲得一個條件變量後,調用此條件變量的wait()方法會導致這個線程釋放這個鎖,並進入“blocked”狀態.
直到另一個線程調用同一個條件變量的notify()方法來喚醒那個進入“blocked”狀態的線程。
如果調用這個條件變量的notifyAll()方法的話就會喚醒所有的在等待的線程。
如果程序或者線程永遠處於“blocked”狀態的話,就會發生死鎖。
所以如果使用了鎖、條件變量等同步機制的話,一定要注意仔細檢查,防止死鎖情況的發生。
對於可能產生異常的臨界區要使用異常處理機制中的finally子句來保證釋放鎖。
等待一個條件變量的線程必須用notify()方法顯式的喚醒,否則就永遠沉默。
保證每一個wait()方法調用都有一個相對應的notify()調用,當然也可以調用notifyAll()方法以防萬一。
import threading
import time
con = threading.Condition()
product = None
class Producer(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global product
if con.acquire():
while True:
if product is None:
print 'produce...'
product = 'anything'
#通知消費者,商品已經生產
con.notify()
#等待通知
con.wait()
time.sleep(5)
class Consumer(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global product
if con.acquire():
while True:
if product is not None:
print 'consume...'
product = None
#通知生產者,商品已經沒了
con.notify()
#等待通知
con.wait()
time.sleep(5)
def test():
t1 = Producer()
t2 = Consumer()
t1.start()
t2.start()
if __name__ == '__main__':
test()
同步隊列Queue模塊實現了一個支持多producer和多consumer的FIFO隊列。當共享信息需要安全的在多線程之間交換時,Queue非常有用。Queue的默認長度是無限的,但是可以設置其構造函數的maxsize參數來設定其長度。Queue的put方法在隊尾插入,該方法的原型是:
put( item[, block[, timeout]])
如果可選參數block爲true並且timeout爲None(缺省值),線程被block,直到隊列空出一個數據單元。如果timeout大於0,在timeout的時間內,仍然沒有可用的數據單元,Full exception被拋出。反之,如果block參數爲false(忽略timeout參數),item被立即加入到空閒數據單元中,如果沒有空閒數據單元,Full exception被拋出。Queue的get方法是從隊首取數據,其參數和put方法一樣。如果block參數爲true且timeout爲None(缺省值),線程被block,直到隊列中有數據。如果timeout大於0,在timeout時間內,仍然沒有可取數據,Empty exception被拋出。反之,如果block參數爲false(忽略timeout參數),隊列中的數據被立即取出。如果此時沒有可取數據,Empty exception也會被拋出。
import time
import threading
from Queue import Queue
class Producer(threading.Thread):
def __init__(self, t_name, queue):
threading.Thread.__init__(self, name=t_name)
self.data = queue
def run(self):
for i in range(6):
print "%s: %s is producing %d to the queue!\n" % (time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), self.getName(), i)
#將值放入隊列
self.data.put(i)
time.sleep(1)
print "%s: %s finished!" % (time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), self.getName())
class Consumer(threading.Thread):
def __init__(self, t_name, queue):
threading.Thread.__init__(self, name=t_name)
self.data = queue
def run(self):
for i in range(6):
#從隊列中取值
val = self.data.get()
print "%s: %s is consuming. %d in the queue is consumed!\n" % (time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), self.getName(), val)
time.sleep(1)
print "%s: %s finished!" % (time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), self.getName())
def test():
queue = Queue()
producer = Producer('Producer', queue)
consumer = Consumer('Consumer', queue)
producer.start()
consumer.start()
if __name__ == '__main__':
test()