線程(英語:thread)是操做系統可以進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運做單位。同一進程中的多條線程將共享該進程中的所有系統資源,一個進程能夠有不少線程,每條線程並行執行不一樣的任務。python
四、優缺點算法
一、單線程執行數據庫
#coding=utf-8 import time def sayHi(): print("Hello I am Se7eN_HOU") time.sleep(1) if __name__ == "__main__": for i in range(5): sayHi()
運行結果爲:安全
二、多線程執行多線程
#coding=utf-8 import threading import time def sayHi(): print("Hello I am Se7eN_HOU") time.sleep(1) if __name__ == "__main__": for i in range(5): t = threading.Thread(target = sayHi) t.start()
運行結果爲:併發
說明app
start()
方法來啓動三、查看線程數量異步
#coding=utf-8 import threading from time import sleep,ctime def sing(): for i in range(3): print("正在唱歌...%d"%i) sleep(1) def dance(): for i in range(3): print("正在跳舞...%d"%i) sleep(1) if __name__ == '__main__': print("---開始---") t1 = threading.Thread(target=sing) t2 = threading.Thread(target=dance) t1.start() t2.start() while True: length = len(threading.enumerate()) print("當前運行的線程數爲:%d"%length) if length<=1: break sleep(0.5)
運行結果爲:async
經過使用threading模塊能完成多任務的程序開發,爲了讓每一個線程的封裝性更完美,因此使用threading模塊時,每每會定義一個新的子類class,只要繼承threading.Thread就能夠了,而後重寫run方法函數
#coding=utf-8 import threading import time class MyThread1(threading.Thread): def run(self): for i in range(3): time.sleep(1) msg = "I'm "+self.name+' @ '+str(i) #name屬性中保存的是當前線程的名字 print(msg) class MyThread2(threading.Thread): def run(self): for i in range(3): time.sleep(1) msg = "I'm "+self.name+' @ '+str(i) #name屬性中保存的是當前線程的名字 print(msg) if __name__ == '__main__': t1 = MyThread1() t2 = MyThread2() t1.start() t2.start()
運行結果爲:
說明
2. 線程的執行順序
#coding=utf-8 import threading import time class MyThread(threading.Thread): def run(self): for i in range(3): time.sleep(1) msg = "I'm "+self.name+' @ '+str(i) #name屬性中保存的是當前線程的名字 print(msg) def test(): for i in range(1,5): t=MyThread() t.start() if __name__ == '__main__': test()
運行結果爲:
說明:
從代碼和執行結果咱們能夠看出,多線程程序的執行順序是不肯定的。當執行到sleep語句時,線程將被阻塞(Blocked),到sleep結束後,線程進入就緒(Runnable)狀態,等待調度。而線程調度將自行選擇一個線程執行。上面的代碼中只能保證每一個線程都運行完整個run函數,可是線程的啓動順序、run函數中每次循環的執行順序都不能肯定。
總結:
線程的幾種狀態
一、共享全局變量
from threading import Thread import time g_num = 100 def work1(): global g_num for i in range(3): g_num += 1 print("----in work1, g_num is %d---"%g_num) def work2(): global g_num print("----in work2, g_num is %d---"%g_num) print("---線程建立以前,g_num is %d---"%g_num) t1 = Thread(target=work1) t1.start() #延時一會,保證t1線程中的事情作完 time.sleep(1) t2 = Thread(target=work2) t2.start()
運行結果爲:
---線程建立以前,g_num is 100--- ----in work1, g_num is 101--- ----in work1, g_num is 102--- ----in work1, g_num is 103--- ----in work2, g_num is 103---
二、列表當作實參傳遞到線程中
from threading import Thread import time def work1(nums): nums.append(44) print("----in work1---",nums) def work2(nums): #延時一會,保證t1線程中的事情作完 time.sleep(1) print("----in work2---",nums) g_nums = [11,22,33] t1 = Thread(target=work1, args=(g_nums,)) t1.start() t2 = Thread(target=work2, args=(g_nums,)) t2.start()
運行結果爲:
----in work1--- [11, 22, 33, 44] ----in work2--- [11, 22, 33, 44]
二、不共享局部變量
import threading from time import sleep def test(sleepTime): num=1 sleep(sleepTime) num+=1 print('---(%s)--num=%d'%(threading.current_thread(), num)) t1 = threading.Thread(target = test,args=(5,)) t2 = threading.Thread(target = test,args=(1,)) t1.start() t2.start()
運行結果爲:
---(<Thread(Thread-2, started 12236)>)--num=2 ---(<Thread(Thread-1, started 5644)>)--num=2
總結:
一、線程衝突
假設兩個線程t1和t2都要對num=0進行增1運算,t1和t2都各對num修改10次,num的最終的結果應該爲20。可是因爲是多線程訪問,有可能出現下面狀況:
在num=0時,t1取得num=0。此時系統把t1調度爲」sleeping」狀態,把t2轉換爲」running」狀態,t2也得到num=0。而後t2對獲得的值進行加1並賦給num,使得num=1。而後系統又把t2調度爲」sleeping」,把t1轉爲」running」。線程t1又把它以前獲得的0加1後賦值給num。這樣,明明t1和t2都完成了1次加1工做,但結果仍然是num=1。
from threading import Thread import time g_num = 0 def test1(): global g_num for i in range(1000000): g_num += 1 print("---test1---g_num=%d"%g_num) def test2(): global g_num for i in range(1000000): g_num += 1 print("---test2---g_num=%d"%g_num) p1 = Thread(target=test1) p1.start() #time.sleep(3) #取消屏蔽以後 再次運行程序,結果會不同 p2 = Thread(target=test2) p2.start() print("---g_num=%d---"%g_num) time.sleep(3) print("---g_num=%d---"%g_num)
運行結果爲:
---g_num=138526--- ---test1---g_num=1264273 ---test2---g_num=1374945 ---g_num=1374945---
取消屏蔽以後,再次運行結果以下:
---test1---g_num=1000000 ---g_num=1029220--- ---test2---g_num=2000000 ---g_num=2000000---
問題產生的緣由就是沒有控制多個線程對同一資源的訪問,對數據形成破壞,使得線程運行的結果不可預期。這種現象稱爲「線程不安全」。
2. 什麼是同步
3. 解決問題的思路
對於上面提出的那個計算錯誤的問題,能夠經過線程同步
來進行解決思路,以下:
1、線程互斥鎖介紹
當多個線程幾乎同時修改某一個共享數據的時候,須要進行同步控制,線程同步可以保證多個線程安全訪問競爭資源,最簡單的同步機制是引入互斥鎖。
互斥鎖爲資源引入一個狀態:鎖定/非鎖定
某個線程要更改共享數據時,先將其鎖定,此時資源的狀態爲「鎖定」,其餘線程不能更改;直到該線程釋放資源,將資源的狀態變成「非鎖定」,其餘的線程才能再次鎖定該資源。互斥鎖保證了每次只有一個線程進行寫入操做,從而保證了多線程狀況下數據的正確性。
threading模塊中定義了Lock類,能夠方便的處理鎖定:
#建立鎖 mutex = threading.Lock() #鎖定 mutex.acquire([blocking]) #釋放 mutex.release()
其中,鎖定方法acquire能夠有一個blocking參數。
使用互斥鎖實現上面的例子的代碼以下:
from threading import Thread, Lock import time g_num = 0 def test1(): global g_num for i in range(1000000): #True表示堵塞 即若是這個鎖在上鎖以前已經被上鎖了,那麼這個線程會在這裏一直等待到解鎖爲止 #False表示非堵塞,即無論本次調用可以成功上鎖,都不會卡在這,而是繼續執行下面的代碼 mutexFlag = mutex.acquire(True) if mutexFlag:#鎖住 g_num += 1 mutex.release()#解鎖 print("---test1---g_num=%d"%g_num) def test2(): global g_num for i in range(1000000): mutexFlag = mutex.acquire(True) #True表示堵塞 if mutexFlag:#鎖住 g_num += 1 mutex.release()#解鎖 print("---test2---g_num=%d"%g_num) #建立一個互斥鎖 #這個所默認是未上鎖的狀態 mutex = Lock() p1 = Thread(target=test1) p1.start() p2 = Thread(target=test2) p2.start() time.sleep(5) print("---g_num=%d---"%g_num)
運行結果爲:
---test1---g_num=1942922 ---test2---g_num=2000000 ---g_num=2000000---
二、上鎖解鎖過程
總結
鎖的好處:
鎖的壞處:
三、死鎖
在線程間共享多個資源的時候,若是兩個線程分別佔有一部分資源而且同時等待對方的資源,就會形成死鎖。
儘管死鎖不多發生,但一旦發生就會形成應用的中止響應。下面看一個死鎖的例子
#coding=utf-8 import threading import time class MyThread1(threading.Thread): def run(self): if mutexA.acquire(): print(self.name+'----do1---up----') time.sleep(1) if mutexB.acquire(): print(self.name+'----do1---down----') mutexB.release() mutexA.release() class MyThread2(threading.Thread): def run(self): if mutexB.acquire(): print(self.name+'----do2---up----') time.sleep(1) if mutexA.acquire(): print(self.name+'----do2---down----') mutexA.release() mutexB.release() mutexA = threading.Lock() mutexB = threading.Lock() if __name__ == '__main__': t1 = MyThread1() t2 = MyThread2() t1.start() t2.start()
運行結果爲:
Thread-1----do1---up----
Thread-2----do2---up----
此時已經進入到了死鎖狀態
from threading import Thread,Lock from time import sleep class Task1(Thread): def run(self): while True: if lock1.acquire(): print("------Task 1 -----") sleep(0.5) lock2.release() class Task2(Thread): def run(self): while True: if lock2.acquire(): print("------Task 2 -----") sleep(0.5) lock3.release() class Task3(Thread): def run(self): while True: if lock3.acquire(): print("------Task 3 -----") sleep(0.5) lock1.release() #使用Lock建立出的鎖默認沒有「鎖上」 lock1 = Lock() #建立另一把鎖,而且「鎖上」 lock2 = Lock() lock2.acquire() #建立另一把鎖,而且「鎖上」 lock3 = Lock() lock3.acquire() t1 = Task1() t2 = Task2() t3 = Task3() t1.start() t2.start() t3.start()
運行結果爲:
可使用互斥鎖完成多個任務,有序的進程工做,這就是線程的同步
在多線程環境下,每一個線程都有本身的數據。一個線程使用本身的局部變量比使用全局變量好,由於局部變量只有線程本身能看見,不會影響其餘線程,而全局變量的修改必須加鎖。
import threading # 建立全局ThreadLocal對象: local_school = threading.local() def process_student(): # 獲取當前線程關聯的student: std = local_school.student print('Hello, %s (in %s)' % (std, threading.current_thread().name)) def process_thread(name): # 綁定ThreadLocal的student: local_school.student = name process_student() t1 = threading.Thread(target= process_thread, args=("Se7eN",), name='Thread-A') t2 = threading.Thread(target= process_thread, args=("HOU",), name='Thread-B') t1.start() t2.start() t1.join() t2.join()
運行結果爲:
Hello, Se7eN (in Thread-A) Hello, HOU (in Thread-B)
說明:
def test(): print("---進程池中的進程---pid=%d,ppid=%d--"%(os.getpid(),os.getppid())) for i in range(3): print("----%d---"%i) time.sleep(1) return "hahah" def test2(args): print("---callback func--pid=%d"%os.getpid()) print("---callback func--args=%s"%args) pool = Pool(3) pool.apply_async(func=test,callback=test2) time.sleep(5) print("----主進程-pid=%d----"%os.getpid())
運行結果爲:
---進程池中的進程---pid=9401,ppid=9400--
----0---
----1---
----2---
---callback func--pid=9400
---callback func--args=hahah
----主進程-pid=9400----
不管是並行仍是併發,在用戶看來都是'同時'運行的,無論是進程仍是線程,都只是一個任務而已,真是幹活的是cpu,cpu來作這些任務,而一個cpu同一時刻只能執行一個任務。
併發是僞並行,即看起來是同時運行。單個cpu+多道技術就能夠實現併發,(並行也屬於併發),簡單的能夠理解爲快速在多個線程來回切換,感受好像同時在作多個事情。
只有具有多個cpu才能實現並行,單核下,能夠利用多道技術,多個核,每一個核也均可以利用多道技術(多道技術是針對單核而言的)。
有四個核,六個任務,這樣同一時間有四個任務被執行,假設分別被分配給了cpu1,cpu2,cpu3,cpu4,一旦任務1遇到I/O就被迫中斷執行,此時任務5就拿到cpu1的時間片去執行,這就是單核下的多道技術 ,而一旦任務1的I/O結束了,操做系統會從新調用它(需知進程的調度、分配給哪一個cpu運行,由操做系統說了算),可能被分配給四個cpu中的任意一個去執行。
多道技術:內存中同時存入多道(多個)程序,cpu從一個進程快速切換到另一個,使每一個進程各自運行幾十或幾百毫秒,這樣,雖然在某一個瞬間,一個cpu只能執行一個任務,但在1秒內,cpu卻能夠運行多個進程,這就給人產生了並行的錯覺,即僞併發,以此來區分多處理器操做系統的真正硬件並行(多個cpu共享同一個物理內存)
舉個例子,打電話時就是同步通訊,發短息時就是異步通訊。