多執行緒編寫基礎

2021-05-27 05:25:12 字數 3069 閱讀 4749

注意事項:

1. volatile 關鍵字並非每種編譯器都支援, 要寫通用的**只能夠採用c++標準的關鍵字.

2. 任何被執行緒讀取的值, 即使有同步, 也只有以指標形式訪問其內容才能夠獲取最新值, 或者是以非inline的函式呼叫才能夠獲取最新值, inline會導致編譯器有可能優化成不是每次訪問變數位址的資料, 而是直接從上次一在暫存器裡面值.

3. 執行緒處理過程當中, 每一步執行之前, 必須得要假設另外的執行緒會把處理**都跑一遍甚至跑好幾遍, 以此來排除非同步的錯誤, 否則將會出現執行一段時期, 隨機出現各種不同的錯誤.

4. 多執行緒編寫, 均必須以全速來測試, 所以不能用printf等的排錯, 用上printf, 就有可能把潛在的錯誤給掩蓋起來, 導致問題不能被發現.

5. 多執行緒編寫, 必須以資源訪問劃分來編寫模型, 並對模型進行測試, 按業務模型來直接寫, 將會導致整個程式不穩定, 這是非常危險的, 因為這樣的bug幾乎不可能修復.

6. 採用臨界區並非百分百安全, 執行緒安全都是必須用精確的邏輯來進行推斷, 不能估算大概時間, 也不能用自認為的流程來推斷, 必須採用電腦的執行邏輯來推斷, 即: 除非被鎖或沒訊號, 否則就能夠執行n遍的邏輯, 更不能夠用sleep....

7. 資源劃分, 是多執行緒編寫的重點, 即同乙個資源會被多少個執行緒同時訪問, 哪種訪問方式需要同步, 哪種不需要同步, 必須清楚.

8. 不要盲目使用多執行緒, 不能夠並行的情況, 誤用多執行緒只會帶來緩慢. 

9. 存在多個執行緒大併發對某個資源進行讀和寫的操作, 絕大部分會是資源劃分上的錯誤. 採用單執行緒的方式, 效率不會比多個執行緒低下.

高效誤區和方案:

1. 誤區: 存在大併發的寫,  而資源是採用臨界區來進行保護, 這種方式只會導致多個執行緒被擠壓成單執行緒執行, 跟自動扶梯原理一樣, 乙個梯級只能夠進入乙個人, 再多人一起擁進去, 速度還只會是一次從出口出乙個. 

__方案: 採用各自寫入, 在取出的時候再歸總才能達到實際的寫併發.  寫之間的競爭縮減成寫和讀一對一的競爭, 而只有在讀到某乙個執行緒的寫入時, 才會跟這個寫競爭, 其他寫並不影響. (假如唯讀, 則完全不存在競爭關係), 並且不論是全域性的寫讀順序還是單一寫的順序均可以保證.

2. 誤區: 存在大併發的讀, 而資源在讀取的時候進行刪除, 這種方式也同樣會導致上述的情況, 因為刪除就等於包括了寫.

3. 誤區: 讀寫不頻繁時, 認為採用臨界區就會百分百安全. 原子鍊錶是必須根據實際情況, 把重要的寫入, 讀取和刪除容入業務邏輯才會正確的. 假如乙份資料需要寫10次才完成, 乙個執行緒寫到一半的時候, 另乙個執行緒發現錯誤而執行了清空操作, 那麼另乙個寫執行緒並不可能得知情況, 而讀執行緒由於沒有立即得到通知而讀取了髒資料.

__方案: 原子鍊錶可以由多執行緒讀寫, 但只能由主控制線程進行清空. 正確的錯誤停止方式是主控制線程設定錯誤, 然後等待其他執行緒停止去原子鍊錶的訪問後, 才進行刪除資料.

4. 誤區: 認為臨界區安全, 效率非常高. 應該多使用. 沒錯, 臨界區的api呼叫很快, 能夠有效保護**段. 但就算**段粒度再少, 只要有臨界區的模式, 就會容易變成瓶頸. 因為臨界區的實質是把並行轉變成序列來實現同步的. 並且假如臨界區當中, 還存在任何的wait來等待訊號量的話, 就極有可能死鎖.

