python實現多線程
python中關於多線程的操作可以使用thread和threading模組來實現,其中thread模組在Py3中已經改名為_thread,不再推薦使用。而threading模組是在thread之上進行了封裝,也是推薦使用的多線程模組,本文主要基於threading模組進行介紹。在某些版本中thread模組可能不存在,要使用dump_threading來代替threading模組。
線程建立
threading模組中每個線程都是一個Thread對象,建立一個線程有兩種方式,一種是將函數傳遞到Thread對象中執行,另一種是從Thread繼承,然後重寫run方法(是不是跟Java很像)。
下面使用這兩種方法分別建立一個線程並同時執行
| 代碼如下 |
複製代碼 |
def threadFunction(): for i in range(10): print 'ThreadFuction - %d'%i time.sleep(random.randrange(0,2)) class ThreadClass(threading.Thread): def __init__(self): threading.Thread.__init__(self); def run(self): for i in range(10): print 'ThreadClass - %d'%i time.sleep(random.randrange(0,2)) if __name__ == '__main__': tFunc = threading.Thread(target = threadFunction); tCls = ThreadClass() tFunc.start() tCls.start() |
執行結果如下,可以看到兩個線程在交替列印。至於空行和一行多個輸出,是因為Py的print並不是安全執行緒的,在當前線程的print列印了部分內容後,準備列印換行之前,被別的線程中的print搶先,在換行之前列印了其它的內容。
ThreadFuction - 0
ThreadFuction - 1
ThreadFuction - 2
ThreadClass - 0
ThreadFuction - 3
ThreadClass - 1
ThreadFuction - 4
ThreadClass - 2
ThreadClass - 3
ThreadClass - 4ThreadFuction - 5
ThreadClass - 5
ThreadClass - 6
ThreadClass - 7
ThreadClass - 8
ThreadFuction - 6ThreadClass - 9
ThreadFuction - 7
ThreadFuction - 8
ThreadFuction - 9
Thread類的建構函式定義如下
class threading.Thread(group=None, target=None, name=None, args=(), kwargs={})
group: 留作ThreadGroup擴充使用,一般沒什麼用
target:新線程的任務函數名
name: 線程名,一般也沒什麼用
args: tuple參數
kwargs:dictionary參數
Thread類的成員變數和函數如下
start() 啟動一個線程
run() 線程執行體,也是一般要重寫的內容
join([timeout]) 等待線程結束
name 線程名
ident 線程ID
daemon 是否守護線程
isAlive()、is_alive() 線程是否存活
getName()、setName() Name的get&set方法
isDaemon()、setDaemon() daemon的get&set方法
這裡的守護線程與Linux中的守護進程並不是一個概念。這裡是指當所有守護線程退出後主程式才會退出,否則即使線程任務沒有結束,只要不是守護線程,都會跟著主程式一起退出。而Linux中的守護進程定義正好相反,守護進程已經脫離父進程,不會隨著父進程的結束而退出。
線程同步
線程同步是多線程中的一個核心問題,threading模組對線程同步有著良好的支援、包括線程特定資料、訊號量、互斥鎖、條件變數等。
線程特定資料
簡而言之,線程特定資料就是線程獨自持有的全域變數,相互之間的修改不會造成影響。
threading模組中使用local()方法產生一個線程獨立對象,舉例如下,其中sleep(1)是為了保證讓子線程先運行完再運行接下來的語句。
| 代碼如下 |
複製代碼 |
data = threading.local() def threadFunction(): global data data.x = 3 print threading.currentThread(), data.x if __name__ == '__main__': data.x = 1 tFunc = threading.Thread(target = threadFunction).start(); time.sleep(1) print threading.current_thread(), data.x |
輸出如下,可以看到,Thread-1中對data.x的修改並沒有影響到主線程中data.x的值。
<Thread(Thread-1, started 36208)> 3
<_MainThread(MainThread, started 35888)> 1
互斥鎖
threading中定義了兩種鎖:threading.Lock和threading.RLock。兩者的不同在於後者是可重新進入鎖,也就是說在一個線程內重複LOCK同一個鎖不會發生死結,這與POSIX中的PTHREAD_MUTEX_RECURSIVE也就是可遞迴鎖的概念是相同的。
關於互斥鎖的API很簡單,只有三個函數————分配鎖,上鎖,解鎖。
threading.Lock() 分配一個互斥鎖
acquire([blocking=1]) 上鎖(阻塞或者非阻塞,非阻塞時相當於try_lock,通過返回False表示已經被其它線程鎖住。)
release() 解鎖
下面通過一個例子來說明互斥鎖的使用。在之前的例子中,多線程print會造成混亂的輸出,這裡使用一個互斥鎖,來保證每行一定只有一個輸出。
| 代碼如下 |
複製代碼 |
def threadFunction(arg): while True: lock.acquire() print 'ThreadFuction - %d'%arg lock.release() if __name__ == '__main__': lock = threading.Lock() threading.Thread(target = threadFunction, args=(1,)).start(); threading.Thread(target = threadFunction, args=(2,)).start(); |
條件變數
條件變數總是與互斥鎖一起使用的,threading中的條件變數預設綁定了一個RLock,也可以在初始化條件變數的時候傳進去一個自己定義的鎖。
可用的函數如下
threading.Condition([lock]) 分配一個條件變數
acquire(*args) 條件變數上鎖
release() 條件變數解鎖
wait([timeout]) 等待喚醒,timeout表示逾時
notify(n=1) 喚醒最大n個等待的線程
notifyAll()、notify_all() 喚醒所有等待的線程
下面這個例子使用條件變數來控制兩個線程交替運行
| 代碼如下 |
複製代碼 |
num = 0 def threadFunction(arg): global num while num < 10: cond.acquire() while num % 2 != arg: cond.wait() print 'Thread %d - %d' %(arg, num) num += 1 cond.notify() cond.release() if __name__ == '__main__': cond = threading.Condition() threading.Thread(target = threadFunction, args=(0,)).start(); threading.Thread(target = threadFunction, args=(1,)).start(); |
輸出如下
Thread 0 - 0
Thread 1 - 1
Thread 0 - 2
Thread 1 - 3
Thread 0 - 4
Thread 1 - 5
Thread 0 - 6
Thread 1 - 7
Thread 0 - 8
Thread 1 - 9
Thread 0 - 10
其實上面這個程式是有問題的,我們想列印的是0~9,但實際上10也被列印了出來,原因很簡單,因為兩個線程交替列印,使得num在一個線程中可能加2,從而導致10被列印出來,所以必須在列印前再次check。
下面來舉個例子來說明下: 使用多線程批量修改主機名稱
新上的伺服器初始化時,需要批量修改伺服器主機名稱。
代碼如下所示:
| 代碼如下 |
複製代碼 |
#!/usr/bin/env python # -*- coding : UTF-8 -*-
import re import os,sys,datetime import threading
class ThreadClass(threading.Thread): def run(self): now = datetime.datetime.now() print "%s over at time %s" % (self.getName(),now)
f = file(hostadd,r) for line in f.readlines(): ip = re.findall(d .d .d .d ,line)[0] host = line[15:].replace(" ","") cmd = "hostname %s && sed -i s/HOSTNAME=.*/HOSTNAME=%s/g /etc/sysconfig/network" % (host,host) process = os.popen(ssh %s % ip "%s" % cmd).read() print process t = ThreadClass() t.start() |