Linux第十三講:線程同步和互斥
- 1.線程互斥
- 1.1進程線程間的互斥背景概念
- 1.2什么是鎖
- 1.2.1認識鎖,理解鎖
- 2.線程同步
- 2.1條件變量
- 2.2生產和消費模型
- 2.3基于阻塞隊列(blockqueue)的生產消費模型
- 2.3.1單生產,單消費的阻塞隊列模擬實現
- 2.3.2多生產,多消費的阻塞隊列模擬實現
- 2.4生產消費模型好處補充
- 3.POSIX信號量
- 3.1什么是POSIX信號量
- 3.2基于喚醒隊列的生產消費模型概念
- 3.3信號量接口的認識和信號量的封裝
- 3.4基于環形隊列的生產消費模型的模擬實現
- 3.4.1單生產,單消費
- 3.4.2多生產,多消費
- 4.日志與線程池
- 4.1日志
- 4.1.1日志的基本概念
- 4.1.2日志的實現
- 4.2線程池
- 4.2.1線程池的實現
- 4.2.1.1普通版本的線程池實現
- 4.2.1.2單例模式 && 餓漢 && 懶漢方式
- 4.2.1.3單例式線程池的實現
- 5.線程安全與重入問題
- 6.死鎖問題
- 6.1什么是死鎖
- 6.2死鎖的必要條件
- 6.3避免死鎖
- 7.STL、智能指針和線程安全問題
1.線程互斥
1.1進程線程間的互斥背景概念
1.2什么是鎖
當多個線程并發操作共享變量時,容易帶來一些問題:
為什么會引發這種問題呢?:
1.2.1認識鎖,理解鎖
通過上面的問題,我們引入鎖的概念:
鎖的使用如下:
感性理解鎖:
鎖的原理:
C++也提供了鎖的接口,可以自行查看
2.線程同步
同步:在保證數據安全的前提下,讓線程能夠按照某種特定的順序訪問臨界資源,從而有效避免饑餓問題,叫做同步
我們先感性理解線程同步:
2.1條件變量
感性理解條件變量:
了解了條件變量,那么條件變量該如何使用呢?:
我們通過代碼來展示條件變量實現的線程同步操作:
2.2生產和消費模型
2.3基于阻塞隊列(blockqueue)的生產消費模型
什么是阻塞隊列:
2.3.1單生產,單消費的阻塞隊列模擬實現
完整代碼實現,上面沒有debug,下面的debug了:
2.3.2多生產,多消費的阻塞隊列模擬實現
多生產,多消費的代碼和單生產,單消費的相同,因為無論是多少生產者或消費者,都只有一個線程能夠進入臨界區域!
2.4生產消費模型好處補充
3.POSIX信號量
和System V類似,POSIX只是一種標準。
3.1什么是POSIX信號量
3.2基于喚醒隊列的生產消費模型概念
3.3信號量接口的認識和信號量的封裝
3.4基于環形隊列的生產消費模型的模擬實現
3.4.1單生產,單消費
3.4.2多生產,多消費
多生產,多消費的模擬實現就和單生產,單消費的模擬實現有所不同。
因為單生產消費只需要維護生產者和消費者之間的互斥和同步關系,使用信號量完全可以實現。
而多生產消費還需要維護生產者之間的互斥關系,以及消費者之間的互斥關系,所以這里需要兩把鎖來維護:
進一步理解信號量:
1.信號量把對臨界資源是否存在,是否就緒,以原子性的形式,在需要訪問臨界資源之前就做出判斷了
2.如果資源可以拆分,比如數組,那么可以考慮使用sem
如果資源不可拆分,比如阻塞隊列,就使用mutex
4.日志與線程池
什么是線程池?:
然而線程池每天的運行結果我們想要實時查看,就需要用到日志記錄
4.1日志
4.1.1日志的基本概念
那么這樣的日志該如何實現呢?:
4.1.2日志的實現
完整代碼如下:
4.2線程池
4.2.1線程池的實現
4.2.1.1普通版本的線程池實現
全部代碼如下:
4.2.1.2單例模式 && 餓漢 && 懶漢方式
餓漢方式和懶漢方式的不同:
我們將使用懶漢方式實現線程池,而懶漢方式被使用的次數更多。
因為我們并不能確定什么時候需要使用到類對象,如果類對象很大,提前被創建,那么在不使用的這段時間內,類對象占用的空間也不能被其它對象使用,會造成資源的浪費。
操作系統也有很多懶漢方式的應用:
當我們malloc時,并不會直接申請物理內存,而是先申請虛擬內存。當我們需要使用物理內存時,才通過缺頁中斷的方式,申請物理內存。
4.2.1.3單例式線程池的實現
那么我們如何保證只創建一個對象呢?:
5.線程安全與重入問題
6.死鎖問題
6.1什么是死鎖
6.2死鎖的必要條件
知道什么是死鎖,那么就需要解決死鎖。只有知道了死鎖的生成條件,那么破壞其中任意一個條件,就會讓死鎖問題解決:
6.3避免死鎖
知道了死鎖的必要條件,破壞任意一個條件就可以避免死鎖:
7.STL、智能指針和線程安全問題
1.STL中的容器不是線程安全的,因為STL的設計初衷就是將性能挖掘到極致,如果加鎖就必然會破壞性能問題。而且對于不同的容器,加鎖的方式也會不同
2.智能指針是否是線程安全的?:1/對于unique_ptr,由于只是在當前代碼塊范圍內生效,所以不涉及線程安全問題 2/對于shared_ptr,使用了引用計數,但是標準庫實現時考慮了這個問題,將引用計數設置為原子操作
3.也有很多其他的鎖:悲觀鎖和樂觀鎖(了解即可):