[轉(zhuǎn)]深入理解 iOS 開發(fā)中的鎖

轉(zhuǎn)自(https://bestswifter.com/ios-lock/#)

深入理解 iOS 開發(fā)中的鎖

摘要

本文的目的不是介紹 iOS 中各種鎖如何使用,一方面筆者沒有大量的實(shí)戰(zhàn)經(jīng)驗(yàn)昆箕,另一方面這樣的文章相當(dāng)多浴滴,比如 iOS中保證線程安全的幾種方式與性能對(duì)比是掰、iOS 常見知識(shí)點(diǎn)(三):Lock胃珍。本文也不會(huì)詳細(xì)介紹鎖的具體實(shí)現(xiàn)原理甸昏,這會(huì)涉及到太多相關(guān)知識(shí)耐薯,筆者不敢誤人子弟舔清。

本文要做的就是簡(jiǎn)單的分析 iOS 開發(fā)中常見的幾種鎖如何實(shí)現(xiàn),以及優(yōu)缺點(diǎn)是什么曲初,為什么會(huì)有性能上的差距体谒,最終會(huì)簡(jiǎn)單的介紹鎖的底層實(shí)現(xiàn)原理。水平有限臼婆,如果不慎有誤抒痒,歡迎交流指正。同時(shí)建議讀者在閱讀本文以前颁褂,對(duì) OC 中各種鎖的使用方法先有大概的認(rèn)識(shí)故响。

在 ibireme 的 不再安全的 OSSpinLock 一文中,有一張圖片簡(jiǎn)單的比較了各種鎖的加解鎖性能:

image.png

本文會(huì)按照從上至下(速度由快至慢)的順序分析每個(gè)鎖的實(shí)現(xiàn)原理颁独。需要說明的是彩届,加解鎖速度不表示鎖的效率,只表示加解鎖操作在執(zhí)行時(shí)的復(fù)雜程度誓酒,下文會(huì)通過具體的例子來解釋樟蠕。

OSSpinLock

上述文章中已經(jīng)介紹了 OSSpinLock 不再安全,主要原因發(fā)生在低優(yōu)先級(jí)線程拿到鎖時(shí),高優(yōu)先級(jí)線程進(jìn)入忙等(busy-wait)狀態(tài)寨辩,消耗大量 CPU 時(shí)間吓懈,從而導(dǎo)致低優(yōu)先級(jí)線程拿不到 CPU 時(shí)間,也就無法完成任務(wù)并釋放鎖靡狞。這種問題被稱為優(yōu)先級(jí)反轉(zhuǎn)耻警。

為什么忙等會(huì)導(dǎo)致低優(yōu)先級(jí)線程拿不到時(shí)間片?這還得從操作系統(tǒng)的線程調(diào)度說起耍攘。

現(xiàn)代操作系統(tǒng)在管理普通線程時(shí)榕栏,通常采用時(shí)間片輪轉(zhuǎn)算法(Round Robin,簡(jiǎn)稱 RR)蕾各。每個(gè)線程會(huì)被分配一段時(shí)間片(quantum)扒磁,通常在 10-100 毫秒左右。當(dāng)線程用完屬于自己的時(shí)間片以后式曲,就會(huì)被操作系統(tǒng)掛起妨托,放入等待隊(duì)列中,直到下一次被分配時(shí)間片吝羞。

自旋鎖的實(shí)現(xiàn)原理

自旋鎖的目的是為了確保臨界區(qū)只有一個(gè)線程可以訪問兰伤,它的使用可以用下面這段偽代碼來描述:

do {

Acquire Lock

Critical section // 臨界區(qū)

Release Lock

Reminder section // 不需要鎖保護(hù)的代碼

}

在 Acquire Lock 這一步,我們申請(qǐng)加鎖钧排,目的是為了保護(hù)臨界區(qū)(Critical Section) 中的代碼不會(huì)被多個(gè)線程執(zhí)行敦腔。

自旋鎖的實(shí)現(xiàn)思路很簡(jiǎn)單,理論上來說只要定義一個(gè)全局變量恨溜,用來表示鎖的可用情況即可符衔,偽代碼如下:

bool lock = false; // 一開始沒有鎖上,任何線程都可以申請(qǐng)鎖

do {

while(lock); // 如果 lock 為 true 就一直死循環(huán)糟袁,相當(dāng)于申請(qǐng)鎖

lock = true; // 掛上鎖判族,這樣別的線程就無法獲得鎖

Critical section // 臨界區(qū)

lock = false; // 相當(dāng)于釋放鎖,這樣別的線程可以進(jìn)入臨界區(qū)

Reminder section // 不需要鎖保護(hù)的代碼

}

注釋寫得很清楚项戴,就不再逐行分析了形帮。可惜這段代碼存在一個(gè)問題: 如果一開始有多個(gè)線程同時(shí)執(zhí)行 while 循環(huán)周叮,他們都不會(huì)在這里卡住辩撑,而是繼續(xù)執(zhí)行,這樣就無法保證鎖的可靠性了仿耽。解決思路也很簡(jiǎn)單槐臀,只要確保申請(qǐng)鎖的過程是原子操作即可。

原子操作

狹義上的原子操作表示一條不可打斷的操作氓仲,也就是說線程在執(zhí)行操作過程中,不會(huì)被操作系統(tǒng)掛起,而是一定會(huì)執(zhí)行完敬扛。在單處理器環(huán)境下晰洒,一條匯編指令顯然是原子操作,因?yàn)橹袛嘁惨ㄟ^指令來實(shí)現(xiàn)啥箭。

