iOS多線(xiàn)程指南

本文將從以下幾個(gè)部分來(lái)介紹多線(xiàn)程。

  • 第一部分介紹多線(xiàn)程的基本原理。
  • 第二部分介紹Run loop。
  • 第三部分介紹多線(xiàn)程的幾種技術(shù)實(shí)現(xiàn)方式。
  • 第四部分介紹線(xiàn)程安全與同步。

第一部分:多線(xiàn)程的基本原理

基礎(chǔ)術(shù)語(yǔ):

  • 線(xiàn)程(線(xiàn)程)用于指代獨(dú)立執(zhí)行的代碼段。
  • 進(jìn)程(process)用于指代一個(gè)正在運(yùn)行的可執(zhí)行程序,它可以包含多個(gè)線(xiàn)程。
  • 任務(wù)(task)用于指代抽象的概念,表示需要執(zhí)行工作。

多線(xiàn)程是一個(gè)比較輕量級(jí)的方法來(lái)實(shí)現(xiàn)單個(gè)應(yīng)用程序內(nèi)多個(gè)代碼執(zhí)行路徑。多線(xiàn)程是一種技術(shù),一種方法,這種方法使得單個(gè)應(yīng)用程序能夠執(zhí)行多個(gè)代碼塊。從技術(shù)角度來(lái)看,一個(gè)線(xiàn)程就是一個(gè)需要管理執(zhí)行代碼的內(nèi)核級(jí)和應(yīng)用級(jí)數(shù)據(jù)結(jié)構(gòu)組合。內(nèi)核級(jí)結(jié)構(gòu)協(xié)助調(diào)度線(xiàn)程事件,并搶占式調(diào)度一個(gè)線(xiàn)程到可用的內(nèi)核之上。 應(yīng)用級(jí)結(jié)構(gòu)包括用于存儲(chǔ)函數(shù)調(diào)用的調(diào)用堆棧和應(yīng)用程序需要管理和操作線(xiàn)程屬性和狀態(tài)的結(jié)構(gòu)。

為了理解多線(xiàn)程種技術(shù),我們先來(lái)了解一下線(xiàn)程的行為。

線(xiàn)程基本行為:

狀態(tài):

線(xiàn)程有三種狀態(tài):運(yùn)行(running)、就緒(ready)、阻塞(blocked)。線(xiàn)程啟動(dòng)之后,線(xiàn)程就進(jìn)入三個(gè)狀態(tài)中的任何一個(gè):運(yùn)行(running)、就緒(ready)、阻塞(blocked)。如 果一個(gè)線(xiàn)程當(dāng)前沒(méi)有運(yùn)行,那么它不是處于阻塞,就是等待外部輸入,或者已經(jīng)準(zhǔn)備 就緒等待分配 CPU。線(xiàn)程持續(xù)在這三個(gè)狀態(tài)之間切換,直到它最終退出或者進(jìn)入中斷狀態(tài)。

分類(lèi):
線(xiàn)程分為可連接線(xiàn)程(Joinable thread )和脫離線(xiàn)程(Datached thread) 。

可連接線(xiàn)程類(lèi)似于子線(xiàn)程。雖然你作為獨(dú)立線(xiàn)程運(yùn)行,但是可連接線(xiàn) 程在它資源可以被系統(tǒng)回收之前必須被其他線(xiàn)程連接。可連接線(xiàn)程同時(shí)提供了一個(gè)顯 示的方式來(lái)把數(shù)據(jù)從一個(gè)正在退出的線(xiàn)程傳遞到其他線(xiàn)程。在它退出之前,可連接線(xiàn) 程可以傳遞一個(gè)數(shù)據(jù)指針或者其他返回值給 pthread_exit 函數(shù)。其他線(xiàn)程可以通過(guò) pthread_join 函數(shù)來(lái)拿到這些數(shù)據(jù)。

脫離線(xiàn)程(Datached thread) 允許系統(tǒng)在線(xiàn)程完成的時(shí)候立 即釋放它的數(shù)據(jù)結(jié)構(gòu)。脫離線(xiàn)程同時(shí)不需要顯示的和你的應(yīng)用程序交互。

它們區(qū)別在于:在應(yīng)用程序退出時(shí),脫離線(xiàn)程可以立即被中斷,而可連接線(xiàn)程則不可以。每個(gè)可連接 線(xiàn)程必須在進(jìn)程被允許可以退出的時(shí)候被連接。

默認(rèn)情況下只有應(yīng)用程序的主線(xiàn)程是可連接的方式創(chuàng)建的,也就是說(shuō)大部分上層的線(xiàn)程技術(shù)都默認(rèn)創(chuàng)建了脫離線(xiàn)程(Datached thread)。當(dāng)然在線(xiàn)程啟動(dòng)后, 你可以通過(guò)調(diào)用 pthread_detach 函數(shù)來(lái)把線(xiàn)程修改為可連接的。

優(yōu)先級(jí):

你創(chuàng)建的任何線(xiàn)程默認(rèn)的優(yōu)先級(jí)是和你本身線(xiàn)程相同。內(nèi)核調(diào)度算法在決定該運(yùn) 行那個(gè)線(xiàn)程時(shí),把線(xiàn)程的優(yōu)先級(jí)作為考量因素,較高優(yōu)先級(jí)的線(xiàn)程會(huì)比較低優(yōu)先級(jí)的 線(xiàn)程具有更多的運(yùn)行機(jī)會(huì)。較高優(yōu)先級(jí)不保證你的線(xiàn)程具體執(zhí)行的時(shí)間,只是相比較 低優(yōu)先級(jí)的線(xiàn)程,它更有可能被調(diào)度器選擇執(zhí)行而已。

配置堆棧:

對(duì)于每個(gè)你新創(chuàng)建的線(xiàn)程,系統(tǒng)會(huì)在你的進(jìn)程空間里面分配一定的內(nèi)存作為該線(xiàn) 程的堆棧。可以通過(guò)相關(guān)方法設(shè)置線(xiàn)程堆棧大小。

中斷線(xiàn)程:

