中山網(wǎng)站建設(shè)技術(shù)如何做seo優(yōu)化
?
?13.?常見(jiàn)鎖概念
(一)了解死鎖
死鎖是指在一組進(jìn)程中的各個(gè)進(jìn)程均占有不會(huì)釋放的資源,但因互相申請(qǐng)被其他進(jìn)程占有的,且不釋放的資源,而處于的一種永久等待狀態(tài)
(二)死鎖四個(gè)必要條件
- 互斥條件:一個(gè)資源每次只能被一個(gè)執(zhí)行流使用
- 請(qǐng)求與保持條件:一個(gè)執(zhí)行流因請(qǐng)求資源而阻塞時(shí),對(duì)已獲得的資源保持不放
- 不剝奪條件:一個(gè)執(zhí)行流已獲得的資源,在末使用完之前,不能強(qiáng)行剝奪
- 循環(huán)等待條件:若干執(zhí)行流之間形成一種頭尾相接的循環(huán)等待資源的關(guān)系
(二)避免死鎖
- 破壞死鎖的四個(gè)必要條件
- 加鎖順序一致 (不是交錯(cuò)申請(qǐng)不同的鎖資源)
- 避免鎖未釋放的場(chǎng)景
- 資源一次性分配
14. linux線(xiàn)程同步
(一)條件變量
當(dāng)一個(gè)線(xiàn)程互斥地訪(fǎng)問(wèn)某個(gè)變量時(shí),它可能發(fā)現(xiàn)在其它線(xiàn)程改變狀態(tài)之前,它什么也做不了,為了使搶奪鎖資源更公正(防止饑餓問(wèn)題),就需要把所有等待鎖資源的線(xiàn)程放入一個(gè)等待隊(duì)列,直到線(xiàn)程被喚醒
。這種情況就需要用到條件變量
注意:
條件變量必須依賴(lài)鎖
(二)同步概念
同步:在保證數(shù)據(jù)安全的前提下,讓線(xiàn)程能夠按照某種特定的順序訪(fǎng)問(wèn)臨界資源,從而有效避免饑餓問(wèn)題,叫做同步
(三)條件變量相關(guān)函數(shù)
條件變量函數(shù) 初始化
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict
attr);
參數(shù):
cond:要初始化的條件變量
attr:條件變量的屬性,一般設(shè)置成NULL
銷(xiāo)毀
int pthread_cond_destroy(pthread_cond_t *cond)
參數(shù):要銷(xiāo)毀的條件變量函數(shù)
等待條件滿(mǎn)足
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
參數(shù):
cond:要在這個(gè)條件變量上等待
mutex:互斥量
注意:
參數(shù)中有互斥量,是因?yàn)閷?shí)現(xiàn)同步與互斥,需要先加鎖,再判斷(判斷也是訪(fǎng)問(wèn)臨界資源,和加鎖保護(hù)線(xiàn)程安全對(duì)應(yīng))是否進(jìn)入等待隊(duì)列休眠,而這個(gè)函數(shù)里面有互斥量是為了把鎖資源釋放,接下來(lái)訪(fǎng)問(wèn)臨界資源的順序是按照隊(duì)列里面的順序進(jìn)行的(前提是線(xiàn)程被喚醒)
大概代碼思路
pthread_mutex_lock(&mutex); // 加鎖
pthread_cond_wait(&cond); //放入等待隊(duì)列
pthread_mutex_unlock(&mutex); //解鎖
注意:
- 喚醒可以由其它線(xiàn)程去喚醒,如主線(xiàn)程
- 三個(gè)函數(shù)的順序不要更換(解鎖和等待都不是原子操作)
喚醒等待
int pthread_cond_broadcast(pthread_cond_t *cond);
喚醒隊(duì)列中的所有線(xiàn)程
int pthread_cond_signal(pthread_cond_t *cond);
按順序喚醒隊(duì)列中的一個(gè)線(xiàn)程
注意:
- 喚醒之后的線(xiàn)程需要申請(qǐng)鎖資源
- 喚醒的時(shí)候,可能出現(xiàn)喚醒錯(cuò)誤的情況。比如在生產(chǎn)者消費(fèi)者模型中(多生產(chǎn)者多消費(fèi)者容易出現(xiàn)這種錯(cuò)誤),喚醒了多個(gè)生成者,并且消費(fèi)者此時(shí)也是屬于喚醒狀態(tài),導(dǎo)致生產(chǎn)者和生產(chǎn)者之間,消費(fèi)者和生產(chǎn)者之間競(jìng)爭(zhēng)同一把鎖,鎖最后的分配,可能導(dǎo)致臨界資源訪(fǎng)問(wèn)條件不滿(mǎn)足(喚醒之后拿到鎖資源的線(xiàn)程,有可能這個(gè)時(shí)候的訪(fǎng)問(wèn)條件不滿(mǎn)足,但依然會(huì)執(zhí)行后面的代碼),出現(xiàn)錯(cuò)誤
正確代碼思路
pthread_mutex_lock(&mutex); // 加鎖
while(... ...) //判斷訪(fǎng)問(wèn)資源的條件是否成立
{
pthread_cond_wait(&cond); //放入等待隊(duì)列
}
pthread_mutex_unlock(&mutex); //解鎖
注意:
判斷用while,是為了保證,線(xiàn)程被喚醒(出了等待隊(duì)列),并且競(jìng)爭(zhēng)到了鎖資源后,再一次判斷資源條件是否成立,才能決定后面的代碼能否執(zhí)行
15. 生產(chǎn)者消費(fèi)者模型
(一)使用生產(chǎn)者消費(fèi)者模型的原因
生產(chǎn)者消費(fèi)者模式就是通過(guò)一個(gè)容器來(lái)解決生產(chǎn)者和消費(fèi)者的強(qiáng)耦合問(wèn)題。生產(chǎn)者和消費(fèi)者彼此之間不直接通訊,而通過(guò)阻塞隊(duì)列來(lái)進(jìn)行通訊
生產(chǎn)者生產(chǎn)完數(shù)據(jù)之后不用等待消費(fèi)者處理,直接扔給阻塞隊(duì)列;消費(fèi)者不找生產(chǎn)者要數(shù)據(jù),而是直接從阻塞隊(duì)列里取
阻塞隊(duì)列就相當(dāng)于一個(gè)緩沖區(qū),平衡了生產(chǎn)者和消費(fèi)者的處理能力,這個(gè)阻塞隊(duì)列就是用來(lái)給生產(chǎn)者和消費(fèi)者解耦的
(二)生產(chǎn)者消費(fèi)者模型優(yōu)點(diǎn)
- 解耦
- 支持并發(fā)
- 支持忙閑不均
(三)基于BlockingQueue的生產(chǎn)者消費(fèi)者模型
BlockingQueue
在多線(xiàn)程編程中阻塞隊(duì)列(Blocking Queue)是一種常用于實(shí)現(xiàn)生產(chǎn)者和消費(fèi)者模型的數(shù)據(jù)結(jié)構(gòu)。其與普通的隊(duì)列區(qū)別在于,當(dāng)隊(duì)列為空時(shí),從隊(duì)列獲取元素的操作將會(huì)被阻塞,直到隊(duì)列中被放入了元素;當(dāng)隊(duì)列滿(mǎn)時(shí),往隊(duì)列里存放元素的操作也會(huì)被阻塞,直到有元素被從隊(duì)列中取出
(四)對(duì)生產(chǎn)者消費(fèi)者模型的總結(jié)
1. 有三種關(guān)系
- 生成者和生成者之間的關(guān)系:互斥
- 生產(chǎn)者和消費(fèi)者之間的關(guān)系:互斥,同步
- 消費(fèi)者和消費(fèi)者之間的關(guān)系:互斥
2. 有兩者角色
生產(chǎn)者和消費(fèi)者
3. 有一種交易場(chǎng)所
特定結(jié)構(gòu)的內(nèi)存空間
16. POSIX信號(hào)量
POSIX信號(hào)量和 SystemV信號(hào)量作用相同,都是用于同步操作,達(dá)到無(wú)沖突的訪(fǎng)問(wèn)共享資源目的。 但POSIX可以用于線(xiàn)程間同步
17. 基于環(huán)形隊(duì)列的生產(chǎn)消費(fèi)模型
(一)了解基于環(huán)形隊(duì)列的生產(chǎn)消費(fèi)模型
環(huán)形隊(duì)列采用數(shù)組模擬,用模運(yùn)算來(lái)模擬環(huán)狀特性
判斷隊(duì)列是否為空或者為滿(mǎn),我們交給信號(hào)量去處理
注意:
- 信號(hào)量的本質(zhì)是一個(gè)計(jì)數(shù)器,用來(lái)描述臨界資源的數(shù)目(這里不把臨界資源當(dāng)成一個(gè)整體,而是很多份,多個(gè)線(xiàn)程并發(fā)訪(fǎng)問(wèn)臨界資源中的不同份)
- 臨界資源是否就緒的判斷是在臨界區(qū)之外的(和前一個(gè)生產(chǎn)消費(fèi)模型區(qū)分開(kāi)來(lái)),在申請(qǐng)信號(hào)量的時(shí)候,就已經(jīng)間接在做判斷了(這一步是預(yù)定操作,即預(yù)定成功,則整個(gè)臨界資源必有一份資源是屬于預(yù)定成功的線(xiàn)程的)
(二)基于環(huán)形隊(duì)列的生產(chǎn)消費(fèi)模型的原理
?
18. 線(xiàn)程池
(一)了解線(xiàn)程池
線(xiàn)程池:
一種線(xiàn)程使用模式
線(xiàn)程過(guò)多會(huì)帶來(lái)調(diào)度開(kāi)銷(xiāo),進(jìn)而影響緩存局部性和整體性能。而線(xiàn)程池維護(hù)著多個(gè)線(xiàn)程(提前開(kāi)辟好的),等待著監(jiān)督管理者分配可并發(fā)執(zhí)行的任務(wù)。這避免了在處理短時(shí)間任務(wù)時(shí)創(chuàng)建與銷(xiāo)毀線(xiàn)程的代價(jià)。線(xiàn)程池不僅能夠保證內(nèi)核的充分利用,還能防止過(guò)分調(diào)度
池化計(jì)數(shù):
線(xiàn)程會(huì)用到池化技術(shù)(以空間換時(shí)間),大致操作(C++為例):現(xiàn)在C++類(lèi)內(nèi)創(chuàng)建線(xiàn)程(創(chuàng)建的線(xiàn)程當(dāng)作類(lèi)內(nèi)成員變量),再利用原生線(xiàn)程去實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模型
注意:
- 在類(lèi)內(nèi)創(chuàng)建時(shí),若線(xiàn)程執(zhí)行的函數(shù)也是類(lèi)內(nèi)函數(shù),會(huì)出現(xiàn)錯(cuò)誤(因?yàn)閳?zhí)行的函數(shù)傳參只有一個(gè)void*,而類(lèi)內(nèi)函數(shù)會(huì)多一個(gè)this指針),所以該執(zhí)行函數(shù)必須是靜態(tài)的
- 若執(zhí)行函數(shù)是靜態(tài)的,同樣會(huì)出現(xiàn)一個(gè)問(wèn)題,即靜態(tài)成員函數(shù)無(wú)法訪(fǎng)問(wèn)非靜態(tài)成員變量(沒(méi)有this指針),所以執(zhí)行函數(shù)的參數(shù)void*必須是this指針,通過(guò)穿過(guò)來(lái)的this指針,訪(fǎng)問(wèn)非靜態(tài)成員變量
(二)線(xiàn)程池的應(yīng)用場(chǎng)景
- 需要大量的線(xiàn)程來(lái)完成任務(wù),且完成任務(wù)的時(shí)間比較短。因?yàn)閱蝹€(gè)任務(wù)小,而任務(wù)數(shù)量巨大, 但對(duì)于長(zhǎng)時(shí)間的任務(wù),線(xiàn)程池的優(yōu)點(diǎn)就不明顯了
- 對(duì)性能要求苛刻的應(yīng)用,比如要求服務(wù)器迅速響應(yīng)客戶(hù)請(qǐng)求
- 接受突發(fā)性的大量請(qǐng)求,但不至于使服務(wù)器因此產(chǎn)生大量線(xiàn)程的應(yīng)用。突發(fā)性大量客戶(hù)請(qǐng)求,在沒(méi)有線(xiàn)程池情況下,將產(chǎn)生大量線(xiàn)程,雖然理論上大部分操作系統(tǒng)線(xiàn)程數(shù)目最大值不是問(wèn)題,短時(shí)間內(nèi)產(chǎn)生大量線(xiàn)程可能使內(nèi)存到達(dá)極限
(三)線(xiàn)程池示例
- 創(chuàng)建固定數(shù)量線(xiàn)程池,循環(huán)從任務(wù)隊(duì)列中獲取任務(wù)對(duì)象
- 獲取到任務(wù)對(duì)象后,執(zhí)行任務(wù)對(duì)象中的任務(wù)接口
19. 線(xiàn)程安全的單例模式
(一)什么是單例模式
單例模式是一種 "經(jīng)典的, 常用的, ??嫉?#34; 設(shè)計(jì)模式(這個(gè)類(lèi)的對(duì)象,只允許創(chuàng)建一個(gè))
(二)什么是設(shè)計(jì)模式
針對(duì)一些經(jīng)典的常見(jiàn)的場(chǎng)景, 給定了一些對(duì)應(yīng)的解決方案, 這個(gè)就是 設(shè)計(jì)模式
(三)單例模式的特點(diǎn)
- 某些類(lèi), 只應(yīng)該具有一個(gè)對(duì)象(實(shí)例), 就稱(chēng)之為單例
- 在很多服務(wù)器開(kāi)發(fā)場(chǎng)景中, 經(jīng)常需要讓服務(wù)器加載很多的數(shù)據(jù) (上百G) 到內(nèi)存中. 此時(shí)往往要用一個(gè)單例的類(lèi)來(lái)管理這些數(shù)據(jù)
(四)餓漢實(shí)現(xiàn)方式和懶漢實(shí)現(xiàn)方式
舉例類(lèi)比(洗完的例子)
餓漢方式:
吃完飯, 立刻洗碗, 這種就是餓漢方式. 因?yàn)橄乱活D吃的時(shí)候可以立刻拿著碗就能吃飯
懶漢方式:
吃完飯, 先把碗放下, 然后下一頓飯用到這個(gè)碗了再洗碗, 就是懶漢方式
注意:
懶漢方式最核心的思想是 "延時(shí)加載". 從而能夠優(yōu)化服務(wù)器的啟動(dòng)速度(在準(zhǔn)備進(jìn)程啟動(dòng)時(shí),需要把一些數(shù)據(jù)加載到內(nèi)存,這一個(gè)過(guò)程的時(shí)間少)
餓漢方式 代碼實(shí)現(xiàn)
template <typename T> class Singleton { static T data; public: static T* GetInstance() { return &data; } };
data 是靜態(tài)類(lèi)內(nèi)成員,會(huì)在進(jìn)程啟動(dòng)之前,早就創(chuàng)建好,而不是等到啟動(dòng)進(jìn)程時(shí),再去創(chuàng)建
?
懶漢模式 代碼實(shí)現(xiàn)
template <typename T> class Singleton { static T* inst; public: static T* GetInstance() { if (inst == nullptr) { inst = new T(); } return inst; } }; template <typename T> T* Singleton<T>:: inst = nullptr;
inst指針是要等待使用的時(shí)候,所指向的內(nèi)容才會(huì)在堆上開(kāi)辟新空間
(五)懶漢方式實(shí)現(xiàn)單例模式(線(xiàn)程安全版本)
代碼
static phread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; template <typename T> class Singleton { static T* inst; // 需要設(shè)置 volatile 關(guān)鍵字, 否則可能被編譯器優(yōu)化. public: static T* GetInstance() { if (inst == NULL) //這一次的判斷可以提高效率,使得沒(méi)有進(jìn)入第二個(gè)判斷的線(xiàn)程,直接執(zhí)行非臨界資源的代碼 { lock.lock(); // 使用互斥鎖, 防止多個(gè)線(xiàn)程進(jìn)入第二個(gè)判斷,保證多線(xiàn)程情況下也只調(diào)用一次 new if (inst == NULL) { inst = new T(); } lock.unlock(); }return inst; } };T* Singleton<T>:: inst = nullptr;
注意:(代碼不是完整的)
lock(),unlock()底層都是用原生線(xiàn)程庫(kù)封裝了
20. STL , 智能指針和線(xiàn)程安全
(一)常見(jiàn)問(wèn)題
- STL中的容器是否是線(xiàn)程安全
不安全
原因:
STL 的設(shè)計(jì)初衷是將性能挖掘到極致, 而一旦涉及到加鎖保證線(xiàn)程安全, 會(huì)對(duì)性能造成巨大的影響.
而且對(duì)于不同的容器, 加鎖方式的不同, 性能可能也不同(例如hash表的鎖表和鎖桶).
因此 STL 默認(rèn)不是線(xiàn)程安全. 如果需要在多線(xiàn)程環(huán)境下使用, 往往需要調(diào)用者自行保證線(xiàn)程安全.
- 智能指針是否是線(xiàn)程安全
- 對(duì)于 unique_ptr, 由于只是在當(dāng)前代碼塊范圍內(nèi)生效, 因此不涉及線(xiàn)程安全問(wèn)題.
- 對(duì)于 shared_ptr, 多個(gè)對(duì)象需要共用一個(gè)引用計(jì)數(shù)變量, 所以會(huì)存在線(xiàn)程安全問(wèn)題. 但是標(biāo)準(zhǔn)庫(kù)實(shí)現(xiàn)的時(shí)候考慮到了這個(gè)問(wèn)題, 基于原子操作(CAS)的方式保證 shared_ptr 能夠高效, 原子的操作引用計(jì)數(shù).
(二)其他常見(jiàn)的各種鎖
- 悲觀鎖:在每次取數(shù)據(jù)時(shí),總是擔(dān)心數(shù)據(jù)會(huì)被其他線(xiàn)程修改,所以會(huì)在取數(shù)據(jù)前先加鎖(讀鎖,寫(xiě)鎖,行鎖等),當(dāng)其他線(xiàn)程想要訪(fǎng)問(wèn)數(shù)據(jù)時(shí),被阻塞掛起
- 樂(lè)觀鎖:每次取數(shù)據(jù)時(shí)候,總是樂(lè)觀的認(rèn)為數(shù)據(jù)不會(huì)被其他線(xiàn)程修改,因此不上鎖。但是在更新數(shù)據(jù)前,會(huì)判斷其他數(shù)據(jù)在更新前有沒(méi)有對(duì)數(shù)據(jù)進(jìn)行修改。主要采用兩種方式:版本號(hào)機(jī)制和CAS操作
- CAS操作:當(dāng)需要更新數(shù)據(jù)時(shí),判斷當(dāng)前內(nèi)存值和之前取得的值是否相等。如果相等則用新值更新。若不等則失敗,失敗則重試,一般是一個(gè)自旋的過(guò)程,即不斷重試。
- 自旋鎖(是否采用自旋鎖,取決于臨界資源訪(fǎng)問(wèn)的時(shí)間的長(zhǎng)短,時(shí)間短,可以采用自旋鎖,即線(xiàn)程不掛起,一直申請(qǐng)所資源)
21. 讀者寫(xiě)者問(wèn)題
讀寫(xiě)鎖
在編寫(xiě)多線(xiàn)程的時(shí)候,有一種情況是十分常見(jiàn)的:有些公共數(shù)據(jù)修改的機(jī)會(huì)比較少,相比較改寫(xiě),它們讀的機(jī)會(huì)反而高的多。
讀寫(xiě)鎖可以專(zhuān)門(mén)處理這種多讀少寫(xiě)的情況
注意:寫(xiě)和寫(xiě)競(jìng)爭(zhēng),寫(xiě)和讀競(jìng)爭(zhēng)且同步,讀和讀共享資源,讀鎖優(yōu)先級(jí)高
相關(guān)函數(shù)
初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t
*restrict attr);
銷(xiāo)毀
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
加鎖和解鎖
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); //寫(xiě)與寫(xiě)競(jìng)爭(zhēng)
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); //讀與寫(xiě)競(jìng)爭(zhēng)
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
代碼(部分)
void * reader(void * arg) //讀端 { char *id = (char *)arg; while (1) { pthread_rwlock_rdlock(&rwlock); if () //判斷 { pthread_rwlock_unlock(&rwlock); break; } pthread_rwlock_unlock(&rwlock); } return nullptr; }void * writer(void * arg) //寫(xiě)端 { char *id = (char *)arg; while (1) { pthread_rwlock_wrlock(&rwlock); if () //判斷條件 { pthread_rwlock_unlock(&rwlock); break; }} return nullptr; }
中間一些過(guò)程省略了
底層實(shí)現(xiàn)(偽代碼)
int reader_count = 0; mutex_t rlock,wlock; 讀端: lock(&rlock) reader_count++; if(reader_count == 1) //第一個(gè)競(jìng)爭(zhēng)到所資源的讀端 {lock(&wlock); //寫(xiě)段不能訪(fǎng)問(wèn) } unlock(&rlock)lock(&rlock); reader_count--; if(reader_count == 0)//一開(kāi)始一批讀端的最后一個(gè) {unlock(&wlock); //此時(shí)讀端可以競(jìng)爭(zhēng) } unlock(&rlock);寫(xiě)端: lock(&wlock); // ... 寫(xiě)入 unlock(&wlock);