然而在多處理器的情況下谍珊,能夠被多個(gè)處理器同時(shí)執(zhí)行的操作任然算不上原子操作。因此急侥,真正的原子操作必須由硬件提供支持砌滞,比如 x86 平臺(tái)上如果在指令前面加上 “LOCK” 前綴,對(duì)應(yīng)的機(jī)器碼在執(zhí)行時(shí)會(huì)把總線鎖住坏怪,使得其他 CPU不能再執(zhí)行相同操作贝润,從而從硬件層面確保了操作的原子性。

這些非常底層的概念無需完全掌握铝宵,我們只要知道上述申請(qǐng)鎖的過程打掘,可以用一個(gè)原子性操作 test_and_set 來完成,它用偽代碼可以這樣表示:

bool test_and_set (bool *target) {

bool rv = *target;

*target = TRUE;

return rv;

}

這段代碼的作用是把 target 的值設(shè)置為 1鹏秋,并返回原來的值尊蚁。當(dāng)然,在具體實(shí)現(xiàn)時(shí)侣夷,它通過一個(gè)原子性的指令來完成横朋。

自旋鎖的總結(jié)

至此,自旋鎖的實(shí)現(xiàn)原理就很清楚了:

bool lock = false; // 一開始沒有鎖上百拓,任何線程都可以申請(qǐng)鎖

do {

while(test_and_set(&lock); // test_and_set 是一個(gè)原子操作

Critical section // 臨界區(qū)

lock = false; // 相當(dāng)于釋放鎖琴锭,這樣別的線程可以進(jìn)入臨界區(qū)

Reminder section // 不需要鎖保護(hù)的代碼

}

如果臨界區(qū)的執(zhí)行時(shí)間過長(zhǎng),使用自旋鎖不是個(gè)好主意耐版。之前我們介紹過時(shí)間片輪轉(zhuǎn)算法祠够,線程在多種情況下會(huì)退出自己的時(shí)間片。其中一種是用完了時(shí)間片的時(shí)間粪牲,被操作系統(tǒng)強(qiáng)制搶占古瓤。除此以外,當(dāng)線程進(jìn)行 I/O 操作腺阳,或進(jìn)入睡眠狀態(tài)時(shí)落君,都會(huì)主動(dòng)讓出時(shí)間片。顯然在 while 循環(huán)中亭引,線程處于忙等狀態(tài)绎速,白白浪費(fèi) CPU 時(shí)間,最終因?yàn)槌瑫r(shí)被操作系統(tǒng)搶占時(shí)間片焙蚓。如果臨界區(qū)執(zhí)行時(shí)間較長(zhǎng)纹冤,比如是文件讀寫洒宝,這種忙等是毫無必要的才写。

信號(hào)量

之前我在 介紹 GCD 底層實(shí)現(xiàn)的文章 中簡(jiǎn)單描述了信號(hào)量 dispatch_semaphore_t 的實(shí)現(xiàn)原理欺栗,它最終會(huì)調(diào)用到 sem_wait 方法咨跌,這個(gè)方法在 glibc 中被實(shí)現(xiàn)如下:

int sem_wait (sem_t *sem) {

int *futex = (int *) sem;

if (atomic_decrement_if_positive (futex) > 0)

return 0;

int err = lll_futex_wait (futex, 0);

return -1;

)

