1. 程式人生 > >併發程式設計理論回顧20點

併發程式設計理論回顧20點

1、多道技術
1.空間複用
同一時間在記憶體中存放多個程式 記憶體相互隔離
2.時間複用
CPU在遇到IO時切換到另一個程式可以實現併發切換+儲存狀態

2、程序理論
程序是一個資源單位 包含了該程式執行所需的所有資源
為什麼使用它? 為了提高程式的執行效率 當遇到IO阻塞時 或需要同時執行多個任務時

linux 和 windows建立程序的區別
linux下建立程序 會直接將父程序的所以資料複製一份給子程序
windows建立程序時 子程序會載入父程序的位元組碼(沒有任何的多餘符號)

3、程序的使用
1.例項化Process這個類 傳入一個target引數
2.繼承Process 實現run函式

4、IPC程序間通訊

為什麼使用IPC? 程序間的記憶體時物理隔離的 無法直接訪問資料
實現方式
1.共享檔案 速度慢 沒有鎖
2.管道 單向通訊 需要有父子關係 沒有鎖
3.共享記憶體 速度快 資料量較小 Manager沒有鎖 Queue有鎖(先進先出)

5、守護程序

A程序守護B程序 B 程序結束 A也結束 皇帝死了 妃子陪葬


6、互斥鎖 死鎖 可重入鎖 訊號量 (搶票程式碼 死鎖程式碼)

1.問題? 多個程序同時讀寫同一份資料時 可能造成資料混亂 (本地IO速度很快 基本不會出現問題)

讀資料沒有必要加鎖寫資料必須加

鎖的特點:併發改序列被鎖的程式碼將會變成序列 效率降低

鎖的原理:就是加上一堆判斷 鎖相當於一個標記

互斥鎖 相互排斥 就像在宿舍大家共用一個廁所 一個使用中 其他人不能用同一執行緒不能多次acquire 會卡死。

2.死鎖?
死鎖造成的問題.程式卡死一個鎖不會產生死鎖
當有多個鎖多個執行緒時會產生死鎖
a b 鎖
p k 執行緒
當p 和 k 都需要a和b鎖時才可能產生死鎖

3.可重入鎖RLock
同一個執行緒可以多次執行acquire 執行一次acquire 計數加1
執行一次release 次數減一 執行acquire的次數需要與release的次數對應
在執行被鎖的程式碼時 同一個執行緒 不會判斷次數 其他執行緒需要判斷 計數為0才可以執行

不是用來解決死鎖的

4.訊號量
案列:
sem = semaphore(2)
acquire
code.....
release
開了十個執行緒 只能有兩個同時執行

訊號量作用:限制同時執行被鎖程式碼的執行緒數量

7、生產者消費者模型
while True:
生產資料 1
處理資料 10
生產資料 和 處理數的能力不匹配 一個快一個慢 整體下變低

學習多程序 可以將生產和處理 分到不同程序中 來解決能力不匹配的問題 快的多幹點 慢的少乾點
第二個問題 作為處理資料的一方不知到什麼時候會有資料 兩個進度不同 使用一個共享的資料容器

將生產和消費分到不同程序(執行緒)中 使用共享資料容器來同步資料 詳見思聰吃熱狗的案例


8、執行緒理論
執行緒是CPU的基本執行單位一連串的程式碼就是像流水線cpu會按照順序依次執行他們

為什麼用? 需要實現併發執行任務 併發任務程序也可以完成

執行緒和程序的區別
1.資源開銷
程序開銷大 執行緒開銷小
2.資料共享
一個程序內的所有執行緒資料共享
舉例:
一個程序中 必然包含一條主執行緒
一個程序可以包含多個執行緒
程序是工廠 執行緒 是流水線
一個工廠有多個流水線 至少得有一個 所有流水線都可以使用工廠內的資源

9、執行緒的使用 (生產者消費者程式碼 搶票程式碼)
使用執行緒
1.例項化Thread類 引數target傳入任務
2.繼承Thread類 實現run函式