退出一個(gè)線(xiàn)程推薦的方法是讓它在它主體入口點(diǎn)正常退出。經(jīng)管 Cocoa、POSIX 和 Multiprocessing Services 提供了直接殺死線(xiàn)程的例程,但是使用這些例程是強(qiáng) 烈不鼓勵(lì)的。殺死一個(gè)線(xiàn)程阻止了線(xiàn)程本身的清理工作。線(xiàn)程分配的內(nèi)存可能造成泄 露,并且其他線(xiàn)程當(dāng)前使用的資源可能沒(méi)有被正確清理干凈,之后造成潛在的問(wèn)題。

如果你的應(yīng)用程序需要在一個(gè)操作中間中斷一個(gè)線(xiàn)程,你應(yīng)該設(shè)計(jì)你的線(xiàn)程響應(yīng) 取消或退出的消息。對(duì)于長(zhǎng)時(shí)運(yùn)行的操作,這意味著周期性停止工作來(lái)檢查該消息是 否到來(lái)。如果該消息的確到來(lái)并要求線(xiàn)程退出,那么線(xiàn)程就有機(jī)會(huì)來(lái)執(zhí)行任何清理和 退出工作;否則,它返回繼續(xù)工作和處理下一個(gè)數(shù)據(jù)塊。

第二部分:Run Loops:

Run loops 是線(xiàn)程相關(guān)的的基礎(chǔ)框架的一部分。本質(zhì)上一個(gè) run loop 就是一個(gè)事件處理的循環(huán),用于不停的調(diào)度工作以及處理輸入事件。使用 run loop 的目的是讓你的線(xiàn)程在有工作的時(shí)候忙于工作,而沒(méi)工作的時(shí)候處于休眠狀態(tài)。

Cocoa 和 Core Fundation 都提供了 run loop objects 來(lái)幫助配置和管理你線(xiàn)程的 run loop。你的應(yīng)用程序不需要顯式的創(chuàng)建這些 對(duì)象(run loop objects);每個(gè)線(xiàn)程,包括程序的主線(xiàn)程都有與之對(duì)應(yīng)的 run loop object。只有輔助線(xiàn)程(你創(chuàng)建的)需要顯式的運(yùn)行它的 run loop。在 Carbon 和 Cocoa 程序中, 主線(xiàn)程會(huì)自動(dòng)創(chuàng)建并運(yùn)行它 run loop,作為一般應(yīng)用程序啟動(dòng)過(guò)程的一部分。

一個(gè) run loop 是用來(lái)在線(xiàn)程上管理事件異步到達(dá)的基礎(chǔ)設(shè)施。一個(gè) run loop 為 線(xiàn)程監(jiān)測(cè)一個(gè)或多個(gè)事件源。當(dāng)事件到達(dá)的時(shí)候,系統(tǒng)喚醒線(xiàn)程并調(diào)度事件到 run loop,然后分配給指定程序。如果沒(méi)有事件出現(xiàn)和準(zhǔn)備處理,run loop 把線(xiàn)程置于休眠狀態(tài)。runloop與線(xiàn)程息息相關(guān),離開(kāi)線(xiàn)程單獨(dú)說(shuō)runloop是沒(méi)有意義的。

** runLoop 本質(zhì)就是一個(gè)對(duì)象,這個(gè)對(duì)象管理了其需要處理的事件和消息,并提供了一個(gè)入口函數(shù)來(lái)執(zhí)行上面 Event Loop 的邏輯。**線(xiàn)程執(zhí)行了這個(gè)函數(shù)后,就會(huì)一直處于這個(gè)函數(shù)內(nèi)部 "接受消息->等待->處理" 的循環(huán)中,直到這個(gè)循環(huán)結(jié)束(比如傳入 quit 的消息),函數(shù)返回。

基本概念:

實(shí)現(xiàn)對(duì)象

OSX/iOS 系統(tǒng)中,提供了兩個(gè)這樣的對(duì)象:NSRunLoop 和 CFRunLoopRef。

  • CFRunLoopRef 是在 CoreFoundation 框架內(nèi)的,它提供了純 C 函數(shù)的 API,所有這些 API 都是線(xiàn)程安全的。蘋(píng)果不允許直接創(chuàng)建 RunLoop,它只提供了兩個(gè)自動(dòng)獲取的函數(shù):CFRunLoopGetMain()CFRunLoopGetCurrent()。第一次獲取RunLoop時(shí),將為你創(chuàng)建runloop對(duì)象。
  • NSRunLoop 是基于CFRunLoopRef 的封裝,提供了面向?qū)ο蟮?API,但是這些 API 不是線(xiàn)程安全的。

在 CoreFoundation 里面關(guān)于 RunLoop 有5個(gè)類(lèi):

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef


    A50FE6F8-E470-48D8-A1C3-5629FCDA7586.png

一個(gè) RunLoop 包含若干個(gè) Mode,每個(gè) Mode 又包含若干個(gè) Source/Timer/Observer。每次調(diào)用 RunLoop 的主函數(shù)時(shí),只能指定其中一個(gè) Mode,這個(gè)Mode被稱(chēng)作 CurrentMode。如果需要切換 Mode,只能退出 Loop,再重新指定一個(gè) Mode 進(jìn)入。這樣做主要是為了分隔開(kāi)不同組的 Source/Timer/Observer,讓其互不影響。

模式(Mode):

Run loop 模式(Mode)是所有要監(jiān)視的輸入源和定時(shí)源以及要通知的 run loop 注冊(cè)觀(guān)察者的集合。每次運(yùn)行你的 run loop,你都要指定(無(wú)論顯示還是隱式)其運(yùn)行個(gè)模 式。在 run loop 運(yùn)行過(guò)程中,只有和模式相關(guān)的源才會(huì)被監(jiān)視并允許他們傳遞事件 消息。(類(lèi)似的,只有和模式相關(guān)的觀(guān)察者會(huì)通知 run loop 的進(jìn)程)。和其他模式關(guān)聯(lián)的源只有在 run loop 運(yùn)行在其模式下才會(huì)運(yùn)行,否則處于暫停狀態(tài)。

輸入源(Source):