首先會(huì)把信號(hào)量的值減一女淑,并判斷是否大于零梗醇。如果大于零吐咳,說明不用等待蝠筑,所以立刻返回唉工。具體的等待操作在 lll_futex_wait 函數(shù)中實(shí)現(xiàn)求妹,lll 是 low level lock 的簡(jiǎn)稱乏盐。這個(gè)函數(shù)通過匯編代碼實(shí)現(xiàn),調(diào)用到 SYS_futex 這個(gè)系統(tǒng)調(diào)用制恍,使線程進(jìn)入睡眠狀態(tài)父能,主動(dòng)讓出時(shí)間片,這個(gè)函數(shù)在互斥鎖的實(shí)現(xiàn)中吧趣,也有可能被用到法竞。

主動(dòng)讓出時(shí)間片并不總是代表效率高。讓出時(shí)間片會(huì)導(dǎo)致操作系統(tǒng)切換到另一個(gè)線程强挫,這種上下文切換通常需要 10 微秒左右岔霸,而且至少需要兩次切換。如果等待時(shí)間很短俯渤,比如只有幾個(gè)微秒呆细,忙等就比線程睡眠更高效。

可以看到八匠,自旋鎖和信號(hào)量的實(shí)現(xiàn)都非常簡(jiǎn)單絮爷,這也是兩者的加解鎖耗時(shí)分別排在第一和第二的原因。再次強(qiáng)調(diào)梨树,加解鎖耗時(shí)不能準(zhǔn)確反應(yīng)出鎖的效率(比如時(shí)間片切換就無法發(fā)生)坑夯,它只能從一定程度上衡量鎖的實(shí)現(xiàn)復(fù)雜程度。

pthread_mutex

pthread 表示 POSIX thread抡四,定義了一組跨平臺(tái)的線程相關(guān)的 API柜蜈,pthread_mutex 表示互斥鎖≈秆玻互斥鎖的實(shí)現(xiàn)原理與信號(hào)量非常相似淑履,不是使用忙等,而是阻塞線程并睡眠藻雪,需要進(jìn)行上下文切換秘噪。

互斥鎖的常見用法如下:

pthread_mutexattr_t attr;

pthread_mutexattr_init(&attr);

pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); // 定義鎖的屬性

pthread_mutex_t mutex;

pthread_mutex_init(&mutex, &attr) // 創(chuàng)建鎖

pthread_mutex_lock(&mutex); // 申請(qǐng)鎖

// 臨界區(qū)

pthread_mutex_unlock(&mutex); // 釋放鎖

對(duì)于 pthread_mutex 來說,它的用法和之前沒有太大的改變勉耀,比較重要的是鎖的類型指煎,可以有 PTHREAD_MUTEX_NORMAL蹋偏、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE 等等贯要,具體的特性就不做解釋了暖侨,網(wǎng)上有很多相關(guān)資料。

一般情況下崇渗,一個(gè)線程只能申請(qǐng)一次鎖,也只能在獲得鎖的情況下才能釋放鎖京郑,多次申請(qǐng)鎖或釋放未獲得的鎖都會(huì)導(dǎo)致崩潰宅广。假設(shè)在已經(jīng)獲得鎖的情況下再次申請(qǐng)鎖,線程會(huì)因?yàn)榈却i的釋放而進(jìn)入睡眠狀態(tài)些举,因此就不可能再釋放鎖跟狱,從而導(dǎo)致死鎖。

然而這種情況經(jīng)常會(huì)發(fā)生户魏,比如某個(gè)函數(shù)申請(qǐng)了鎖驶臊,在臨界區(qū)內(nèi)又遞歸調(diào)用了自己。辛運(yùn)的是 pthread_mutex 支持遞歸鎖叼丑,也就是允許一個(gè)線程遞歸的申請(qǐng)鎖关翎,只要把 attr 的類型改成 PTHREAD_MUTEX_RECURSIVE 即可。

互斥鎖的實(shí)現(xiàn)

互斥鎖在申請(qǐng)鎖時(shí)鸠信,調(diào)用了 pthread_mutex_lock 方法纵寝,它在不同的系統(tǒng)上實(shí)現(xiàn)各有不同,有時(shí)候它的內(nèi)部是使用信號(hào)量來實(shí)現(xiàn)星立,即使不用信號(hào)量爽茴,也會(huì)調(diào)用到 lll_futex_wait 函數(shù),從而導(dǎo)致線程休眠绰垂。