10、守護執行緒
守護執行緒 會在被守護執行緒結束時一併結束
與守護程序的區別
a 守護 b 同時還有另一個執行緒 c
a 會等到 b 和 c都結束才結束
皇后 守護皇帝
皇宮裡還有太子
皇后會等到皇帝和太子都死了 才死
守護執行緒會等待所有非守護執行緒結束後才算結束
main
sub1
sub2
sub3

sub1.deamon = True
sub1會等待 main sub2 sub3 全都結束 才會結束

11、GIL(Global Interpreter Lock)
問題: 一個py程式 要想執行 必須執行直譯器 直譯器的工作時翻譯程式碼 並執行
當一個py程序中 有多個執行緒 執行緒的任務就是執行程式碼 意味者 多個執行緒都要使用直譯器
簡單的說 多執行緒會爭搶直譯器的執行權
如果是自己開的執行緒 多執行緒要訪問相同資料 加鎖就能解決
但是有一些程式碼不需要程式設計師寫的 也確實需要共享使用 就是直譯器

GC:垃圾回收器 負責清理記憶體中的無用資料 清理垃圾也需要執行程式碼 但是GC不應該卡住使用者的程式碼執行
只能開執行緒
GC 看到 x = 10 x = 1 準備刪除10 這時候突然CPU切到使用者執行緒 a = 10 此此時還沒有問題
緊接著 CPU 又切到GC GC上來就刪除10 在切到使用者執行緒 a 所指向的地址被清理了 產生錯誤

解決方案: 給直譯器加上鎖 保證GC執行期間 使用者執行緒不能執行

全域性直譯器鎖帶來的問題
同一時間只有一個執行緒能使用直譯器 無法利用多核CPU
那Cpython 多執行緒是雞肋嗎?
當程式是IO密集時 多執行緒能提高效率 IO的速度 明顯要比CPU執行速度慢
當程式時計算密集時 多執行緒無法提升效率 得使用多程序

12、GIL 和 自定義鎖
相同點:都是互斥鎖
不同點:
GIL直譯器級別鎖 鎖的時直譯器程式碼
自定義鎖 鎖的是自己寫的程式碼
自動加鎖只要有執行緒在使用直譯器 自動解鎖 1.IO 2.執行時間過長3ms 3.執行緒執行結束
有了GIL 為什麼還需要自定義鎖?
GIL 不清楚什麼程式碼會造成資料競爭問題 不知道什麼地方該加

13、程序池,執行緒池
池是一種容器程序池裡面裝的是程序
為什麼需要這個容器?
當程式中有多個程序時 管理變得非常麻煩程序池可以幫我們管理程序
1.程序的建立
2.程序的銷燬
3.任務的分配
4.限制最大的程序數 保證系統正常執行
使用方式?
ThreadPoolExecutor 執行緒池
例項化 時指定最大執行緒數
ProcessPoolExecutor 程序池
例項化 時指定最大程序數
執行submit來提交任務

14、佇列 queue

這個queue和程序裡的Queue不同 就是一個簡單的容器
佇列是一種資料的容器
特點:先進先出
queue先進先出
lifoqueue先進先出
priorityqueue 優先順序佇列 整型表示優先順序 數字越大優先順序越低

15、同步非同步 阻塞 非阻塞 概念
同步提交任務需要等待任務執行完成才能繼續執行
非同步提交任務不需要等待任務執行 可以立即繼續執行
指的都是提交任務的方式

阻塞遇到IO 失去了CPU執行權 看上去也是在等 與同步會混淆
非阻塞就緒,執行,程式碼正常執行

阻塞非阻塞指的是執行緒狀態
執行緒的三種狀態 就緒 執行 阻塞

16、非同步回撥
發起了一個非同步任務 任務完成後回來呼叫指定的函式
pool = ThreadPoolExecutor()
f = pool.submit(task)
f.add_done_callback(函式名)

不需要等到任務結束 繼續執行下一行
什麼是非同步回撥 在發起非同步任務後 子執行緒或子程序 完成任務後需要通知任務發起方 如果通知就呼叫一個函式 add_done_callback(函式名)
為什麼需要非同步回撥
由於任務是非同步執行 任務發起方不知道什麼時候完成 所以使用回撥的方式來告訴發起方任務執行結果