輸入源異步的發(fā)送消息給你的線(xiàn)程。 是事件產(chǎn)生的地方。事件來(lái)源取決于輸入源的種類(lèi):基于端口的輸入源和自定義輸入源。

  • Source0(自定義輸入源) 只包含了一個(gè)回調(diào)(函數(shù)指針,它并不能主動(dòng)觸發(fā)事件。使用時(shí),你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個(gè) Source 標(biāo)記為待處理,然后手動(dòng)調(diào)用 CFRunLoopWakeUp(runloop) 來(lái)喚醒 RunLoop,讓其處理這個(gè)事件。
  • Source1(基于端口的輸入源) 包含了一個(gè) mach_port 和一個(gè)回調(diào)(函數(shù)指針),被用于通過(guò)內(nèi)核和其他線(xiàn)程相互發(fā)送消息。這種 Source 能主動(dòng)喚醒 RunLoop 的線(xiàn)程。 Cocoa 和 Core Foundation 內(nèi)置支持使用端口相關(guān)的對(duì)象和函數(shù)來(lái)創(chuàng)建的基于端 口的源。例如,在 Coco 里面你從來(lái)不需要直接創(chuàng)建輸入源。你只要簡(jiǎn)單的創(chuàng)建端口 對(duì)象,并使用 NSPort 的方法把該端口添加到 run loop。端口對(duì)象會(huì)自己處理創(chuàng)建和 配置輸入源。

備注:selector源本質(zhì)上是Cocoa 定義了自定義輸入源。它允許你在任何線(xiàn)程執(zhí)行selector。并且是強(qiáng)制執(zhí)行

當(dāng)你創(chuàng)建輸入源,你需要將其分配給 run loop 中的一個(gè)或多個(gè)模式。模式只會(huì) 在特定事件影響監(jiān)聽(tīng)的源。

定時(shí)源(Timer)

定時(shí)源在預(yù)設(shè)的時(shí)間點(diǎn)同步方式傳遞消息。定時(shí)器是線(xiàn)程通知自己做某事的一種方法.它和 NSTimer 是toll-free bridged 的,可以混用。其包含一個(gè)時(shí)間長(zhǎng)度和一個(gè)回調(diào)(函數(shù)指針)。當(dāng)其加入到 RunLoop 時(shí),RunLoop會(huì)注冊(cè)對(duì)應(yīng)的時(shí)間點(diǎn),當(dāng)時(shí)間點(diǎn)到時(shí),RunLoop會(huì)被喚醒以執(zhí)行那個(gè)回調(diào)。

Run Loop 觀(guān)察者(Observer)

每個(gè) Observer 都包含了一個(gè)回調(diào)(函數(shù)指針),當(dāng) RunLoop 的狀態(tài)發(fā)生變化時(shí),觀(guān)察者就能通過(guò)回調(diào)接受到這個(gè)變化。
你可以使用 run loop 觀(guān)察者來(lái)為處理某一特定事件或是進(jìn) 入休眠的線(xiàn)程做準(zhǔn)備。你可以將 run loop 觀(guān)察者和以下事件關(guān)聯(lián):

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry        = (1UL << 0), // 即將進(jìn)入Loop
    kCFRunLoopBeforeTimers  = (1UL << 1), // 即將處理 Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠
    kCFRunLoopAfterWaiting  = (1UL << 6), // 剛從休眠中喚醒
    kCFRunLoopExit          = (1UL << 7), // 即將退出Loop
};```

源是合適的同步或異步事件發(fā)生時(shí)觸發(fā),而 run loop 觀(guān)察者則是在 run loop 本身運(yùn)行的特定時(shí)候觸發(fā)。

現(xiàn)在看看CFRunLoopMode 和 CFRunLoop 結(jié)構(gòu),你會(huì)對(duì)此清晰很多:

struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
struct __CFRunLoop {
CFMutableSetRef _commonModes; // Set
CFMutableSetRef _commonModeItems; // Set
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set
...
};```

這里有個(gè)概念叫 "CommonModes":一個(gè) Mode 可以將自己標(biāo)記為"Common"屬性(通過(guò)將其 ModeName 添加到 RunLoop 的 "commonModes" 中)。每當(dāng) RunLoop 的內(nèi)容發(fā)生變化時(shí),RunLoop 都會(huì)自動(dòng)將 _commonModeItems 里的 Source/Observer/Timer 同步到具有 "Common" 標(biāo)記的所有Mode里。

Run Loop 的事件隊(duì)列

每次運(yùn)行 run loop,你線(xiàn)程的 run loop 對(duì)會(huì)自動(dòng)處理之前未處理的消息,并通 知相關(guān)的觀(guān)察者。具體的順序如下:


事件隊(duì)列.png

因?yàn)槎〞r(shí)器和輸入源的觀(guān)察者是在相應(yīng)的事件發(fā)生之前傳遞消息,所以通知的時(shí) 間和實(shí)際事件發(fā)生的時(shí)間之間可能存在誤差。如果需要精確時(shí)間控制,你可以使用休 眠和喚醒通知來(lái)幫助你校對(duì)實(shí)際發(fā)生事件的時(shí)間。

因?yàn)楫?dāng)你運(yùn)行 run loop 時(shí)定時(shí)器和其它周期性事件經(jīng)常需要被傳遞,撤銷(xiāo) run loop 也會(huì)終止消息傳遞。典型的例子就是鼠標(biāo)路徑追蹤。因?yàn)槟愕拇a直接獲取到 消息而不是經(jīng)由程序傳遞,因此活躍的定時(shí)器不會(huì)開(kāi)始直到鼠標(biāo)追蹤結(jié)束并將控制權(quán) 交給程序。

Run loop 可以由 run loop 對(duì)象顯式喚醒。其它消息也可以喚醒 run loop。例如, 添加新的非基于端口的源會(huì)喚醒 run loop 從而可以立即處理輸入源而不需要等待其 他事件發(fā)生后再處理。

runloop實(shí)現(xiàn)原理:

介紹runloop原理之前先介紹一下ios系統(tǒng)層次