上文說到如果臨界區(qū)很短室奏,忙等的效率也許更高,所以在有些版本的實(shí)現(xiàn)中劲装,會(huì)首先嘗試一定次數(shù)(比如 1000 次)的 testandtest胧沫,這樣可以在錯(cuò)誤使用互斥鎖時(shí)提高性能。

另外酱畅,由于 pthread_mutex 有多種類型琳袄,可以支持遞歸鎖等,因此在申請(qǐng)加鎖時(shí)纺酸,需要對(duì)鎖的類型加以判斷窖逗,這也就是為什么它和信號(hào)量的實(shí)現(xiàn)類似,但效率略低的原因餐蔬。

NSLock

NSLock 是 Objective-C 以對(duì)象的形式暴露給開發(fā)者的一種鎖碎紊,它的實(shí)現(xiàn)非常簡(jiǎn)單佑附,通過宏,定義了 lock 方法:

define MLOCK \

  • (void) lock\

{\

int err = pthread_mutex_lock(&_mutex);\

// 錯(cuò)誤處理 ……

}

NSLock 只是在內(nèi)部封裝了一個(gè) pthread_mutex仗考,屬性為 PTHREAD_MUTEX_ERRORCHECK音同,它會(huì)損失一定性能換來錯(cuò)誤提示。

這里使用宏定義的原因是秃嗜,OC 內(nèi)部還有其他幾種鎖权均,他們的 lock 方法都是一模一樣,僅僅是內(nèi)部 pthread_mutex 互斥鎖的類型不同锅锨。通過宏定義叽赊,可以簡(jiǎn)化方法的定義。

NSLock 比 pthread_mutex 略慢的原因在于它需要經(jīng)過方法調(diào)用必搞,同時(shí)由于緩存的存在必指,多次方法調(diào)用不會(huì)對(duì)性能產(chǎn)生太大的影響。

NSCondition

NSCondition 的底層是通過條件變量(condition variable) pthread_cond_t 來實(shí)現(xiàn)的恕洲。條件變量有點(diǎn)像信號(hào)量塔橡,提供了線程阻塞與信號(hào)機(jī)制,因此可以用來阻塞某個(gè)線程霜第,并等待某個(gè)數(shù)據(jù)就緒葛家,隨后喚醒線程,比如常見的生產(chǎn)者-消費(fèi)者模式庶诡。

如何使用條件變量

很多介紹 pthread_cond_t 的文章都會(huì)提到惦银,它需要與互斥鎖配合使用:

void consumer () { // 消費(fèi)者

pthread_mutex_lock(&mutex);

while (data == NULL) {

pthread_cond_wait(&condition_variable_signal, &mutex); // 等待數(shù)據(jù)

}

// --- 有新的數(shù)據(jù),以下代碼負(fù)責(zé)處理 ↓↓↓↓↓↓

// temp = data;

// --- 有新的數(shù)據(jù)末誓,以上代碼負(fù)責(zé)處理 ↑↑↑↑↑↑

pthread_mutex_unlock(&mutex);

}

void producer () {

pthread_mutex_lock(&mutex);

// 生產(chǎn)數(shù)據(jù)

pthread_cond_signal(&condition_variable_signal); // 發(fā)出信號(hào)給消費(fèi)者扯俱,告訴他們有了新的數(shù)據(jù)

pthread_mutex_unlock(&mutex);

}

自然我們會(huì)有疑問:“如果不用互斥鎖,只用條件變量會(huì)有什么問題呢喇澡?”迅栅。問題在于,temp = data; 這段代碼不是線程安全的晴玖,也許在你把 data 讀出來以前读存,已經(jīng)有別的線程修改了數(shù)據(jù)。因此我們需要保證消費(fèi)者拿到的數(shù)據(jù)是線程安全的呕屎。

wait 方法除了會(huì)被 signal 方法喚醒让簿,有時(shí)還會(huì)被虛假喚醒,所以需要這里 while 循環(huán)中的判斷來做二次確認(rèn)秀睛。

為什么要使用條件變量

介紹條件變量的文章非常多尔当,但大多都對(duì)一個(gè)一個(gè)基本問題避而不談:“為什么要用條件變量?它僅僅是控制了線程的執(zhí)行順序蹂安,用信號(hào)量或者互斥鎖能不能模擬出類似效果椭迎?”