__方案: 必須深刻理解好各種同步物件適用的場合. 臨界區, 只適用於並行轉換成序列化的場合, 其他場合是不適用的. 多執行緒存在幾種關係: 搶占(即先到先得), 流水(即下一線程由上一線程喚醒做事), 並行(即每個執行緒互不影響), 搶占的關係下, 採用了臨界區, 會導致序列化, 效率嚴重影響. 雖然有tryenter模式, 但想想工作執行緒在訪問資源的時候, 是的確想去得到資源進行處理, 而不是得不到又去處理其他事, 再回過來頭詢問能不能得到, 這樣有可能導致其中某些執行緒一直得不到資源進行處理, 而白白浪費cpu資源. 在流水關係下, 採用了臨界區, 不但得不到想要的效果, 還會是錯誤的. 因為臨界區是不能夠保證進入順序的. 並行關係下, 需要臨界區保護的, 就是並行入口和出口, 即序列獲取資源後, 序列輸出. 這樣會導致乙個問題, 就是對資源的處理時快時慢. 並行速度越高, 這個時快時慢的效果越明顯, 臨界區是不能夠保證進入的次序的, 即有可能第乙個執行緒獲取第乙個資源, 由於輸出時一直不能enter, 而是由其他執行緒獲取, 這樣變成第乙個進入的資源反而變成處理速度最慢的. 這些採用臨界區的結果絕大部分都不是編寫時想要的結果, 但採用臨界區帶來這種結果是必然的. 雖然不是不安全, 但得不到需要的效果, 還不如單執行緒來做. 而搶占, 流水, 並行這些模式如何能夠正確, 比較公平的方式來進行處理, 上述也有解決方法. 搶占, 採用interlock的模式, interlock的彙編**, 其實只有乙個xadd. 流水, 採用的是event, mutex等訊號模式(較成熟的流水模型網上有的), 並行, 可採用主線程分配資源的方式進行, 這樣可降低併發讀的衝突, 也可採用interlock的模式完全避開併發讀, 輸出時, 採用上述的1方式即可.

多執行緒debug:

1. 有bug是很麻煩的, 因為很難查出原因, 特別是按業務邏輯而不是功能邏輯來寫的多執行緒的時候, 更加會查死人. 按業務邏輯的方式來寫多執行緒, 可以說是設計上的錯誤, 不論業務還是功能, 其抽象出來的結果都是輸入, 處理, 輸出這三種, 而業務, 總是由功能組合而成, 從業務角度來寫多執行緒, 程式不可能健壯. 而按功能寫的時候, 檢測bug假如為記憶體出錯, 那肯定是寫的人功底不足, 重查所有**是必須的. 假如是邏輯錯誤, 只能夠採用上述的一條: 某個執行緒在走下一步之前, 其他執行緒按**的邏輯在腦袋裡面來跑上幾遍, 看看執行到**會造成不同步導致錯誤即較為容易找到缺口. 千萬別寫log, printf等操作進行debug, 這就會導致當前**的時間邏輯改變, 導致以當前**的處理上的bug被掩蓋. 多執行緒有bug, 只能夠以當前的**來進行邏輯分析是必須的.

2. 達不到預算效果, 多執行緒寫的時候, 達不到預算效果, 可以說就是心痛了. 辛苦弄出來的功能, 卻發現連單執行緒都不如. 這個時候必須分析所有同步機制, 檢視是否會出現某些執行緒是浪費cpu資源, 某些執行緒是一直等待, 甚至某些執行緒根本不工作等等, 按上述的幾種基本辦法, 大體上可以解決較多效率問題的了. 多執行緒的效率, 只能從序列分解成並行來解決, 而不是追求安全能解決的, 並行不了的地方應該要用單執行緒, 這樣才會高效率, 不須序列的地方就採用並行, 多執行緒的效率都是來自於此. 同步機制不需要懂得太多, 善用原子操作和訊號量已經足夠應付很多場合了.

以上為個人經驗總結, 保留以備忘記.

C 中編寫多執行緒

隨便看看!net將關於多執行緒的功能定義在system.threading名字空間中。因此,要使用多執行緒,必須先宣告引用此名字空間 using system.threading 即使你沒有編寫多執行緒應用程式的經驗,也可能聽說過 啟動執行緒 殺死執行緒 這些詞,其實除了這兩個外,涉及多執行緒方面的...

C 中編寫多執行緒

在c 中早都聽說這個東西了,但是以前一直沒有使用過,現在第一次嘗試,來衝衝電。net將關於多執行緒的功能定義在system.threading名字空間中。因此,要使用多執行緒,必須先宣告引用此名字空間 using system.threading 即使你沒有編寫多執行緒應用程式的經驗,也可能聽說過 ...

多執行緒基礎

對於多執行緒程式設計,很多人概念不清,寫 的時候要麼是處處加鎖,影響效能不說,還容易莫名其妙的死鎖,還有人對多執行緒敬而遠之。所以學習多執行緒程式設計最重要的不是學習 api,而是理解什麼才是多執行緒安全的 從例子說起 include include long global1 0 volatile ...