iOS的系統(tǒng)架構(gòu)分為四個(gè)層次:核心操作系統(tǒng)層(Core OS layer)、核心服務(wù)層(Core Services layer)、媒體層(Media layer)和可觸摸層(Cocoa Touch layer)。

  • 位于iOS系統(tǒng)架構(gòu)最下面的一層是核心操作系統(tǒng)層(Core OS layer),它包括內(nèi)存管理、文件系統(tǒng)、電源管理以及一些其他的操作系統(tǒng)任務(wù)。它可以直接和硬件設(shè)備進(jìn)行交互。核心操作系統(tǒng)層包括以下這些組件:Accelerate Framework、External Accessory Framework、Security Framework、System等幾個(gè)框架,基本都是基于c語(yǔ)言的接口。
  • 第二層是核心服務(wù)層,我們可以通過(guò)它來(lái)訪(fǎng)問(wèn)iOS的一些服務(wù)。包含:Address Book Framework、CFNetwork Framework、Core Data Framework、Core Foundation Framework、Core Location Framework、Core Media Framework、Core Telephony Framework、Event Kit Framework、Foundation Framework、Mobile Core Services Framework、Quick Look Framework、Store Kit Framework、System Configuration Framework、Block Objects、Grand Central Dispatch 、In App Purchase、Location Services、SQLite、XML Support等一些框架,也基本都是基于c語(yǔ)言的接口。
  • 第三層是媒體層,通過(guò)它我們可以在應(yīng)用程序中使用各種媒體文件,進(jìn)行音頻與視頻的錄制,圖形的繪制,以及制作基礎(chǔ)的動(dòng)畫(huà)效果。它包括以下這些組件:
    Core Audio OpenGL Audio Mixing Audio Recording Video Playback JPG,PNG,TIFF PDF Quartz Core Animation OpenGL ES
  • 第四層是可觸摸層,這一層為我們的應(yīng)用程序開(kāi)發(fā)提供了各種有用的框架,并且大部分與用戶(hù)界面有關(guān),本質(zhì)上來(lái)說(shuō)它負(fù)責(zé)用戶(hù)在iOS設(shè)備上的觸摸交互操作。它包括以下這些組件: Multi-Touch Events Core Motion Camera View Hierarchy Localization Alerts Web Views Image Picker Multi-Touch Controls.

cocoa 很多組件都有兩種實(shí)現(xiàn),一種是基于 C 的以 CF 開(kāi)頭的類(lèi)(CF=Core Foundation),這是比較底層的;另一種是基于 Obj-C 的以 NS 開(kāi)頭的類(lèi)(NS=Next Step),這種類(lèi)抽象層次更高,易于使用。

最上面一層也稱(chēng):Darwin即操作系統(tǒng)的核心。


Darwin.png

其中,在硬件層上面的三個(gè)組成部分:Mach、BSD、IOKit (還包括一些上面沒(méi)標(biāo)注的內(nèi)容),共同組成了 XNU 內(nèi)核。
XNU 內(nèi)核的內(nèi)環(huán)被稱(chēng)作 Mach,其作為一個(gè)微內(nèi)核,僅提供了諸如處理器調(diào)度、IPC (進(jìn)程間通信)等非常少量的基礎(chǔ)服務(wù)。
BSD 層可以看作圍繞 Mach 層的一個(gè)外環(huán),其提供了諸如進(jìn)程管理、文件系統(tǒng)和網(wǎng)絡(luò)等功能。 IOKit 層是為設(shè)備驅(qū)動(dòng)提供了一個(gè)面向?qū)ο?C++)的一個(gè)框架。

Mach 本身提供的 API 非常有限,而且蘋(píng)果也不鼓勵(lì)使用 Mach 的 API,但是這些API非常基礎(chǔ),如果沒(méi)有這些API的話(huà),其他任何工作都無(wú)法實(shí)施。在 Mach 中,所有的東西都是通過(guò)自己的對(duì)象實(shí)現(xiàn)的,進(jìn)程、線(xiàn)程和虛擬內(nèi)存都被稱(chēng)為"對(duì)象"。和其他架構(gòu)不同, Mach 的對(duì)象間不能直接調(diào)用,只能通過(guò)消息傳遞的方式實(shí)現(xiàn)對(duì)象間的通信。"消息"是 Mach 中最基礎(chǔ)的概念,消息在兩個(gè)端口 (port) 之間傳遞,這就是 Mach 的 IPC (進(jìn)程間通信) 的核心。

為了實(shí)現(xiàn)消息的發(fā)送和接收,mach_msg() 函數(shù)實(shí)際上是調(diào)用了一個(gè) Mach 陷阱 (trap),即函數(shù)mach_msg_trap(),陷阱這個(gè)概念在 Mach 中等同于系統(tǒng)調(diào)用。當(dāng)你在用戶(hù)態(tài)調(diào)用 mach_msg_trap() 時(shí)會(huì)觸發(fā)陷阱機(jī)制,切換到內(nèi)核態(tài);內(nèi)核態(tài)中內(nèi)核實(shí)現(xiàn)的 mach_msg() 函數(shù)會(huì)完成實(shí)際的工作。

RunLoop 的核心就是一個(gè) mach_msg() RunLoop 調(diào)用這個(gè)函數(shù)去接收消息,如果沒(méi)有別人發(fā)送 port 消息過(guò)來(lái),內(nèi)核會(huì)將線(xiàn)程置于等待狀態(tài)。例如你在模擬器里跑起一個(gè) iOS 的 App,然后在 App 靜止時(shí)點(diǎn)擊暫停,你會(huì)看到主線(xiàn)程調(diào)用棧是停留在 mach_msg_trap() 這個(gè)地方。

runloop基本使用:
獲得runloop對(duì)象

1.在 Cocoa 程序中,使用 NSRunLoop 的 currentRunLoop 類(lèi)方法來(lái)檢索一個(gè) NSRunLoop 對(duì)象。
2.使用CFRunLoopGetCurrent函數(shù)。

