綜述
多線程是程式設計中的一個重要方面,尤其是在伺服器Deamon程式方面。無論何種系統,線程調度的開銷都比傳統的進程要快得多。
Python可以方便地支援多線程。可以快速建立線程、互斥鎖、訊號量等等元素,支援線程讀寫同步互斥。美中不足的是,Python的運行在Python
虛擬機器上,建立的多線程可能是虛擬線程,需要由Python虛擬機器來輪詢調度,這大大降低了Python多線程的可用性。希望高版本的Python可以
解決這個問題,發揮多CPU的最大效率。 網上有些朋友說要獲得真正多CPU的好處,有兩種方法: 1.可以建立多個進程而不是線程,進程數和cpu一樣多。
2.使用Jython 或 IronPython,可以得到真正的多線程。 閑話少說,下面看看Python如何建立線程
Python線程建立
使用threading模組的 Thread類 類介面如下 class
Thread( group=None, target=None, name=None, args=(), kwargs={}) 需要關注的參數是target和args. target 是需要子線程啟動並執行目標函數,args是函數的參數,以tuple的形式傳遞。 以下代碼建立一個指向函數worker 的子線程
def worker(a_tid,a_account):
...
th = threading.Thread(target=worker,args=(i,acc) ) ; 啟動這個線程
th.start() 等待線程返回
threading.Thread.join(th)
或者th.join() 如果你可以對要處理的資料進行很好的劃分,而且線程之間無須通訊,那麼你可以使用:建立=》運行=》回收的方式編寫你的多線程程式。但是如果線程之間需要訪問共同的對象,則需要引入互斥鎖或者訊號量對資源進行互斥訪問。 下面講講如何建立互斥鎖
建立鎖
g_mutex = threading.Lock()
....
使用鎖
for
... :
#鎖定,從下一句代碼到釋放前互斥訪問
g_mutex.acquire()
a_account.deposite(1)
#釋放
g_mutex.release() 最後,類比一個公交地鐵IC卡繳車費的多線程程式 有10個讀卡機,每個讀卡機收費器每次扣除使用者一塊錢進入總賬中,每讀卡機每天一共被刷10000000次。賬戶原有100塊。所以最後的總賬應該為10000100。先不使用互斥鎖來進行鎖定(注釋掉了鎖定代碼),看看後果如何。 import time,datetime
import threading
def worker(a_tid,a_account):
global g_mutex
print "Str " , a_tid, datetime.datetime.now()
for i in range(1000000):
#g_mutex.acquire()
a_account.deposite(1)
#g_mutex.release()
print "End " , a_tid , datetime.datetime.now()
class Account:
def __init__ (self, a_base ):
self.m_amount=a_base
def deposite(self,a_amount):
self.m_amount+=a_amount
def withdraw(self,a_amount):
self.m_amount-=a_amount
if __name__ == "__main__":
global g_mutex
count = 0
dstart = datetime.datetime.now()
print "Main Thread Start At: " , dstart
#init thread_pool
thread_pool = []
#init mutex
g_mutex = threading.Lock()
# init thread items
acc = Account(100)
for i in range(10):
th = threading.Thread(target=worker,args=(i,acc) ) ;
thread_pool.append(th)
# start threads one by one
for i in range(10):
thread_pool[i].start()
#collect all threads
for i in range(10):
threading.Thread.join(thread_pool[i])
dend = datetime.datetime.now()
print "count=",acc.m_amount
print "Main Thread End at: " ,dend , " time span " , dend-dstart; 注意,先不用互斥鎖進行臨界段存取控制,運行結果如下: Main Thread Start At: 2009-01-13 00:17:55.296000
Str 0 2009-01-13 00:17:55.312000
Str 1 2009-01-13 00:17:55.453000
Str 2 2009-01-13 00:17:55.484000
Str 3 2009-01-13 00:17:55.531000
Str 4 2009-01-13 00:17:55.562000
Str 5 2009-01-13 00:17:55.609000
Str 6 2009-01-13 00:17:55.640000
Str 7 2009-01-13 00:17:55.687000
Str 8 2009-01-13 00:17:55.718000
Str 9 2009-01-13 00:17:55.781000
End 0 2009-01-13 00:18:06.250000
End 1 2009-01-13 00:18:07.500000
End 4 2009-01-13 00:18:07.531000
End 2 2009-01-13 00:18:07.562000
End 3 2009-01-13 00:18:07.593000
End 9 2009-01-13 00:18:07.609000
End 7 2009-01-13 00:18:07.640000
End 8 2009-01-13 00:18:07.671000
End 5 2009-01-13 00:18:07.687000
End 6 2009-01-13 00:18:07.718000
count= 3434612
Main Thread End at: 2009-01-13 00:18:07.718000 time span 0:00:12.422000 從結果看到,程式確實是多線程啟動並執行。但是由於沒有對對象Account進行互斥訪問,所以結果是錯誤的,只有3434612,比原預計少了很多。 把上面陰影部分代碼的注釋開啟,運行結果如下Main Thread Start At: 2009-01-13 00:26:12.156000
Str 0 2009-01-13 00:26:12.156000
Str 1 2009-01-13 00:26:12.390000
Str 2 2009-01-13 00:26:12.437000
Str 3 2009-01-13 00:26:12.468000
Str 4 2009-01-13 00:26:12.515000
Str 5 2009-01-13 00:26:12.562000
Str 6 2009-01-13 00:26:12.593000
Str 7 2009-01-13 00:26:12.640000
Str 8 2009-01-13 00:26:12.671000
Str 9 2009-01-13 00:26:12.718000
End 0 2009-01-13 00:27:01.781000
End 1 2009-01-13 00:27:05.890000
End 5 2009-01-13 00:27:06.046000
End 7 2009-01-13 00:27:06.078000
End 4 2009-01-13 00:27:06.109000
End 2 2009-01-13 00:27:06.140000
End 6 2009-01-13 00:27:06.156000
End 8 2009-01-13 00:27:06.187000
End 3 2009-01-13 00:27:06.203000
End 9 2009-01-13 00:27:06.234000
count= 10000100
Main Thread End at: 2009-01-13 00:27:06.234000 time span 0:00:54.078000 這次可以看到,結果正確了。已耗用時間比不進行互斥多了很多,需要花54秒才能運行(我機器爛,沒錢更新,呵呵),不過這也是同步的代價,沒辦法。