17、協程
協程 指的是 單執行緒實現併發
為什麼用協程? 多執行緒實現併發 有什麼問題?
TCP程式中 處理客戶端的連線 需要子執行緒 但是子執行緒依然會阻塞 一旦阻塞 CPU切走 但是無法保證是否切到當前程式
提高效率的解決方案 是想辦法儘可能多的佔用CPU 當程式遇到阻塞時 切換到別的任務 注意使用程式內切換

協程的使用
1.生成器
2.greenlet 封裝了生成器 不能檢測到IO行為
3.gevent 封裝了grennlet 既能夠切換執行 也能檢測IO
# gevent 需要配合monkey補丁 monkey補丁內部將原本阻塞的模組 替換為了非阻塞的
# monkey必須放在匯入(需要檢測IO的模組)模組之前
monkey.patch_all()
gevent核心函式spawn(函式名)
join讓主執行緒等待所有任務執行完成才結束

18、IO模型
網路傳輸的兩個階段
waitdata(recv accept) copydata(send)
一個應用程式的快取 和系統快取
recv accept 先 wait 在copy
send 只有copy
阻塞IO
之前所提到的,除了協程 都是阻塞 IO
應用程式 傳送 系統呼叫 作業系統等待資料(wait) 資料準備好 return data

非阻塞IO
recv send accept 都不會阻塞 會立即執行 但是不能保證立馬就有資料 沒有資料丟擲異常
我們需要手動捕獲異常 捕獲異常後可以處理別的任務  CPU的利用率提高了  但是同時也浪費了CPU 當沒有任何資料處理的時候 就在空轉

多路複用
管理連線的一種方式
為什麼使用它? 相對於非阻塞IO降低無用的系統呼叫
怎麼管?
核心函式select 預設時阻塞的 阻塞到有任意一個連線可以被處理
一 建立連線 和管理連線

1.建立伺服器socket物件
2.將伺服器物件交給select來管理
3.一旦有客戶端發起連線 select將不在阻塞
4.select將返回一個可讀的socket物件列表(第一次只有伺服器)
5.伺服器的可讀代表有連線請求 需要執行accept 返回一個客戶端連線conn 由於是非阻塞 不能立即去recv
6.把客戶端socket物件也交給select來管理 將conn加入兩個被檢測的列表中

7.下一次檢測到可讀的socket 可能是伺服器 也可能客戶端 所以加上判斷 伺服器就accept 客戶端就recv
8.如果檢測到有可寫(可以send就是系統快取可用)的socket物件 則說明可以向客戶端傳送資料了
7 和 8 執行順序不是固定的
處理資料收發
兩個需要捕獲異常的地方
1.recv 執行第7步 表示可以讀 為什麼異常 只有一種可能客戶端斷開連線
還需要加上if not 判斷是否有資料 ;linux下 對方下線不會丟擲異常 會收到空訊息
2.send 執行第8步 表示可以寫 為什麼異常 只有一種可能客戶端斷開連線 

非同步IO
IO包括 網路IO 本地IO
上面的三種描述的都是網路IO
不能本地IO的問題
解決的方案就是:
將同步的IO操作改成非同步的IO操作 在IO期間 可以執行其他的任務
使用asyncio模組


19、socketserver
是什麼? 對伺服器端的socket的封裝
封裝了多執行緒和socket
為什麼用? 簡化程式碼
使用方法:
socketserver (forkingUDP forkingTCP windows無法使用)
核心類 ThreadingUDPServer ThreadingTCPServer
ThreadingTCPServer 例項化時 傳入伺服器地址 和 自定義的一個數據處理類
自定義類需要繼承BaseRequestHandler類中需包含handle函式
物件呼叫serve_forever

20、Event
是什麼?  執行緒間通訊的方式
為什麼用?  簡化程式碼
set()設定為True
wati()阻塞 直到為True