2.配置 Run Loop

  • 在你在輔助線(xiàn)程運(yùn)行 run loop 之前,你必須至少添加一輸入源或定時(shí)器給它。
  • 你也可以添加run loop觀(guān)察者來(lái)監(jiān)視r(shí)un loop的不同執(zhí)行階段情 況。為了給run loop添加一個(gè)觀(guān)察者,你可以創(chuàng)建CFRunLoopObserverRef不透明類(lèi) 型,并使用CFRunLoopAddObserver將它添加到你的run loop。
  • 當(dāng)當(dāng)前長(zhǎng)時(shí)間運(yùn)行的線(xiàn)程配置 run loop 的時(shí)候,最好添加至少一個(gè)輸入源到 run loop 以接收消息。雖然你可以使用附屬的定時(shí)器來(lái)進(jìn)入 run loop,但是一旦定時(shí)器 觸發(fā)后,它通常就變?yōu)闊o(wú)效了,這會(huì)導(dǎo)致 run loop 退出。雖然附加一個(gè)循環(huán)的定時(shí) 器可以讓 run loop 運(yùn)行一個(gè)相對(duì)較長(zhǎng)的周期,但是這也會(huì)導(dǎo)致周期性的喚醒線(xiàn)程, 這實(shí)際上是輪詢(xún)(polling)的另一種形式而已。與之相反,輸入源會(huì)一直等待某事 件發(fā)生,在事情導(dǎo)致前它讓線(xiàn)程處于休眠狀態(tài)。

3.啟動(dòng) Run Loop

啟動(dòng) run loop 只對(duì)程序的輔助線(xiàn)程有意義。有幾種方式可以啟動(dòng) run loop,包括以下這些:

  • 無(wú)條件的
    無(wú)條件進(jìn)入run loop是最簡(jiǎn)單的方法,但是它會(huì)使你的線(xiàn)程處于一個(gè)永久的循環(huán)之中。
  • 設(shè)置超時(shí)時(shí)間
    設(shè)置超時(shí)時(shí)間進(jìn)入runloop 后。runloop會(huì)運(yùn)作到某一事件到達(dá)或者規(guī)定時(shí)間已經(jīng)到期。
  • 特定的模式
    模式和超時(shí)不是 互斥的,他們可以在啟動(dòng) run loop 的時(shí)候同時(shí)使用。模式限制了可以傳遞事件給 run loop 的輸入源的類(lèi)型

4.退出Run Loop

  • 1.給 run loop 設(shè)置超時(shí)時(shí)間
  • 2.通知 run loop 停止

使用 CFRunLoopStop 來(lái)顯式的停止 run loop 和使用超時(shí)時(shí)間產(chǎn)生的結(jié)果相似。 Run loop 把所有剩余的通知發(fā)送出去再退出。與設(shè)置超時(shí)的不同的是你可以在無(wú)條 件啟動(dòng)的 run loop 里面使用該技術(shù)。

盡管移除 run loop 的輸入源和定時(shí)器也可能導(dǎo)致 run loop 退出,但這并不是可 靠的退出 run loop 的方法。一些系統(tǒng)例程會(huì)添加輸入源到 run loop 里面來(lái)處理所需 事件。因?yàn)槟愕拇a未必會(huì)考慮到這些輸入源,這樣可能導(dǎo)致你無(wú)法沒(méi)從系統(tǒng)例程中 移除它們,從而導(dǎo)致退出 run loop。

配置runloop的源:

待續(xù)

第三部分介紹多線(xiàn)程的幾種實(shí)現(xiàn)方式。

iOS支持三個(gè)層次的線(xiàn)程編程。分別是:

  • Thread
  • Cocoa Operations
  • Grand Central Dispatch技術(shù)

Thread技術(shù):
Thread包含兩種:

  • 1.Cocoa threads
    優(yōu)點(diǎn):NSThread 比其他兩個(gè)輕量級(jí)
    缺點(diǎn):需要自己管理線(xiàn)程的生命周期,線(xiàn)程同步。線(xiàn)程同步對(duì)數(shù)據(jù)的加鎖會(huì)有一定的系統(tǒng)開(kāi)銷(xiāo)
    創(chuàng)建:
-(id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument
+(void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument
[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];
NSThread* myThread = [[NSThread alloc] initWithTarget:self
                                     selector:@selector(doSomething:)
                                         object:nil];
 [myThread start];

用NSObject的類(lèi)方法 performSelectorInBackground:withObject:
通信:

[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES]
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];
[self performSelector:@selector(run) withObject:nil];

相關(guān)的一些方法:

//取消線(xiàn)程
-(void)cancel;
//啟動(dòng)線(xiàn)程
-(void)start;
//判斷某個(gè)線(xiàn)程的狀態(tài)的屬性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;
//設(shè)置和獲取線(xiàn)程名字
-(void)setName:(NSString *)n;
-(NSString *)name;
//獲取當(dāng)前線(xiàn)程信息
+(NSThread *)currentThread;
//獲取主線(xiàn)程信息
+(NSThread *)mainThread;
//使當(dāng)前線(xiàn)程暫停一段時(shí)間,或者暫停到某個(gè)時(shí)刻
+(void)sleepForTimeInterval:(NSTimeInterval)time;
+(void)sleepUntilDate:(NSDate *)date;
  • 2.POSIX threads: 基于 C 語(yǔ)言的一個(gè)多線(xiàn)程庫(kù)。

POSIX線(xiàn)程(POSIX threads),簡(jiǎn)稱(chēng)Pthreads,是線(xiàn)程的POSIX標(biāo)準(zhǔn)。該標(biāo)準(zhǔn)定義了創(chuàng)建和操縱線(xiàn)程的一整套API。在類(lèi)Unix操作系統(tǒng)(Unix、Linux、Mac OS X等)中,都使用Pthreads作為操作系統(tǒng)的線(xiàn)程。

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    pthread_t thread;
    //創(chuàng)建一個(gè)線(xiàn)程并自動(dòng)執(zhí)行
    pthread_create(&thread, NULL, start, NULL);
}
void *start(void *data) {
    NSLog(@"%@", [NSThread currentThread]);

    returnNULL;
}
Grand Central Dispatch#####