網(wǎng)上的相關(guān)資料比較少锐帜,我簡(jiǎn)單說一下個(gè)人看法。信號(hào)量可以一定程度上替代 condition畜号,但是互斥鎖不行缴阎。在以上給出的生產(chǎn)者-消費(fèi)者模式的代碼中, pthread_cond_wait 方法的本質(zhì)是鎖的轉(zhuǎn)移简软,消費(fèi)者放棄鎖蛮拔,然后生產(chǎn)者獲得鎖,同理替饿,pthread_cond_signal 則是一個(gè)鎖從生產(chǎn)者到消費(fèi)者轉(zhuǎn)移的過程语泽。

如果使用互斥鎖,我們需要把代碼改成這樣:

void consumer () { // 消費(fèi)者

pthread_mutex_lock(&mutex);

while (data == NULL) {

pthread_mutex_unlock(&mutex);

pthread_mutex_lock(&another_lock) // 相當(dāng)于 wait 另一個(gè)互斥鎖

pthread_mutex_lock(&mutex);

}

pthread_mutex_unlock(&mutex);

}

這樣做存在的問題在于视卢,在等待 anotherlock 之前, 生產(chǎn)者有可能先執(zhí)行代碼廊驼, 從而釋放了 anotherlock据过。也就是說,我們無法保證釋放鎖和等待另一個(gè)鎖這兩個(gè)操作是原子性的妒挎,也就無法保證“先等待绳锅、后釋放 another_lock” 這個(gè)順序。

用信號(hào)量則不存在這個(gè)問題酝掩,因?yàn)樾盘?hào)量的等待和喚醒并不需要滿足先后順序鳞芙,信號(hào)量只表示有多少個(gè)資源可用,因此不存在上述問題期虾。然而與 pthread_cond_wait 保證的原子性鎖轉(zhuǎn)移相比原朝,使用信號(hào)量似乎存在一定風(fēng)險(xiǎn)(暫時(shí)沒有查到非原子性操作有何不妥)。

不過镶苞,使用 condition 有一個(gè)好處喳坠,我們可以調(diào)用 pthread_cond_broadcast 方法通知所有等待中的消費(fèi)者,這是使用信號(hào)量無法實(shí)現(xiàn)的茂蚓。

NSCondition 的做法

NSCondition 其實(shí)是封裝了一個(gè)互斥鎖和條件變量壕鹉, 它把前者的 lock 方法和后者的 wait/signal 統(tǒng)一在 NSCondition 對(duì)象中,暴露給使用者:

  • (void) signal {

pthread_cond_signal(&_condition);

}

// 其實(shí)這個(gè)函數(shù)是通過宏來定義的聋涨,展開后就是這樣

  • (void) lock {

int err = pthread_mutex_lock(&_mutex);

}

它的加解鎖過程與 NSLock 幾乎一致晾浴,理論上來說耗時(shí)也應(yīng)該一樣(實(shí)際測(cè)試也是如此)。在圖中顯示它耗時(shí)略長(zhǎng)牍白,我猜測(cè)有可能是測(cè)試者在每次加解鎖的前后還附帶了變量的初始化和銷毀操作脊凰。

NSRecursiveLock

上文已經(jīng)說過,遞歸鎖也是通過 pthread_mutex_lock 函數(shù)來實(shí)現(xiàn)淹朋,在函數(shù)內(nèi)部會(huì)判斷鎖的類型笙各,如果顯示是遞歸鎖钉答,就允許遞歸調(diào)用,僅僅將一個(gè)計(jì)數(shù)器加一杈抢,鎖的釋放過程也是同理数尿。

NSRecursiveLock 與 NSLock 的區(qū)別在于內(nèi)部封裝的 pthread_mutex_t 對(duì)象的類型不同,前者的類型為 PTHREAD_MUTEX_RECURSIVE惶楼。

NSConditionLock

NSConditionLock 借助 NSCondition 來實(shí)現(xiàn)右蹦,它的本質(zhì)就是一個(gè)生產(chǎn)者-消費(fèi)者模型〖呔瑁“條件被滿足”可以理解為生產(chǎn)者提供了新的內(nèi)容何陆。NSConditionLock 的內(nèi)部持有一個(gè) NSCondition 對(duì)象,以及 _condition_value 屬性豹储,在初始化時(shí)就會(huì)對(duì)這個(gè)屬性進(jìn)行賦值:

// 簡(jiǎn)化版代碼

  • (id) initWithCondition: (NSInteger)value {

if (nil != (self = [super init])) {

_condition = [NSCondition new]

_condition_value = value;

}

return self;

}

它的 lockWhenCondition 方法其實(shí)就是消費(fèi)者方法:

  • (void) lockWhenCondition: (NSInteger)value {

[_condition lock];

while (value != _condition_value) {

[_condition wait];

}

}

對(duì)應(yīng)的 unlockWhenCondition 方法則是生產(chǎn)者贷盲,使用了 broadcast 方法通知了所有的消費(fèi)者:

  • (void) unlockWithCondition: (NSInteger)value {

_condition_value = value;

[_condition broadcast];

[_condition unlock];

}

@synchronized

這其實(shí)是一個(gè) OC 層面的鎖, 主要是通過犧牲性能換來語法上的簡(jiǎn)潔與可讀剥扣。

我們知道 @synchronized 后面需要緊跟一個(gè) OC 對(duì)象巩剖,它實(shí)際上是把這個(gè)對(duì)象當(dāng)做鎖來使用。這是通過一個(gè)哈希表來實(shí)現(xiàn)的钠怯,OC 在底層使用了一個(gè)互斥鎖的數(shù)組(你可以理解為鎖池)佳魔,通過對(duì)對(duì)象去哈希值來得到對(duì)應(yīng)的互斥鎖。

具體的實(shí)現(xiàn)原理可以參考這篇文章: 關(guān)于 @synchronized晦炊,這兒比你想知道的還要多

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鞠鲜,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子断国,更是在濱河造成了極大的恐慌贤姆,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件并思,死亡現(xiàn)場(chǎng)離奇詭異庐氮,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)宋彼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門弄砍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人输涕,你說我怎么就攤上這事音婶。” “怎么了莱坎?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵衣式,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng)碴卧,這世上最難降的妖魔是什么弱卡? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮住册,結(jié)果婚禮上婶博,老公的妹妹穿的比我還像新娘。我一直安慰自己荧飞,他們只是感情好凡人,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著叹阔,像睡著了一般挠轴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耳幢,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天岸晦,我揣著相機(jī)與錄音,去河邊找鬼睛藻。 笑死委煤,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的修档。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼府框,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼吱窝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起迫靖,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤院峡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后系宜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體照激,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年盹牧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了俩垃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡汰寓,死狀恐怖口柳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情有滑,我是刑警寧澤跃闹,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響望艺,放射性物質(zhì)發(fā)生泄漏苛秕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一找默、第九天 我趴在偏房一處隱蔽的房頂上張望艇劫。 院中可真熱鬧,春花似錦啡莉、人聲如沸港准。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浅缸。三九已至,卻和暖如春魄咕,著一層夾襖步出監(jiān)牢的瞬間衩椒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工哮兰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留毛萌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓喝滞,卻偏偏與公主長(zhǎng)得像阁将,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子右遭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容

  • 本文轉(zhuǎn)載自:深入理解 iOS 開發(fā)中的鎖作者博客:bestswifter 摘要 本文的目的不是介紹 iOS 中各種...
    Harveyhhw閱讀 468評(píng)論 0 2
  • iOS線程安全的鎖與性能對(duì)比 一做盅、鎖的基本使用方法 1.1、@synchronized 這是我們最熟悉的枷鎖方式窘哈,...
    Jacky_Yang閱讀 2,209評(píng)論 0 17
  • Q:為什么出現(xiàn)多線程吹榴? A:為了實(shí)現(xiàn)同時(shí)干多件事的需求(并發(fā)),同時(shí)進(jìn)行著下載和頁面UI刷新滚婉。對(duì)于處理器图筹,為每個(gè)線...
    幸福相依閱讀 1,570評(píng)論 0 2
  • 前言 在多線程開發(fā)中,常會(huì)遇到多個(gè)線程訪問修改數(shù)據(jù)让腹。為了防止數(shù)據(jù)不一致或數(shù)據(jù)污染远剩,通常采用加鎖機(jī)制來保證線程安全。...
    趙夢(mèng)楠閱讀 912評(píng)論 0 5
  • 這歌寫的如何我是聽不出來哨鸭,但是聽著這歌詞民宿,我還是有所感觸。能讓人有所感觸的歌當(dāng)然不能稱之為壞了吧像鸡。歌詞分為三小節(jié)活鹰,...
    lyhux閱讀 615評(píng)論 0 0