Qt中文文檔-同步線程
本篇文章來自項(xiàng)目?
QtDocumentCN.
https://github.com/QtDocumentCN/QtDocumentCN
文章協(xié)議
https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh
同步線程
盡管線程的初衷是讓代碼并行運(yùn)行,仍然有許多時(shí)候,線程必須停下來等待其他線程。例如,若有兩個(gè)線程嘗試同時(shí)寫入同一個(gè)變量,則結(jié)果是不確定的。讓線程強(qiáng)制等待另外線程,這樣的機(jī)制被稱為互斥,是一種保護(hù)共享資源(如數(shù)據(jù))的常用技術(shù)。
Qt 在為線程同步提供高級機(jī)制的同時(shí),也提供了低級原語。
低級同步原語
QMutex 是能夠進(jìn)行強(qiáng)制互斥的基礎(chǔ)類。為了獲取對一個(gè)共享資源的訪問權(quán),線程會鎖住一個(gè)互斥鎖。若此時(shí),另一個(gè)線程嘗試再次加鎖,則導(dǎo)致后者進(jìn)入睡眠狀態(tài),直到第一個(gè)線程完成任務(wù)并解開互斥鎖。
QReadWriteLock 類似 QMutex ,只是前者區(qū)分了“讀”與“寫”的訪問權(quán)限。當(dāng)一段數(shù)據(jù)未處于寫入過程中時(shí),多個(gè)線程可以安全地同時(shí)讀取它。QMutex 能強(qiáng)制多個(gè)線程輪流訪問共享數(shù)據(jù),而 QReadWriteLock 允許同時(shí)讀取,提高了并行性。
QSemaphore 是 QMutex 的泛化,它保護(hù)一定數(shù)量的同類資源。QMutex 則與之相反,僅保護(hù)一個(gè)資源。信號量示例 是 QSemaphore 的一個(gè)使用典例:同步訪問在生產(chǎn)者和使用者之間的循環(huán)緩沖區(qū)。
QWaitCondition 不同于強(qiáng)制互斥,它提供一個(gè) 條件變量 來同步線程。不同于其他原語令線程等待至資源解鎖,QWaitCondition 可以讓線程在符合特定條件時(shí)退出等待。為了讓等待中的線程繼續(xù)執(zhí)行,調(diào)用 wakeOne() 來隨機(jī)喚醒一個(gè)線程,或者調(diào)用 wakeAll() 來同時(shí)喚醒所有線程。等待條件示例 將告訴您如何使用 QWaitCondition 取代 QSemaphore 來解決生產(chǎn)者-消費(fèi)者問題。
注意:Qt 的同步類依賴于使用正確對齊的指針。例如,您不能在 MSVC 中使用打包類。(譯者注:即使用 #pragma pack 或 alignas 等方式非常規(guī)對齊地存儲同步類對象)
這些線程同步類能讓一個(gè)方法做到線程安全。不過,這會導(dǎo)致性能損失,因此大多數(shù) Qt 的方法都不保障線程安全。
風(fēng)險(xiǎn)
如果一個(gè)線程鎖定了一個(gè)資源,卻在沒有使用完畢后解鎖它,則可能會導(dǎo)致程序凍結(jié),因?yàn)槠渌€程將永遠(yuǎn)無法訪問該資源。這很容易出現(xiàn)在,例如,有異常拋出,并強(qiáng)制當(dāng)前函數(shù)返回,而沒有釋放鎖的場景中。(譯者注:異常拋出時(shí),當(dāng)前函數(shù)會被強(qiáng)制就地返回,本地棧變量會被釋放。因此在引發(fā)異常的代碼之后的 unlock() 不會被執(zhí)行)
另一個(gè)類似的場景是 死鎖 。例如,假設(shè)線程 A 正在等待線程 B 解鎖一個(gè)資源。若此時(shí)線程 B 也在等待線程 A 解鎖另一個(gè)資源,那么兩個(gè)線程持續(xù)相互等待,這將造成程序凍結(jié)。
提供便利的類
QMutexLocker、QReadLocker 與 QWriteLocker 使得 QMutex 與 QReadWriteLock 使用起來更加簡單。這些類會在它們構(gòu)造時(shí)鎖定資源,在析構(gòu)時(shí)自動解鎖資源。設(shè)計(jì)它們的初衷是簡化使用 QMutex 與 QReadWriteLock 的代碼,降低資源被意外永久鎖定的可能性。(譯者注:此方式可以保證線程安全,即在拋出異常,導(dǎo)致函數(shù)強(qiáng)制返回時(shí),鎖會因?yàn)楸镜氐?locker 棧對象析構(gòu)而被自動解鎖)
高級事件隊(duì)列
Qt 的 事件系統(tǒng) 在跨線程通信中非常有用。每個(gè)線程都有自己的事件循環(huán)。要令槽函數(shù)(或任何可動態(tài)調(diào)用的方法)在另一個(gè)線程中被調(diào)用,可將該調(diào)用目標(biāo)置入目標(biāo)線程的事件循環(huán)中。這可讓目標(biāo)線程完成當(dāng)前任務(wù)后再執(zhí)行槽函數(shù),而原始線程繼續(xù)并行運(yùn)行。
若要將調(diào)用代碼置于事件循環(huán)中,可創(chuàng)建一個(gè)隊(duì)列 信號槽 連接。每當(dāng)發(fā)出信號時(shí),事件系統(tǒng)將記錄它的參數(shù)。該信號接收者 所屬 的線程將會執(zhí)行對應(yīng)的槽函數(shù)。此外,也可以使用 QMetaObject::invokeMethod() 以達(dá)到同樣效果,同時(shí)無需發(fā)射信號。在這兩種情況下,都必須使用 隊(duì)列連接,因?yàn)?直接連接 會繞過事件系統(tǒng),在當(dāng)前線程中立刻執(zhí)行該方法。
不同于使用低級原語,使用事件系統(tǒng)進(jìn)行線程同步?jīng)]有死鎖的風(fēng)險(xiǎn)。不過,事件系統(tǒng)不會強(qiáng)制執(zhí)行互斥。如果可動態(tài)調(diào)用的方法訪問共享數(shù)據(jù),則依然需要使用低級原語對其進(jìn)行保護(hù)。
即便如此,Qt 的事件系統(tǒng)以及 隱式共享 的數(shù)據(jù)結(jié)構(gòu)依然提供了傳統(tǒng)線程鎖定的替代方案。如果僅使用信號槽,而且線程間無共享變量,多線程程序完全可以不使用低級原語。
小程序

參與項(xiàng)目
歡迎各位參與我們的項(xiàng)目
參與方式見下方鏈接
------------------------
長按下方二維碼關(guān)注