Grand Central Dispatch (GCD)是Apple開(kāi)發(fā)的一個(gè)多核編程的解決方法。

優(yōu)勢(shì):
GCD是蘋(píng)果公司為多核的并行運(yùn)算提出的解決方案
GCD會(huì)自動(dòng)利用更多的CPU內(nèi)核(比如雙核、四核)
GCD會(huì)自動(dòng)管理線(xiàn)程的生命周期(創(chuàng)建線(xiàn)程、調(diào)度任務(wù)、銷(xiāo)毀線(xiàn)程)
程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫(xiě)任何線(xiàn)程管理代碼。

dispatch object并不參與垃圾回收系統(tǒng),所以即使開(kāi)啟了GC,你也必須手動(dòng)管理GCD對(duì)象的內(nèi)存。

基本概念:

隊(duì)列(dispatch queue):dispatch queue是一個(gè)對(duì)象,它可以接受任務(wù),并將任務(wù)以先到先執(zhí)行的順序來(lái)執(zhí)行。

  • 串行隊(duì)列(SerialDispatchQueue):同一時(shí)間只執(zhí)行單一任務(wù)。
  • 并發(fā)隊(duì)列(ConcurrentDispatchQueue):可以讓多個(gè)任務(wù)并發(fā)執(zhí)行,并發(fā)功能只有在一異步函數(shù)下才有效果。
  • 同步:在當(dāng)前線(xiàn)程中執(zhí)行
  • 異步:在另一條線(xiàn)程中執(zhí)行

GCD中的隊(duì)列:

  • 1.MainDispatchQueue:

與主線(xiàn)程功能相同。實(shí)際上,提交至main queue的任務(wù)會(huì)在主線(xiàn)程中執(zhí)行。main queue可以調(diào)用dispatch_get_main_queue()來(lái)獲得。因?yàn)閙ain queue是與主線(xiàn)程相關(guān)的,所以這是一個(gè)串行隊(duì)列。一般用來(lái)執(zhí)行UI方面的操作。

  • 2.Global queues

全局隊(duì)列是并發(fā)隊(duì)列,并由整個(gè)進(jìn)程共享。進(jìn)程中存在三個(gè)全局隊(duì)列:高、中(默認(rèn))、低、后臺(tái)四個(gè)優(yōu)先級(jí)隊(duì)列。可以調(diào)用dispatch_get_global_queue函數(shù)傳入優(yōu)先級(jí)來(lái)訪(fǎng)問(wèn)隊(duì)列。
GCD默認(rèn)已經(jīng)提供了全局的并發(fā)隊(duì)列,供整個(gè)應(yīng)用使用,不需要手動(dòng)創(chuàng)建。使用dispatch_get_global_queue函數(shù)獲得全局的并發(fā)隊(duì)列
備注:系統(tǒng)提供了兩個(gè)隊(duì)列,一個(gè)是MainDispatchQueue,一個(gè)是GlobalDispatchQueue。

  • 3.用戶(hù)隊(duì)列:

用函數(shù) dispatch_queue_create 創(chuàng)建的隊(duì)列. 這些隊(duì)列是串行的。正因?yàn)槿绱耍鼈兛梢杂脕?lái)完成同步機(jī)制。

幾個(gè)重要方法:

dispatch_once  它可以保證整個(gè)應(yīng)用程序生命周期中某段代碼只被執(zhí)行一次!
dispatch_after   幾秒鐘后執(zhí)行
dispatch_set_target_queue 設(shè)置一個(gè)dispatch queue的優(yōu)先級(jí)
dispatch_apply  執(zhí)行某個(gè)代碼片段若干次。
dispatch group Dispatch Group機(jī)制允許我們監(jiān)聽(tīng)一組任務(wù)是否完成
 dispatch_barrier_async 通過(guò)dispatch_barrier_async函數(shù)提交的任務(wù)會(huì)等它前面的任務(wù)執(zhí)行結(jié)束才開(kāi)始,然后它后面的任務(wù)必須等它執(zhí)行完畢才能開(kāi)始
Cocoa Operations#####

Cocoa Operations多線(xiàn)程技術(shù)主要有兩個(gè)類(lèi):NSOperationQueue與NSOperation。
NSOperation

一個(gè)operation就相當(dāng)于一個(gè)代碼塊,這里只是把它提高到一種任務(wù)的角度來(lái)看待,然后,任務(wù)便會(huì)有開(kāi)始執(zhí)行(start)、取消(cancel)、是否取消(isCancel)、是否完成(isFinishing)、暫停(pause)等狀態(tài)函數(shù),其本身是不會(huì)創(chuàng)建新的線(xiàn)程來(lái)執(zhí)行它的,NSOperation本身是抽象基類(lèi),因此必須使用它的子類(lèi),使用NSOperation子類(lèi)的方式有2種:1

  • Foundation框架提供了兩個(gè)具體子類(lèi)直接供我們使用:NSInvocationOperation和NSBlockOperation
    程安全與同步
  • 自定義子類(lèi)繼承NSOperation,實(shí)現(xiàn)內(nèi)部相應(yīng)的方法。

NSOperationQueue

OperationQueue實(shí)質(zhì)上也就是數(shù)組管理,對(duì)添加進(jìn)去的operation進(jìn)行管理、創(chuàng)建線(xiàn)程等;添加到queue里的operation,queue默認(rèn)會(huì)調(diào)用operation的start函數(shù)來(lái)執(zhí)行任務(wù),而start函數(shù)默認(rèn)又是調(diào)用main函數(shù)的。默認(rèn)是同步執(zhí)行的。

如果將NSOperation添加到NSOperationQueue(操作隊(duì)列)中,系統(tǒng)會(huì)自動(dòng)異步執(zhí)行NSOperation中的操作

添加操作到NSOperationQueue中,自動(dòng)執(zhí)行操作,自動(dòng)開(kāi)啟線(xiàn)程

使用步驟

