央視報道“抽查不合格電磁爐”出現“小米”字樣?王化:假貨
什麼是執行緒
程序:資源單位(起一個程序僅僅只是在記憶體空間中開闢一塊獨立的空間)
執行緒:執行單位(真正被cpu執行的其實是程序裡面的執行緒,執行緒指的就是程式碼的執行過程,執行程式碼中所需要使用到的資源都找所在的程序索要)
每一個程序肯定自帶一個執行緒
將作業系統比喻成一個大的工廠,那麼程序就相當於工廠裡面的車間,而執行緒就是車間裡面的流水線。
為什麼要有執行緒
開設程序
1.申請記憶體空間 耗資源
2.“拷貝程式碼” 耗資源
開執行緒
一個程序內可以開設多個執行緒,在用一個程序內開設多個執行緒無需再次申請記憶體空間操作
總結:
開設執行緒的開銷要遠遠的小於程序的開銷
同一個程序下的多個執行緒資料是共享的!!!
開設執行緒的兩種方式
開啟執行緒不需要在main下面執行程式碼 直接書寫就可以,但是還是習慣性的將啟動命令寫在main下面
第一種方式
# from threading import Thread # import time # # # def task(name): # print('%s is running'%name) # time.sleep(1) # print('%s is over'%name) # # t = Thread(target=task,args=('ys',)) # t.start() # 建立執行緒的開銷非常小 幾乎是程式碼一執行執行緒就已經建立了# print('主')
第二種方式
from threading import Thread import time class MyThead(Thread): def __init__(self, name): # 重寫了別人的方法 又不知道別人的方法裡有啥 你就呼叫父類的方法 super().__init__() self.name = name def run(self): print('%s is running'%self.name) time.sleep(1) print('egon DSB') if __name__ == '__main__': t = MyThead('ys') t.start() print('主')
執行緒物件的jion方法
from threading import Thread import time def task(name): print('%s is running'%name) time.sleep(3) print('%s is over'%name) if __name__ == '__main__': t = Thread(target=task,args=('ys',)) t.start() t.join() # 主執行緒等待子執行緒執行結束再執行 print('主')
同一個程序下的多個執行緒資料是否共享
from threading import Thread import time money = 100 def task(): global money money = 666 print(money) if __name__ == '__main__': t = Thread(target=task) t.start() t.join() print(money)
執行緒物件
統計當前正在活躍的執行緒數
print('主',active_count())
獲取執行緒名字
print('主',current_thread().name)
驗證執行緒是否在同一個程序
from threading import Thread, active_count, current_thread import os,time def task(n): print('hello world',os.getpid()) time.sleep(n) if __name__ == '__main__': t = Thread(target=task,args=(1,)) t.start() print('主',os.getpid())
程式碼
from threading import Thread, active_count, current_thread import os,time def task(n): # print('hello world',os.getpid()) print('hello world',current_thread().name) time.sleep(n) if __name__ == '__main__': t = Thread(target=task,args=(1,)) t1 = Thread(target=task,args=(2,)) t.start() t1.start() t.join() print('主',active_count()) # 統計當前正在活躍的執行緒數 # print('主',os.getpid()) # print('主',current_thread().name) # 獲取執行緒名字
守護執行緒
主執行緒執行結束之後不會立刻結束 會等待所有其他非守護執行緒結束才會結束,因為主執行緒的結束意味著所在的程序的結束
# from threading import Thread # import time # # # def task(name): # print('%s is running'%name) # time.sleep(1) # print('%s is over'%name) # # # if __name__ == '__main__': # t = Thread(target=task,args=('egon',)) # t.daemon = True # t.start() # print('主')
迷惑性的例子
from threading import Thread import time def foo(): print(123) time.sleep(1) print('end123') def func(): print(456) time.sleep(3) print('end456') if __name__ == '__main__': t1 = Thread(target=foo) t2 = Thread(target=func) t1.daemon = True t1.start() t2.start() print('主.......')
執行緒互斥鎖
lock
from threading import Thread,Lock import time money = 100 mutex = Lock() def task(): global money mutex.acquire() tmp = money time.sleep(0.1) money = tmp - 1 mutex.release() if __name__ == '__main__': t_list = [] for i in range(100): t = Thread(target=task) t.start() t_list.append(t) for t in t_list: t.join() print(money)
當多個人同時操作同一份資料,資料不安全的情況下,就加鎖處理。將併發變成序列,犧牲了效率,增加了安全
GIL全域性直譯器鎖理論
定義
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
解譯
在CPython直譯器中GIL是一把互斥鎖,用來阻止同一個程序下的多個執行緒的同時執行(同一個程序下的多個執行緒無法利用多核優勢)。
因為cpython中的記憶體管理不是執行緒安全的
記憶體管理(垃圾回收機制)
引用計數
標記清除
分代回收
總結:
1、GIL不是python的特點而是CPython直譯器的特點
2、GIL是保證直譯器級別的資料的安全
3、GIL會導致同一個程序下的多個執行緒的無法同時執行即無法利用多核優勢
4、針對不同的資料還是需要加不同的鎖處理
5、解釋型語言的通病:同一個程序下多個執行緒無法利用多核優勢
GIL與普通互斥鎖的區別
from threading import Thread,Lock import time mutex = Lock() money = 100 def task(): global money
# 互斥鎖的另種寫法 # with mutex: # tmp = money # time.sleep(0.1) # money = tmp -1 mutex.acquire() tmp = money time.sleep(0.1) # 只要你進入IO了 GIL會自動釋放 money = tmp - 1 mutex.release() if __name__ == '__main__': t_list = [] for i in range(100): t = Thread(target=task) t.start() t_list.append(t) for t in t_list: t.join() print(money) """ 100個執行緒起起來之後 要先去搶GIL 我進入io GIL自動釋放 但是我手上還有一個自己的互斥鎖 其他執行緒雖然搶到了GIL但是搶不到互斥鎖 最終GIL還是回到你的手上 你去操作資料 """
同一個程序下的多執行緒無法利用多核優勢,是不是就沒有用了
理論
""" 多執行緒是否有用要看具體情況 單核:四個任務(IO密集型\計算密集型) 多核:四個任務(IO密集型\計算密集型) """ # 計算密集型 每個任務都需要10s 單核(不用考慮了) 多程序:額外的消耗資源 多執行緒:介紹開銷 多核 多程序:總耗時 10+ 多執行緒:總耗時 40+ # IO密集型 多核 多程序:相對浪費資源 多執行緒:更加節省資源
程式碼
計算密集型
# from multiprocessing import Process # from threading import Thread # import os,time # # # def work(): # res = 0 # for i in range(10000000): # res *= i # # if __name__ == '__main__': # l = [] # print(os.cpu_count()) # 獲取當前計算機CPU個數 # start_time = time.time() # for i in range(12): # p = Process(target=work) # 1.4679949283599854 # t = Thread(target=work) # 5.698534250259399 # t.start() # # p.start() # # l.append(p) # l.append(t) # for p in l: # p.join() # print(time.time()-start_time)
IO密集型
from multiprocessing import Process from threading import Thread import os,time def work(): time.sleep(2) if __name__ == '__main__': l = [] print(os.cpu_count()) # 獲取當前計算機CPU個數 start_time = time.time() for i in range(4000): # p = Process(target=work) # 21.149890184402466 t = Thread(target=work) # 3.007986068725586 t.start() # p.start() # l.append(p) l.append(t) for p in l: p.join() print(time.time()-start_time)
總結
多程序和多執行緒都有各自的優勢
寫專案的時候通常可以多程序下面再開設多執行緒,這樣的話既可以利用多核也可以節省資源消耗
死鎖與遞迴鎖
當你知道鎖的使用搶鎖必須要釋放鎖,其實你在操作鎖的時候也極其容易產生死鎖現象(整個程式卡死 阻塞)
from threading import Thread, Lock import time mutexA = Lock() mutexB = Lock() # 類只要加括號多次 產生的肯定是不同的物件 # 如果你想要實現多次加括號得到的是相同的物件 單例模式 class MyThead(Thread): def run(self): self.func1() self.func2() def func1(self): mutexA.acquire() print('%s 搶到A鎖'% self.name) # 獲取當前執行緒名 mutexB.acquire() print('%s 搶到B鎖'% self.name) mutexB.release() mutexA.release() def func2(self): mutexB.acquire() print('%s 搶到B鎖'% self.name) time.sleep(2) mutexA.acquire() print('%s 搶到A鎖'% self.name) # 獲取當前執行緒名 mutexA.release() mutexB.release() if __name__ == '__main__': for i in range(10): t = MyThead() t.start()
遞迴鎖(RLock)
特點
可以被連續的acquire和release
但是隻能被第一個搶到這把鎖執行上述操作
它的內部有一個計數器 每acquire一次計數加一 每realse一次計數減一,只要計數不為0 那麼其他人都無法搶到該鎖
實現
from threading import Thread, Lock, RLock import time mutexA = mutexB = RLock() class MyThead(Thread): def run(self): self.func1() self.func2() def func1(self): mutexA.acquire() print('%s 搶到A鎖'% self.name) # 獲取當前執行緒名 mutexB.acquire() print('%s 搶到B鎖'% self.name) mutexB.release() mutexA.release() def func2(self): mutexB.acquire() print('%s 搶到B鎖'% self.name) time.sleep(2) mutexA.acquire() print('%s 搶到A鎖'% self.name) # 獲取當前執行緒名 mutexA.release() mutexB.release() if __name__ == '__main__': for i in range(10): t = MyThead() t.start()
訊號量
訊號量在不同的階段可能對應不同的技術點,在併發程式設計中訊號量指的是鎖!!!
大白話理解:
如果我們將互斥鎖比喻成一個廁所的話,那麼訊號量就相當於多個廁所
程式碼演示
from threading import Thread, Semaphore import time import random sm = Semaphore(5) # 括號內寫數字 寫幾就表示開設幾個坑位 def task(name): sm.acquire() print('%s 正在蹲坑'% name) time.sleep(random.randint(1, 5)) sm.release() if __name__ == '__main__': for i in range(20): t = Thread(target=task, args=('傘兵%s號'%i, )) t.start()
Event事件
一些程序/執行緒需要等待另外一些程序/執行緒執行完畢之後才能執行,類似於發射訊號一樣
程式碼演示
from threading import Thread, Event import time event = Event() # 造了一個紅綠燈 def light(): print('紅燈亮著的') time.sleep(3) print('綠燈亮了') # 告訴等待紅燈的人可以走了 event.set() def car(name): print('%s 車正在燈紅燈'%name) event.wait() # 等待別人給你發訊號 print('%s 車加油門飆車走了'%name) if __name__ == '__main__': t = Thread(target=light) t.start() for i in range(20): t = Thread(target=car, args=('%s'%i, )) t.start()
執行緒q
同一個程序下多個執行緒資料是共享的,為什麼同一個程序下還會去使用佇列呢,因為佇列是管道 + 鎖,所以用佇列還是為了保證資料的安全
1、佇列q 先進先出
# q = queue.Queue(3) # q.put(1) # q.get() # q.get_nowait() # q.get(timeout=3) # q.full() # q.empty()
2、後進先出q
# q = queue.LifoQueue(3) # last in first out # q.put(1) # q.put(2) # q.put(3) # print(q.get()) # 3
3、優先順序q 你可以給放入佇列中的資料設定進出的優先順序
q = queue.PriorityQueue(4) q.put((10, '111')) q.put((100, '222')) q.put((0, '333')) q.put((-5, '444')) print(q.get()) # (-5, '444') # put括號內放一個元祖 第一個放數字表示優先順序 # 需要注意的是 數字越小優先順序越高!!!