1、線程python
是進程中的一個實體,是被系統獨立調度和分派的基本單位,線程本身不擁有系統資源,只擁有一點在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的所有資源。一個線程能夠建立和撤消另外一個線程,同一進程中的多個線程之間能夠併發執行。線程有就緒、阻塞和運行三種基本狀態。安全
2、線程的兩種建立方式併發
1.第一種方式app
from threading import Thread T = Thread(function,args=(arg1,arg2,...)) T.start()
2.第二種方式編輯器
from threading import Thread Class MThread(Thread): Def run(self): Pass mt = MThread() mt.start()
3、線程的空間ui
import os def f(): print(os.getpid()) t = Thread(target=f,) t.start()
2.線程空間不是隔離的spa
import os def f(): print('子線程pid:', os.getpid())#(1) t = Thread(target=f,) t.start() print('主線程pid:', os.getpid())#(2) 說明:(1)、(2)兩處的值是同樣的。可見,線程都是在一個進程內,而一個進程都有本身獨立的空間。
3.線程與進程的效率對比操作系統
(1)只是建立線程和進程線程
def f1(): pass def f2(): pass if __name__ == '__main__': t_s = time.time() t_lst = [] for i in range(20): t = Thread(target=f1,) t.start() t_lst.append(t) for tt in t_lst: tt.join() t_e = time.time() p_s = time.time() p_lst = [] for i in range(20): p = Process(target=f2,) p.start() p_lst.append(p) for pp in p_lst: pp.join() p_e = time.time() print('線程建立時間:', t_e - t_s) print('進程建立時間:', p_e - p_s) 結果: 線程建立時間: 0.004002809524536133 進程建立時間: 1.838303565979004
經過結果能夠看出來,一樣建立20個,線程只須要了0.004秒,也就是4毫秒,而建立進程倒是1838.3毫秒。得出一個結論:進程建立過程比線程建立過程要麻煩。由於進程建立的過程是,須要在內存裏開闢一個空間,把解釋器代碼加載進來,還須要把本身寫的程序也加載進去。而線程是進程裏的一個實體,只須要進程中的一點資源。全部線程共享進程中的全部資源。blog
(2)再來一個線程中有I/O阻塞的比較
(2)def f1(): print('f1>>>>>aaaaa') time.sleep(1) print("f1>>>>>bbbbb") def f2(): print('f2>>>>>aaaaa') time.sleep(1) print("f2>>>>>bbbbb") if __name__ == '__main__': t_s = time.time() t_lst = [] for i in range(20): t = Thread(target=f1,) t.start() t_lst.append(t) for tt in t_lst: tt.join() t_e = time.time() p_s = time.time() p_lst = [] for i in range(20): p = Process(target=f2,) p.start() p_lst.append(p) for pp in p_lst: pp.join() p_e = time.time() print('線程建立時間:', t_e - t_s) print('進程建立時間:', p_e - p_s) 結果: 。。。。。 線程建立時間: 1.0074326992034912 進程建立時間: 3.006932497024536
線程只是在一個進程中操做,這樣就利用多道技術,實現了併發(Python中的線程不能實現多核方式,後面介紹)。進程卻要建立20個,開銷,時間都要比線程的多。
(3)這個是計算型的操做
def f1(): # print('f1>>>>>aaaaa') # time.sleep(1) # print("f1>>>>>bbbbb") n = 10 for i in range(10000000): n += i def f2(): # print('f2>>>>>aaaaa') # time.sleep(1) # print("f2>>>>>bbbbb") n = 10 for i in range(10000000): n += i if __name__ == '__main__': t_s = time.time() t_lst = [] for i in range(5): t = Thread(target=f1,) t.start() t_lst.append(t) for tt in t_lst: tt.join() t_e = time.time() p_s = time.time() p_lst = [] for i in range(5): p = Process(target=f2,) p.start() p_lst.append(p) for pp in p_lst: pp.join() p_e = time.time() print('線程操做時間:', t_e - t_s) print('進程操做時間:', p_e - p_s) 結果: 線程操做時間: 3.7418324947357178 進程操做時間: 2.925071954727173
好神奇啊,此次進程使用的時間短了,線程的卻多了。這也是由於,線程不能使用多核技術。
4、鎖
先看第一段代碼:
num = 100 def f(): global num tmp = num tmp -= 1 time.sleep(0.01) num = tmp if __name__ == '__main__': t_lst = [] for i in range(10): t1 = Thread(target=f) t1.start() t_lst.append(t1) [t.join() for t in t_lst] print(num) 執行結果: 99
上面這段代碼的做用是,想循環10次,爲num每次減1,可是結果倒是99。緣由就是,線程執行的太快了,致使10個線程都執行到了time.sleep(0.01)這裏,而後都去等着操做系統再次調用。調用到了後,再次執行賦值的操做,這樣num就是99了。
爲了數據安全,加把鎖吧,看代碼:
num = 100 def f(loc): global num loc.acquire() tmp = num tmp -= 1 time.sleep(0.01) num = tmp loc.release() if __name__ == '__main__': loc = Lock() t_lst = [] for i in range(10): t1 = Thread(target=f,args=(loc,)) t1.start() t_lst.append(t1) [t.join() for t in t_lst] print(num) 結果: 90
此次完成了心願,結果是90了。當第一個線程拿到鎖後,執行全部的操做,即使有sleep須要等待,其餘9個線程也得等着,必須等着第一完成了。第一個完成後,接下來就是第二個線程,也跟第一個同樣,無論其餘的怎麼着急,就是慢慢執行本身的。依次類推,最後結果就是90了。
2.死鎖
在工做中有可能會有鎖的嵌套,稍有不慎,那麼就會死鎖了。仍是看代碼:
# def f1(locA, locB): # locA.acquire() # print('f1aaaaaaaaaaaa') # time.sleep(0.1) # locB.acquire() # print('f1bbbbbbbbb') # locB.release() # locA.release() # def f2(locA, locB): # locB.acquire() # print('f2--aaaaaaaa') # time.sleep(0.1) # locA.acquire() # print('f2--bbbbbbbb') # locA.release() # locB.release() # if __name__ == '__main__': # locA = Lock() # locB = Lock() # # t1 = Thread(target=f1, args=(locA, locB)) # t2 = Thread(target=f2, args=(locA, locB)) # # t1.start() # t2.start()
運行後,會看到程序一直處於運行中。不會往下走了,由於線程t1等着要locB的鎖,而線程t2等着要線程locA的鎖,從而致使兩邊就這樣互相等待着,程序一直不運行。
3.遞歸鎖
爲了解決死鎖,Python出現了遞歸鎖。看代碼:
# def f1(locA, locB): # locA.acquire() # print('f1aaaaaaaaaaaa') # time.sleep(0.1) # locB.acquire() # print('f1bbbbbbbbb') # locB.release() # locA.release() # def f2(locA, locB): # # locB.acquire() # with locB: # print('f2--aaaaaaaa') # time.sleep(0.1) # # locA.acquire() # with locA: # print('f2--bbbbbbbb') # # locA.release() # # locB.release() # if __name__ == '__main__': # locA = locB = RLock() # t1 = Thread(target=f1, args=(locA, locB)) # t2 = Thread(target=f2, args=(locA, locB)) # # t1.start() # t2.start()
遞歸鎖,當acquire時,內部會有計數器,加1;前面acquire幾回,就會記爲幾,當釋放時,會依次再把鎖釋放掉。
4.GIL
這個是加載cpython解釋器上的一把鎖,由於它而致使Python的線程不能使用多核技術,只能串行。看圖:
接下來看圖說話:當咱們運行了一個py文件後,其實就是啓動了一個進程,操做系統就會把代碼讀取到內容中,併爲這個進程分配相應的內存空間。在這個進程中,還會讀入解釋器的代碼。編輯器會把這些代碼處理成C語言的字節碼,而後虛擬機把這些字節碼再處理成二進制,這樣CPU就能夠處理了。
GIL鎖就是加在解釋器上的,每次只能有一個線程拿到這個GIL鎖,其餘的線程只能等待前面的把鎖釋放了再拿。遇到I/O阻塞的,操做系統,會把GIL鎖拿回來,交給下一個線程。若是再遇到I/O阻塞還會繼續拿過來交給下一個線程。這樣就實現了相似單核的併發。
還有一些計算型的程序,使用線程時,第一個線程拿到了GIL鎖後,會一直執行完畢。而後,操做系統把鎖再交給下一個線程,這個線程仍是從頭執行到尾。也就是遇到計算型,中間沒有I/O阻塞的程序,就是串行,一個一個線程去執行。
這裏,就能夠看出前面進程和線程比較時,關於時間多少的問題了。