NSOperation和NSOperationQueue實(shí)現(xiàn)多線(xiàn)程的具體步驟:
(1)先將需要執(zhí)行的操作封裝到一個(gè)NSOperation對(duì)象中
(2)然后將NSOperation對(duì)象添加到NSOperationQueue中
(3)系統(tǒng)會(huì)?動(dòng)將NSOperationQueue中的NSOperation取出來(lái)
(4)將取出的NSOperation封裝的操作放到?條新線(xiàn)程中執(zhí)?

第四部分 線(xiàn)程安全與同步

兩個(gè)線(xiàn)程同時(shí)修改同一資源有可能以意想不到的方式互相干擾。使我的程序發(fā)生問(wèn)題,iOS提 ??供了你可以使用的多個(gè)同步工具,從??供互斥訪(fǎng)問(wèn)你程序的有序的事件的工具等來(lái)解決線(xiàn)程間同步的問(wèn)題。
1.原子操作:

原子操作是同步的一個(gè)簡(jiǎn)單的形式,它處理簡(jiǎn)單的數(shù)據(jù)類(lèi)型。原子操作的優(yōu)勢(shì)是它們不妨礙競(jìng)爭(zhēng)的線(xiàn)程。對(duì)于簡(jiǎn)單的操作,比如遞增一個(gè)計(jì)數(shù)器,原子操作比使用鎖具有更高的性能優(yōu)勢(shì)。

2.條件信號(hào)量

條件是信號(hào)量的另外一個(gè)形式,它允許在條件為真的時(shí)候線(xiàn)程間互相發(fā)送信號(hào)。條件通常被使用來(lái)說(shuō)明資源可用性,或用來(lái)確保任務(wù)以特定的順序執(zhí)行。當(dāng)一個(gè)線(xiàn)程測(cè)試一個(gè)條件時(shí),它會(huì)被阻塞直到條件為真。它會(huì)一直阻塞直到其他線(xiàn)程顯
信號(hào)量的狀態(tài)。條件和互斥鎖(mutex lock)的區(qū)別在于多個(gè)線(xiàn)程被允許同時(shí)訪(fǎng)問(wèn)一個(gè)條件。條件更多是允許不同線(xiàn)程根據(jù)一些指定的標(biāo)準(zhǔn)通過(guò)的守門(mén)人。

3.鎖

鎖是最常用的同步工具。你可以是使用鎖來(lái)保護(hù)臨界區(qū)(critical section),這些代碼段在同一個(gè)時(shí)間只能允許被一個(gè)線(xiàn)程訪(fǎng)問(wèn)。比如,一個(gè)臨界區(qū)可能會(huì)操作一個(gè)特定的數(shù)據(jù)結(jié)構(gòu),或使用了每次只能一個(gè)客戶(hù)端訪(fǎng)問(wèn)的資源。


  • 使用POSIX互斥鎖

POSIX 互斥鎖在很多程序里面很容易使用。為了新建一個(gè)互斥鎖,你聲明并初始化一個(gè) pthread_mutex_t 的結(jié)構(gòu)。為了鎖住和解鎖一個(gè)互斥鎖,你可以使用pthread_mutex_lock 和 pthread_mutex_unlock 函數(shù)。列表 4-2 顯式了要初始化并使用一個(gè) POSIX 線(xiàn)程的互斥鎖的基礎(chǔ)代碼。當(dāng)你用完一個(gè)鎖之后,只要簡(jiǎn)單的調(diào)用pthread_mutex_destroy 來(lái)釋放該鎖的數(shù)據(jù)結(jié)構(gòu)。

pthread_mutex_t mutex;
void MyInitFunction()
{
pthread_mutex_init(&mutex, NULL);
}
void MyLockingFunction()
{
pthread_mutex_lock(&mutex);
// Do work.
pthread_mutex_unlock(&mutex);
  • NSLock鎖

在 Cocoa 程序中 NSLock 中實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的互斥鎖。所有鎖(包括 NSLock)的接口實(shí)際上都是通過(guò) NSLocking 協(xié)議定義的,它定義了 lock 和 unlock 方法。你使用這些方法來(lái)獲取和釋放該鎖。

除了標(biāo)準(zhǔn)的鎖行為,NSLock 類(lèi)還增加了 tryLock 和 lockBeforeDate:方法。方法tryLock 試圖獲取一個(gè)鎖,但是如果鎖不可用的時(shí)候,它不會(huì)阻塞線(xiàn)程。相反,它只是返回 NO。而 lockBeforeDate:方法試圖獲取一個(gè)鎖,但是如果鎖沒(méi)有在規(guī)定的時(shí)間內(nèi)被獲得,它會(huì)讓線(xiàn)程從阻塞狀態(tài)變?yōu)榉亲枞麪顟B(tài)(或者返回 NO)。

