1、並沒有按照執行順序等待結果
2、而是所有的任務都在非同步執行著
很明顯的非同步,大家都相互執行著(非同步過程),誰先結束我就先拿誰的結果,而我等待的過程就是一個阻塞過程,整體就是一個非同步阻塞。
使用生產者消費者模型舉例:
import requests from multiprocessing import Process, Queue url = [ 'https://www.baidu.com', 'https://www.taobao.com', 'https://www.jd.com', 'https://i.cnblogs.com' ] def producer(name, url, q): # 生產者負責爬取網頁,獲取狀態碼放進列隊 ret = requests.get(url) q.put((name, ret.status_code)) def consumer(q): # 消費者 while True: conn = q.get() if conn: print(conn) else:break if __name__ == '__main__': q = Queue() p_list = [] for i in url: p = Process(target=producer, args=(i, i, q)) p.start() p_list.append(p) Process(target=consumer, args=(q,)).start() # 輸出 ('https://www.jd.com', 200) ('https://www.baidu.com', 200) ('https://www.taobao.com', 200) ('https://i.cnblogs.com', 200)
二、Manager模組程式間資料共享(瞭解)
程式之間是可以實現資料共享的,通過Manager這個類就可以實現了,只不過呢實現這個資料共享的代價是非常大的,即需要對這個共享的資料進行加鎖,又要去操作許多不必要的操作。
一般情況下我們不使用這種方法,使用程式的原因就是因為程式之間資料隔離,如果非要讓大量資料共享,就證明這個場景不適合用程式去解決
from multiprocessing import Process, Manager, Lock def change_dic(dic, lock): with lock: # 使用資料共享需要加鎖才能保證資料的安全,和搶票例子一樣 dic['count'] -= 1 if __name__ == '__main__': m = Manager() lock = Lock() dic = m.dict({'count': 100}) # 可以是字典,列表 p_l = [] for i in range(100): p = Process(target=change_dic, args=(dic, lock)) p.start() p_l.append(p) for p in p_l: p.join() print(dic) # 輸出 {'count': 0}
三、執行緒
程式:資料隔離,資源分配的最小單位,可以利用多核,由作業系統排程,資料不安全,開啟關閉切換時間開銷大
程式只是用來把資源集中到一起(程式只是一個資源單位,或者說資源集合),而執行緒才是CPU的執行單位。
執行緒:資料共享,作業系統排程的最小單位,可以利用多核,由作業系統排程,資料不安全,開啟關閉切換時間開銷小
能被作業系統排程(給CPU執行)的最小單位,同一個程式中的多個執行緒同時能被CPU執行,
執行緒與程式的區別:
1、執行緒共享建立它的程式的地址空間;程式具有自己的地址空間。
2、執行緒可以直接訪問其程式的資料;程式具有父程式資料的副本。
3、執行緒可以直接與程式中的其他執行緒通訊;程式必須使用程式間通訊與同級程式進行通訊。
4、新執行緒很容易建立;新程式需要複製父程式。
5、執行緒可以對同一程式的執行緒使用相當大的控制權;程式只能控制子程式。
6、對主執行緒的更改可能會影響該程式對其他執行緒的行為;對父程式的修改不會影響子程式。
執行緒的特點:
1、輕型實體:執行緒中的實體基本不擁有系統資源。
2、獨立排程和分派的基本單位:由於執行緒很輕,故執行緒的切換非常迅速且開銷小(同一程式中的)。
3、共享程式資源:執行緒在同一程式中的各個執行緒,都可以共享該程式擁有的所有資源。
4、可併發執行:在一個程式中的多個執行緒之間,可以併發執行,甚至允許在一個程式中所有執行緒都能併發執行
全域性直譯器鎖 GIL (global interpreter lock)
在CPython中的多執行緒中,垃圾回收機制(gc)理解為相當於一個執行緒,它使用了引用計數 + 分代回收,來對變數中的引用計數為0的變數進行回收,但是如果在多執行緒中當CPU同時對多個執行緒的變數進行操作,gc 都要兼顧對每條執行緒的變數做引用計數,這樣的話還是會造成和搶票例子一樣的效果,為此誕生了一個全域性直譯器鎖,它讓每條執行緒中的變數要被CPU操作時,同時並且只有一條執行緒能被CPU操作。
全域性直譯器鎖的出現主要是為了完成gc的回收機制,對不同執行緒的引用計數的變化記錄更加精準
但是全域性直譯器鎖導致了同一個程式中的多個執行緒只能有一個執行緒真正被CPU執行
節省的是io操作時間,而不是CPU計算的時間,因為CPU的計算速度非常快,大部分情況下,我們沒有辦法把一條程式中所有的io操作規避掉。
四、threading模組
thread模組提供了基本的執行緒和鎖的支援,threading提供了更高階別、功能更強的執行緒管理的功能。
thread模組不支援守護執行緒,當主線程式退出時,所有的子執行緒不論他們是否還在工作,都會被強行退出。而threading模組支援守護執行緒。
執行緒的建立Threading. Thread類
multiprocessing模組完全模仿了threading模組的介面,二者在使用層面,有很大的相似性。
current_thread()獲取當前所在的執行緒物件,current_thread(). ident 通過 ident 可以獲取執行緒id
執行緒是不能從外部terminale
所有的子執行緒只能是自己執行完程式碼之後關閉
enumerate 列表,儲存了所有活著的執行緒物件,包括主執行緒
active_count 數字,儲存了所有活著的執行緒個數
from threading import Thread, current_thread, active_count, enumerate import time def func(i): time.sleep(1) print(f'這是執行緒func{i},執行緒id={current_thread().ident}') t_list = [] for i in range(5): t = Thread(target=func, args=(i,)) t.start() t_list.append(t) print(enumerate()) print(f'活著的執行緒個數{active_count()}') for th in t_list: th.join() print('所有的執行緒都執行完了!') # 輸出 [<_MainThread(MainThread, started 8600)>, <Thread(Thread-1, started 18832)>,...] 活著的執行緒個數6 這是執行緒func3,執行緒id=3148 這是執行緒func1,執行緒id=2296 這是執行緒func0,執行緒id=18832 這是執行緒func2,執行緒id=18644 這是執行緒func4,執行緒id=12072 所有的執行緒都執行完了!
使用物件導向建立執行緒
from threading import Thread, current_thread class MyThread(Thread): def __init__(self,i): self.i = i super().__init__() def run(self): print(f'這是執行緒{self.i},執行緒號={current_thread().ident}') t = MyThread(1) t.start() # 開啟執行緒,線上程中執行run方法。 # 輸出 這是執行緒1,執行緒號=10160
執行緒之間的資料共享
修改成功說明程式的資料是共享給執行緒的。
from threading import Thread count = 100 def func(): global count count -= 1 t_list = [] for i in range(100): t = Thread(target=func) t.start() t_list.append(t) for th in t_list: th.join() print(f'所有的執行緒執行完了,count={count}') # 輸出 所有的執行緒執行完了,count=0