global interpreter lock(cpython)html
同一時刻只有一個線程運行在一個cpu上執行字節碼(沒法將多個線程映射到多個cpu上)python
import dis def add(a): a = a + 1 return a print(dis.dis(add))
每次的結果都不同 線程之間的安全問題編程
GIL會根據執行的直接碼行數或者時間片釋放GIL安全
遇到IO操做時主動釋放多線程
total = 0 def add(): #1. dosomething1 #2. io操做 # 1. dosomething3 global total for i in range(1000000): total += 1 def desc(): global total for i in range(1000000): total -= 1 import threading thread1 = threading.Thread(target=add) thread2 = threading.Thread(target=desc) thread1.start() thread2.start() thread1.join() thread2.join() print(total)
操做系統可以調度的的最小單位是進程,由於進程對系統的資源消耗很是大,因此後期就演變成了線程,線程其實是依賴於咱們的進程(任務管理器中咱們實際上能看到的實際上是進程 ),操做系統能調度的最小單元是線程。性能
對於io操做爲主的編程來講,多進程和多先產出的性能差異不大,甚至多線程比多進程的性能還高,由於多線程編程更加輕量級。ui
import time from threading import Thread def get_detail_html(url): print("get detail html started") time.sleep(2) print("get detail html end") def get_detail_url(url): print("get detail url started") time.sleep(4) print("get detail url end") if __name__ == '__main__': thread1 = Thread(target=get_detail_html, args=("",)) thread2 = Thread(target=get_detail_url, args=("",)) # 設置爲守護線程 當主線程運行完時 子線程被kill掉 thread1.setDaemon(True) thread2.setDaemon(True) start_time = time.time() thread1.start() thread2.start() # 設置爲阻塞 等待線程運行完再關閉主線程 thread1.join() thread2.join() # 默認狀況下 主線程退出與時 子線程不會被kill掉 print("last time: {}".format(time.time() - start_time))
import time import threading def get_detail_html(url): print("get detail html started") time.sleep(2) print("get detail html end") def get_detail_url(url): print("get detail url started") time.sleep(4) print("get detail url end") #2. 經過集成Thread來實現多線程 class GetDetailHtml(threading.Thread): def __init__(self, name): super().__init__(name=name) def run(self): print("get detail html started") time.sleep(2) print("get detail html end") class GetDetailUrl(threading.Thread): def __init__(self, name): super().__init__(name=name) def run(self): print("get detail url started") time.sleep(4) print("get detail url end") if __name__ == "__main__": thread1 = GetDetailHtml("get_detail_html") thread2 = GetDetailUrl("get_detail_url") start_time = time.time() thread1.start() thread2.start() thread1.join() thread2.join() #當主線程退出的時候, 子線程kill掉 print ("last time: {}".format(time.time()-start_time))
使用queueurl
# filename: thread_queue_test.py # 經過queue的方式進行線程間同步 from queue import Queue import time import threading def get_detail_html(queue): # 死循環 爬取文章詳情頁 while True: url = queue.get() # for url in detail_url_list: print("get detail html started") time.sleep(2) print("get detail html end") def get_detail_url(queue): # 死循環 爬取文章列表頁 while True: print("get detail url started") time.sleep(4) for i in range(20): # put 等到有空閒位置 再放入 # put_nowait 非阻塞方式 queue.put("http://projectsedu.com/{id}".format(id=i)) print("get detail url end") # 1. 線程通訊方式- 共享變量 if __name__ == "__main__": detail_url_queue = Queue(maxsize=1000) thread_detail_url = threading.Thread(target=get_detail_url, args=(detail_url_queue,)) for i in range(10): html_thread = threading.Thread(target=get_detail_html, args=(detail_url_queue,)) html_thread.start() start_time = time.time() # 調用task_down從主線程退出 detail_url_queue.task_done() # 從queue的角度阻塞 detail_url_queue.join() print("last time: {}".format(time.time() - start_time))
在多線程編程中必需要面對的問題操作系統
# 沒有鎖 def add1(a): a += 1 def desc1(a): a -= 1 """add 1. load a a = 0 2. load 1 1 3. + 1 4. 賦值給a a=1 """ """add 1. load a a = 0 2. load 1 1 3. - 1 4. 賦值給a a=-1 """ import dis print(dis.dis(add1)) print(dis.dis(desc1))
用鎖會影響性能,鎖會引發死鎖(兩次獲取鎖,獲取鎖以後不釋放,互相等待(a須要b的資源 b須要a的資源))線程
import threading from threading import Lock total = 0 # 定義一把鎖 lock = Lock() def add(): global total global lock for i in range(1000000): # 獲取鎖 lock.acquire() total += 1 # 釋放鎖 lock.release() def desc(): global total for i in range(1000000): lock.acquire() total -= 1 lock.release() thread1 = threading.Thread(target=add) thread2 = threading.Thread(target=desc) thread1.start() thread2.start() thread1.join() thread2.join() print(total)
""" A(a、b) acquire (a) acquire (b) B(a、b) acquire (b) acquire (a) # 解決辦法 B(a、b) acquire (a) acquire (b) """
import threading from threading import RLock total = 0 # 可重入鎖 能夠在同一個線程中可載入屢次 lock = RLock() def add(lock): global total for i in range(1000000): # 獲取鎖 lock.acquire() lock.acquire() total += 1 do_something(lock) # 釋放鎖 lock.release() lock.release() def desc(): global total for i in range(1000000): lock.acquire() total -= 1 lock.release() def do_something(lock): lock.acquire() # do something lock.release() thread1 = threading.Thread(target=add) thread2 = threading.Thread(target=desc) thread1.start() thread2.start() thread1.join() thread2.join() print(total)
用於複雜的線程間同步
# 沒有條件鎖 不能實現對話 import threading class XiaoAi(threading.Thread): def __init__(self, lock): super().__init__(name="小愛") self.lock = lock def run(self): self.lock.acquire() print("{} : 在 ".format(self.name)) self.lock.release() self.lock.acquire() print("{} : 好啊 ".format(self.name)) self.lock.release() class TianMao(threading.Thread): def __init__(self, lock): super().__init__(name="天貓精靈") self.lock = lock def run(self): self.lock.acquire() print("{} : 小愛同窗 ".format(self.name)) self.lock.release() self.lock.acquire() print("{} : 咱們來對古詩吧 ".format(self.name)) self.lock.release() if __name__ == "__main__": cond = threading.Condition() xiaoai = XiaoAi(cond) tianmao = TianMao(cond) xiaoai.start() tianmao.start()
# 條件鎖 import threading class XiaoAi(threading.Thread): def __init__(self, cond): super().__init__(name="小愛") self.cond = cond def run(self): with self.cond: self.cond.wait() print("{} : 在 ".format(self.name)) self.cond.notify() self.cond.wait() print("{} : 好啊 ".format(self.name)) self.cond.notify() self.cond.wait() print("{} : 君住長江尾 ".format(self.name)) self.cond.notify() self.cond.wait() print("{} : 共飲長江水 ".format(self.name)) self.cond.notify() self.cond.wait() print("{} : 此恨什麼時候已 ".format(self.name)) self.cond.notify() self.cond.wait() print("{} : 定不負相思意 ".format(self.name)) self.cond.notify() class TianMao(threading.Thread): def __init__(self, cond): super().__init__(name="天貓精靈") self.cond = cond def run(self): with self.cond: print("{} : 小愛同窗 ".format(self.name)) self.cond.notify() self.cond.wait() print("{} : 咱們來對古詩吧 ".format(self.name)) self.cond.notify() self.cond.wait() print("{} : 我住長江頭 ".format(self.name)) self.cond.notify() self.cond.wait() print("{} : 日日思君不見君 ".format(self.name)) self.cond.notify() self.cond.wait() print("{} : 此水幾時休 ".format(self.name)) self.cond.notify() self.cond.wait() print("{} : 只願君心似我心 ".format(self.name)) self.cond.notify() self.cond.wait() if __name__ == "__main__": from concurrent import futures cond = threading.Condition() xiaoai = XiaoAi(cond) tianmao = TianMao(cond) # 啓動順序很重要 # 在調用with cond以後才能調用wait或者notify方法 # condition有兩層鎖, 一把底層鎖會在線程調用了wait方法的時候釋放, # 上面的鎖會在每次調用wait的時候分配一把並放入到cond的等待隊列中, # 等到notify方法的喚醒 xiaoai.start() tianmao.start()