BOOL moreToDo = YES;
NSLock *theLock = [[NSLock alloc] init];
...
while (moreToDo) {
/* Do another increment of calculation */
/* until there’s no more to do. */
if ([theLock tryLock]) {
/* Update display used by all threads. */
[theLock unlock];
}
  • 使用@synchronized指令

@synchronized 指令是在 Objective-C 代碼中創(chuàng)建一個(gè)互斥鎖非常方便的方法。@synchronized 指令做和其他互斥鎖一樣的工作(它防止不同的線(xiàn)程在同一時(shí)間獲取同一個(gè)鎖)。然而在這種情況下,你不需要直接創(chuàng)建一個(gè)互斥鎖或鎖對(duì)象。相反,你只需要簡(jiǎn)單的使用 Objective-C 對(duì)象作為鎖的令牌,如下面例子所示:

-(void)myMethod:(id)anObj
{
@synchronized(anObj)
{
// Everything between the braces is protected by the @synchronized directive.
}

作為一種預(yù)防措施,@synchronized 塊隱式的添加一個(gè)異常處理例程來(lái)保護(hù)代碼。該處理例程會(huì)在異常拋出的時(shí)候自動(dòng)的釋放互斥鎖。這意味著為了使用@synchronized 指令,你必須在你的代碼中啟用異常處理。了如果你不想讓隱式的異常處理例程帶來(lái)額外的開(kāi)銷(xiāo),你應(yīng)該考慮使用鎖的類(lèi)。

  • 其他鎖
    NSRecursiveLock 遞歸鎖

NSRecursiveLock 類(lèi)定義的鎖可以在同一線(xiàn)程多次獲得,而不會(huì)造成死鎖。一個(gè)遞歸鎖會(huì)跟蹤它被多少次成功獲得了。每次成功的獲得該鎖都必須平衡調(diào)用鎖住和解鎖的操作。只有所有的鎖住和解鎖操作都平衡的時(shí)候,鎖才真正被釋放給其他線(xiàn)程獲得。
正如它名字所言,這種類(lèi)型的鎖通常被用在一個(gè)遞歸函數(shù)里面來(lái)防止遞歸造成阻塞線(xiàn)程。你可以類(lèi)似的在非遞歸的情況下使用他來(lái)調(diào)用函數(shù),這些函數(shù)的語(yǔ)義要求它們使用鎖。以下是一個(gè)簡(jiǎn)單遞歸函數(shù),它在遞歸中獲取鎖。如果你不在該代碼里使用NSRecursiveLock 對(duì)象,當(dāng)函數(shù)被再次調(diào)用的時(shí)候線(xiàn)程將會(huì)出現(xiàn)死鎖。
NSConditionLock
NSConditionLock 對(duì)象定義了一個(gè)互斥鎖,可以使用特定值來(lái)鎖住和解鎖。不要把該類(lèi)型的鎖和條件混淆了。它的行為和條件有點(diǎn)類(lèi)似,但是它們的實(shí)現(xiàn)非常不同。
通常,當(dāng)多線(xiàn)程需要以特定的順序來(lái)執(zhí)行任務(wù)的時(shí)候,你可以使用一個(gè)NSConditionLock 對(duì)象,比如當(dāng)一個(gè)線(xiàn)程生產(chǎn)數(shù)據(jù),而另外一個(gè)線(xiàn)程消費(fèi)數(shù)據(jù)。生產(chǎn)者執(zhí)行時(shí),消費(fèi)者使用由你程序指定的條件來(lái)獲取鎖(條件本身是一個(gè)你定義的整形值)。當(dāng)生產(chǎn)者完成時(shí),它會(huì)解鎖該鎖并設(shè)置鎖的條件為合適的整形值來(lái)喚醒消費(fèi)者線(xiàn)程,之后消費(fèi)線(xiàn)程繼續(xù)處理數(shù)據(jù)。

id condLock = [[NSConditionLock alloc] initWithCondition:NO_DATA];
while(true)
{
[condLock lock];
[condLock unlockWithCondition:HAS_DATA];
}

4. 執(zhí)行Selector例程
Cocoa 程序包含了一個(gè)在一個(gè)線(xiàn)程以同步的方式傳遞消息的方便方法。NSObject類(lèi)聲明方法來(lái)在應(yīng)用的一個(gè)活動(dòng)線(xiàn)程上面執(zhí)行 selector 的方法。這些方法允許你的線(xiàn)程以異步的方式來(lái)傳遞消息,以確保它們?cè)谕粋€(gè)線(xiàn)程上面執(zhí)行是同步的。比如,你可以通過(guò)執(zhí)行 selector 消息來(lái)把一個(gè)從你分布計(jì)算的結(jié)果傳遞給你的應(yīng)用的主線(xiàn)程或其他目標(biāo)線(xiàn)程。每個(gè)執(zhí)行 selector 的請(qǐng)求都會(huì)被放入一個(gè)目標(biāo)線(xiàn)程的 run loop的隊(duì)列里面,然后請(qǐng)求會(huì)按照它們到達(dá)的順序被目標(biāo)線(xiàn)程有序的處理。
5.內(nèi)存屏障和 Volatile 變量

內(nèi)存屏障(memory barrier)是一個(gè)使用來(lái)確保內(nèi)存操作按照正確的順序工作的非阻塞的同步工具。內(nèi)存屏障的作用就像一個(gè)柵欄,迫使處理器來(lái)完成位于障礙前面的任何加載和存儲(chǔ)操作,才允許它執(zhí)行位于屏障之后的加載和存儲(chǔ)操作。內(nèi)存屏障同樣使用來(lái)確保一個(gè)線(xiàn)程(但對(duì)另外一個(gè)線(xiàn)程可見(jiàn))的內(nèi)存操作總是按照預(yù)定的順序完成。如果在這些地方缺少內(nèi)存屏障有可能讓其他線(xiàn)程看到看似不可能的結(jié)果(比如,內(nèi)存屏障的維基百科條目)。為了使用一個(gè)內(nèi)存屏障,你只要在你代碼里面需要的地方簡(jiǎn)單的調(diào)用 OSMemoryBarrier 函數(shù)。

Volatile 變量適用于獨(dú)立變量的另一個(gè)內(nèi)存限制類(lèi)型。編譯器優(yōu)化代碼通過(guò)加載這些變量的值進(jìn)入寄存器。對(duì)于本地變量,這通常不會(huì)有什么問(wèn)題。但是如果一個(gè)變量對(duì)另外一個(gè)線(xiàn)程可見(jiàn),那么這種優(yōu)化可能會(huì)阻止其他線(xiàn)程發(fā)現(xiàn)變量的任何變化。在變量之前加上關(guān)鍵字 volatile 可以強(qiáng)制編譯器每次使用變量的時(shí)候都從內(nèi)存里面加載。如果一個(gè)變量的值隨時(shí)可能給編譯器無(wú)法檢測(cè)的外部源更改,那么你可以把該變量聲明為 volatile 變量。

因?yàn)閮?nèi)存屏障和 volatile 變量降低了編譯器可執(zhí)行的優(yōu)化,因此你應(yīng)該謹(jǐn)慎使用它們,只在有需要的地方時(shí)候,以確保正確性。關(guān)于更多使用內(nèi)存屏障的信息,參閱 OSMemoryBarrier 主頁(yè)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,034評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,327評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,554評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,337評(píng)論 6 404
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 54,883評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評(píng)論 3 439
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,114評(píng)論 0 286
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,625評(píng)論 1 332
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,555評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,737評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,973評(píng)論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,362評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,615評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,343評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,699評(píng)論 2 370

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