多線程(一)

什麼是線程? ➢線程是一個進程的實體,一個進程可以擁有多個線程,一個線程必須有一個父進程。線程是由表示程序運行狀態的寄存器(如程序計數器、棧指針)以及堆棧組成,它是比進程更小的單位。 ➢線程是程序中的一個執行流。一個執行流是由CPU運行程序代碼並操作程序的數據所形成的。因此,線程被認爲是以CPU爲主體的行爲。 ➢線程不包含進程地址空間中的代碼和數據,線程是計算過程在某一時刻的狀態。所以,系統在產生一個線程或各個線程之間切換時,負擔要比進程小得多。 ➢線程是一個用戶級的實體,線程結構駐留在用戶空間中,能夠被普通的用戶級函數直接訪問。 ➢一個線程本身不是程序,它必須運行於一個程序(進程)之中。因此,線程可以定義爲一個程序中的單個執行流。 •多線程是指一個程序中包含多個執行流,多線程是實現併發的一種有效手段。一個進程在其執行過程中,可以產生多個線程,形成多個執行流。每個執行流即每個線程也有它自身的產生、存在和消亡的過程。 •多線程程序設計的含義就是可以將程序任務分成幾個並行的子任務。 線程和進程的區別 ➢進程是資源分配的最小單位,線程是程序執行的最小單位 ➢ 進程有自己的獨立地址空間,每啓動一個進程,系統就會爲它分配地址空間,建立數據表來維護代碼段、堆棧段和數據段,這種操作非常昂貴。而線程是共享進程中的數據的,使 用相同的地址空間,因此CPU切換一個線程的花費遠比進程要小很多,同時創建一個線程 的開銷也比進程要小很多。 ➢ 線程之間的通信更方便,同一進程下的線程共享全局變量、靜態變量等數據,而進程之間的通信需要以通信的方式(IPC)進行。不過如何處理好同步與互斥是編寫多線程程序的難 點。 ➢ 但是多進程程序更健壯,多線程程序只要有一個線程死掉,整個進程也死掉了,而一個進程死掉並不會對另外一個進程造成影響,因爲進程有自己獨立的地址空間。 線程的狀態圖 線程阻塞通常是指一個線程在執行過程中暫停,以等待某個條件的觸發; Python線程threading模塊 Threading模塊提供的類: Thread,Lock,Rlock,Condition,Semaphore,Event,Timer,local等。 threading模塊提供的常用方法: ✓ threading.currentThread(): 返回當前的線程變量。 ✓ threading.enumerate(): 返回一個包含正在運行的線程的list。正在運行指線 程啓動後、結束前,不包括啓動前和終止後的線程。 ✓ threading.activeCount():返回正在運行的線程數量,與len(threading.enumerate())有相同的結果 。 threading模塊提供了更方便的API來操作線程。 Thread是threading模塊中最重要的類之一,可以使用它來創建線程。 該類創建線程有有兩種方式: 1.直接創建threading.Thread類的對象,初始化時將可調用對象作爲參數傳入。 2.通過繼承Thread類,重寫它的run方法。 Thread類的構造方法: __init__(self, group=None, target=None, name=None, args=(),kwargs=None, verbose=None) 參數說明: •group:線程組,目前還沒有實現,庫引用中提示必須是None; •target:要執行的方法; •name:線程名; •args/kwargs:要傳入方法的參數。 Thread類的方法 1)isAlive():返回線程是否在運行。正在運行指的是啓動後,終止前。 2)getName(name):獲取線程名。 3)setName(name):設置線程名。 4)isDaemon(bool):判斷線程是否隨主線程一起結束。 5)setDaemon(bool):設置是否爲守護線程。初始值從創建該線程的線程繼承而來,默認爲False,當沒有非守護線程仍在運行時,程序將終止。比如,主線程A中,創建了子線程B,並且在主線程A中調用了B.setDaemon(),意思是,把線程B設置爲守護線程,這時候,要是主線程A執行結束了,就不管子線程B是否完成,一併和主線程A退出。這就是setDaemon方法的含義,這基本和join是相反的。此外,還有個要特別注意,必須在start() 方法調用之前調用此方法,如果不設置爲守護線程,程序有可能會被無限掛起。 6)start():啓動線程。 7)join([timeout]):阻塞當前上下文環境的線程,直到調用此方法的線程終止或到達指定的等待時間timeout(可選參數)。即當前的線程要等調用join()這個方法的線程執行完,或者是達到規定的時間。比如,主線程A中,創建了子線程B,並且在主線程A中調用了B.join(),那麼,主線程A會在調用的地方等待,直到子線程B完成操作後,纔可以接着往下執行,那麼在調用這個線程時可以使用被調用線程的join方法。 8)run():用於表示線程活動的方法,通常需要重寫,編寫代碼實現做需要的功能。 創建threading.Thread線程 #encoding=utf-8 from threading import Thread import time def run(a = None,b = None): print(a,b) time.sleep(1) #創建一個子線程 t = Thread(target=run, args=("this is a","thread")) #獲取線程的名字 print(t.getName()) #判斷線程是否還活着,在start後,在執行完畢前調用isAlive()纔會返回true print(t.isAlive()) t.start() print(t.isAlive()) t.join() print(t.isAlive()) 通過繼承Thread類創建線程 使用Threading模塊創建線程,直接從threading.Thread繼承,然後重寫__init__方法和run方法 #encoding=utf-8 from threading import Thread import time class MyThread(Thread): #需要調用父類的__init__方法 def __init__(self,a): super(MyThread,self).__init__() #Thread.__init__(self) self.a = a #需重寫父類的run方法 def run(self): time.sleep(self.a) print("sleep:",self.a) t1 = MyThread(2) t2 = MyThread(3) t1.start() t2.start() t1.join() t2.join() 注意:多線程執行本身是沒有先後順序的 run方法是在start()時候自動調用的; 繼承Thread類-新增run方法參數 注意: 繼承Thread類的新類MyThread構造函數中必須要調用父類的構造方法,這樣才能產生父類的構造函數中的參數,才能產生線程所需要的參數。新的類中如果需要別的參數,直接在其構造方法中添加即可。同時,新類中,在重寫父類的run方法時,它默認是不帶參數的,如果需要給他提供參數,需要在類的構造函數中指定,因爲在線程執行過程中,run方法是線程自己去調用的,不用我們手動調用,所以沒法直接給傳遞參數,只能在構造方法中設定好參數,然後在run方法中再去取這些參數。 #encoding=utf-8 import threading import time class timer(threading.Thread): def __init__(self,num,interval): threading.Thread.__init__(self) self.thread_num = num self.interval = interval self.thread_stop = False def run(self): while not self.thread_stop: print("Thread Object(%d),Time:%s\n" %(self.thread_num,time.ctime())) time.sleep(self.interval) def stop(self): self.thread_stop = True def test(): thread1 = timer(1,1) thread2 = timer(2,2) thread1.start() thread2.start() time.sleep(10) thread1.stop() thread2.stop() if __name__ == "__main__": test() 線程退出 下面的程序實現了多線程執行後自動退出,無需設定結束的時間也沒有使用join函數,主線程會自己退出。 #encoding=utf-8 import threading import time #如果flag設定爲1線程啓動後會直接退出 exitFlag = 0 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) print_time(self.name,5,self.counter) print("Exiting " + self.name) def print_time(threadName,delay,counter): while counter:#利用counter變量控制線程是否結束 if exitFlag: thread.exit() time.sleep(delay) print("%s:%s" %(threadName,time.ctime(time.time()))) counter -= 1 thread1 = MyThread(1,"線程1",1) thread2 = MyThread(1,"線程2",2) thread1.start() thread2.start() print("主進程結束!") Join函數用法 #encoding=utf-8 import threading import time def context(tJoin): print("in threadContext") tJoin.start() tJoin.join() print("out threadContext.") def join(): print("in threadJoin.") time.sleep(1) print("out threadJoin") tJoin = threading.Thread(target=join) tContext = threading.Thread(target=context,args=(tJoin,)) tContext.start() 主程序中tJoin = threading.Thread(target=join)這句代碼執行後,只是創建了一個線程對象 tJoin,但並未啓動線程。 tContext = threading.Thread(target=context, args=(tJoin,)) tContext.start() 上面這兩句執行後,創建了一個線程對象tContext並啓動該線程(打印in threadContext.) ,同時將tJoin線程對象作爲參數傳給context函數,在context函數中,啓動了tJoin這個線程,同時該線程又調用了join()函數(tJoin.join()),那tContext線程將等待tJoin這線程執行完成後,才能繼續tContext線程後面的,所以先執行join()函數,打印輸出下面兩句: in threadJoin.out threadJoin.tJoin線程執行結束後,繼續執行tContext線程,於是打印輸出了out threadContext.,於是就看到我們上面看到的輸出結果,並且無論執行多少次,結果都是這個順序。但如果將context()函數中tJoin.join()這句註釋掉,再執行該程序,打印輸出的結果順序就不定了,因爲此時這兩線程就是併發執行的; 守護線程 如果一個線程被設置爲守護線程,不會阻礙主線程的退出,會隨着主線程的退出而退出; #encoding=utf-8 import threading import time class MyThread(threading.Thread): def __init__(self,id): threading.Thread.__init__(self) def run(self): time.sleep(5) print("This is " + self.getName()) if __name__ == "__main__": t1 = MyThread(999) t1.setDaemon(True)#設置線程爲守護線程 t1.start() print("I am the father thread.") 由於線程啓動後,然後主線程就結束了,而子線程的run方法需要等待5秒,這時候子線程已經隨着主線程的退出而退出了;所以不會打印run裏面的語句; 從結果可以看出,調用了setDaemon()函數後,子線程被設置爲守護線程,主線程打印內容後就結束了,不管子線程是否執行完畢,一併被終止了。可見join()函數與setDaemon()函數功能是相反的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章