iOS線程學(xué)習(xí)筆記

文字源自對以下文章的摘抄:

  1. threading-programming-guide筆記一
  2. threading-programming-guide筆記二
  3. threading-programming-guide筆記三
  4. threading-programming-guide筆記四

感謝原作者。
這里摘抄眉睹,只為學(xué)習(xí)目的迟郎,以便日后再復(fù)習(xí)。

一、OS X和iOS中提供的不那么底層的實現(xiàn)多任務(wù)并發(fā)執(zhí)行的解決方案:

Operation object:該技術(shù)出現(xiàn)在OS X 10.5中善绎,通過將要執(zhí)行的任務(wù)封裝成操作對象的方式實現(xiàn)任務(wù)在多線程中執(zhí)行旗扑。任務(wù)可以理解為你要想執(zhí)行的一段代碼。在這個操作對象中不光包含要執(zhí)行的任務(wù)曼库,還包含線程管理的內(nèi)容区岗,使用時通常與操作隊列對象聯(lián)合使用,操作隊列對象會管理操作對象如何使用線程毁枯,所以我們只需要關(guān)心要執(zhí)行的任務(wù)本身即可慈缔。

  • GCD:該技術(shù)出現(xiàn)在OS X 10.6中,它與Operation Object的初衷類似种玛,就是讓開發(fā)者只關(guān)注要執(zhí)行的任務(wù)本身藐鹤,而不需要去關(guān)注線程的管理。你只需要創(chuàng)建好任務(wù)赂韵,然后將任務(wù)添加到一個工作隊列里即可娱节,該工作隊列會根據(jù)當(dāng)前CPU性能及內(nèi)核的負(fù)載情況,將任務(wù)安排到合適的線程中去執(zhí)行祭示。

  • Idle-time notification:該技術(shù)主要用于處理優(yōu)先級相對比較低肄满、執(zhí)行時間比較短的任務(wù),讓應(yīng)用程序在空閑的時候執(zhí)行這類任務(wù)质涛。Cocoa框架提供NSNotificationQueue對象處理空閑時間通知悄窃,通過使用NSPostWhenIdle選項,向隊列發(fā)送空閑時間通知的請求蹂窖。

  • Asynchronous functions:系統(tǒng)中有一些支持異步的函數(shù)轧抗,可以自動讓你的代碼并行執(zhí)行。這些異步函數(shù)可能通過應(yīng)用程序的守護進(jìn)程或者自定義的線程執(zhí)行你的代碼瞬测,與主進(jìn)程或主線程分離横媚,達(dá)到并行執(zhí)行任務(wù)的功能纠炮。

  • Timers:我們也可以在應(yīng)用程序主線程中使用定時器去執(zhí)行一些比較輕量級的、有一定周期性的任務(wù)灯蝴。

  • Separate processes:雖然通過另起一個進(jìn)程比線程更加重量級恢口,但是在某些情況下要比使用線程更好一些,比如你需要的執(zhí)行的任務(wù)和你的應(yīng)用程序在展現(xiàn)數(shù)據(jù)和使用方面沒有什么關(guān)系穷躁,但是可以優(yōu)化你的應(yīng)用程序的運行環(huán)境耕肩,或者提高應(yīng)用程序獲取數(shù)據(jù)的效率等。

在應(yīng)用程序?qū)用嫖侍叮还苁鞘裁雌脚_猿诸,線程的運行方式都是大體相同的,在線程的運行過程中一般都會經(jīng)歷三種狀態(tài)狡忙,即運行中梳虽、準(zhǔn)備運行、阻塞灾茁。

二窜觉、RunLoop

參考:threading-programming-guide筆記三

簡單的來說,RunLoop用于管理和監(jiān)聽異步添加到線程中的事件北专,當(dāng)有事件輸入時禀挫,系統(tǒng)喚醒線程并將事件分派給RunLoop,當(dāng)沒有需要處理的事件時拓颓,RunLoop會讓線程進(jìn)入休眠狀態(tài)语婴。這樣就能讓線程常駐在進(jìn)程中,而不會過多的消耗系統(tǒng)資源录粱,達(dá)到有事做事,沒事睡覺的效果画拾。

Run Loop在線程中的主要作用就是幫助線程常駐在進(jìn)程中啥繁,并且不會過多消耗資源。所以說Run Loop在二級線程中也不是必須需要的青抛,要根據(jù)該線程執(zhí)行的任務(wù)類型以及在整個應(yīng)用中擔(dān)任何作用而決定是否需要使用Run Loop旗闽。比如說,如果你創(chuàng)建一個二級線程只是為了執(zhí)行一個不會頻繁執(zhí)行的一次性任務(wù)蜜另,或者需要執(zhí)行很長時間的任務(wù)适室,那么可能就不需要使用Run Loop了。如果你需要一個線程執(zhí)行周期性的定時任務(wù)举瑰,或者需要較為頻繁的與主線程之間進(jìn)行交互捣辆,那么就需要使用Run Loop。

使用Run Loop的情況大概有以下四點:

  • 通過基于端口或自定義的數(shù)據(jù)源與其他線程進(jìn)行交互此迅。
  • 在線程中執(zhí)行定時事件源的任務(wù)汽畴。
  • 使用Cocoa框架提供的performSelector…系列方法旧巾。
  • 在線程中執(zhí)行較為頻繁的,具有周期性的任務(wù)忍些。
Run Loop對象

要想操作配置Run Loop鲁猩,那自然需要通過Run Loop對象來完成,它提供了一系列接口罢坝,可幫助我們便捷的添加Input sources廓握、timers以及觀察者。較高級別的Cocoa框架提供了NSRunLoop類嘁酿,較底層級別的Core Foundation框架提供了指向CFRunloopRef的指針隙券。

獲取Run Loop對象

在Cocoa和Core Foundation框架中都沒有提供創(chuàng)建Run Loop的方法,只有從當(dāng)前線程獲取Run Loop的方法:

  • 在Cocoa框架中痹仙,NSRunLoop類提供了類方法currentRunLoop()獲取NSRunLoop對象是尔。
    該方法是獲取當(dāng)前線程中已存在的Run Loop,如果不存在开仰,那其實還是會創(chuàng)建一個Run Loop對象返回拟枚,只是Cocoa框架沒有向我們暴露該接口。
  • 在Core Foundation框架中提供了CFRunLoopGetCurrent()函數(shù)獲取CFRunLoop對象

雖然這兩個Run Loop對象并不完全等價众弓,它們之間還是可以轉(zhuǎn)換的恩溅,我們可以通過NSRunLoop對象提供的getCFRunLoop()方法獲取CFRunLoop對象。因為NSRunLoop和CFRunLoop指向的都是當(dāng)前線程中同一個Run Loop谓娃,所以在使用時它們可以混用脚乡,比如說要給Run Loop添加觀察者時就必須得用CFRunLoop了。

配置Run Loop觀察者

可以向Run Loop中添加各種事件源和觀察者滨达,這里事件源是必填項奶稠,也就是說Run Loop中至少要有一種事件源,不論是Input source還是timer捡遍,如果Run Loop中沒有事件源的話锌订,那么在啟動Run Loop后就會立即退出。而觀察者是可選項画株,如果沒有監(jiān)控Run Loop各運行狀態(tài)的需求辆飘,可以不配置觀察者。

啟動Run Loop

在啟動Run Loop前務(wù)必要保證已添加一種類型的事件源谓传。在Cocoa框架和Core Foundation框架中啟動Run Loop大體有三種形式蜈项,分別是無條件啟動、設(shè)置時間限制啟動续挟、指定特定模式啟動紧卒。

1.無條件啟動

NSRunLoop對象的run()方法和Core Foundation框架中的CFRunLoopRun()函數(shù)都是無條件啟動Run Loop的方式。這種方式雖然是最簡單的啟動方式诗祸,但也是最不推薦使用的一個方式常侦,因為這種方式將Run Loop置于一個永久運行并且不可控的狀態(tài)浇冰,它使Run Loop只能在默認(rèn)模式下運行,無法給Run Loop設(shè)置特定的或自定義的模式聋亡,而且以這種模式啟動的Run Loop只能通過CFRunLoopStop(_ rl: CFRunLoop!)函數(shù)強制停止肘习。

2.設(shè)置時間限制啟動

該方式對應(yīng)的方法是NSRunLoop對象的runUntilDate(_ limitDate: NSDate)方法,在啟動Run Loop時設(shè)置超時時間坡倔,一旦超時那么Run Loop則自動退出漂佩。該方法的好處是可以在循環(huán)中反復(fù)啟動Run Loop處理相關(guān)任務(wù),而且可控制運行時長罪塔。

3.指定特定模式啟動

該方式對應(yīng)的方法是NSRunLoop對象的runMode(_ mode: String, beforeDate limitDate: NSDate)方法和Core Foundation框架的CFRunLoopRunInMode(_ mode: CFString!, _ seconds: CFTimeInterval, _ returnAfterSourceHandled: Bool)函數(shù)投蝉。前者有兩個參數(shù),第一個參數(shù)是Run Loop模式征堪,第二個參數(shù)仍然是超時時間瘩缆,該方法使Run Loop只處理指定模式中的事件源事件,當(dāng)處理完事件或超時Run Loop會退出佃蚜,該方法的返回值類型是Bool庸娱,如果返回true則表示Run Loop啟動成功,并分派執(zhí)行了任務(wù)或者達(dá)到超時時間谐算,若返回false則表示Run Loop啟動失敗熟尉。后者有三個參數(shù),前兩個參數(shù)的作用一樣洲脂,第三個參數(shù)的意思是Run Loop是否在執(zhí)行完任務(wù)后就退出斤儿,如果設(shè)置為false,那么代表Run Loop在執(zhí)行完任務(wù)后不退出恐锦,而是一直等到超時后才退出往果。該方法返回Run Loop的退出狀態(tài):

  • CFRunLoopRunResult.Finished:表示Run Loop已分派執(zhí)行完任務(wù),并且再無任務(wù)執(zhí)行的情況下退出一铅。
  • CFRunLoopRunResult.Stopped:表示Run Loop通過CFRunLoopStop(_ rl: CFRunLoop!)函數(shù)強制退出陕贮。
  • CFRunLoopRunResult.TimedOut:表示Run Loop因為超時時間到而退出。
  • CFRunLoopRunResult.HandledSource:表示Run Loop已執(zhí)行完任務(wù)而退出馅闽,改狀態(tài)只有在returnAfterSourceHandled設(shè)置為true時才會出現(xiàn)飘蚯。
退出Run Loop

退出Run Loop的方式總體來說有三種:

  • 啟動Run Loop時設(shè)置超時時間馍迄。
  • 強制退出Run Loop福也。
  • 移除Run Loop中的事件源,從而使Run Loop退出攀圈。

第一種方式是推薦使用的方式暴凑,因為可以給Run Loop設(shè)置可控的運行時間,讓它執(zhí)行完所有的任務(wù)以及給觀察者發(fā)送通知赘来。第二種強制退出Run Loop主要是應(yīng)對無條件啟動Run Loop的情況现喳。第三種方式是最不推薦的方式凯傲,雖然在理論上說當(dāng)Run Loop中沒有任何數(shù)據(jù)源時會立即退出,但是在實際情況中我們創(chuàng)建的二級線程除了執(zhí)行我們指定的任務(wù)外嗦篱,有可能系統(tǒng)還會讓其執(zhí)行一些系統(tǒng)層面的任務(wù)冰单,而且這些任務(wù)我們一般無法知曉,所以用這種方式退出Run Loop往往會存在延遲退出灸促。

Run Loop對象的線程安全性

Run Loop對象的線程安全性取決于我們使用哪種API去操作诫欠。Core Foundation框架中的CFRunLoop對象是線程安全的,我們可以在任何線程中使用浴栽。Cocoa框架的NSRunLoop對象是線程不安全的荒叼,我們必須在擁有Run Loop的當(dāng)前線程中操作Run Loop,如果操作了不屬于當(dāng)前線程的Run loop典鸡,會導(dǎo)致異常和各種潛在的問題發(fā)生被廓。

自定義Run Loop事件源

Cocoa框架因為是較為高層的框架,所以沒有提供操作較為底層的Run Loop事件源相關(guān)的接口和對象萝玷,所以我們只能使用Core Foundation框架中的對象和函數(shù)創(chuàng)建事件源并給Run Loop設(shè)置事件源嫁乘。

1.創(chuàng)建Run Loop事件源對象
創(chuàng)建事件源的方法:

func CFRunLoopSourceCreate(_ allocator: CFAllocator!, _ order: CFIndex, _ context: UnsafeMutablePointer<CFRunLoopSourceContext>) -> CFRunLoopSource!

  1. allocator:該參數(shù)為對象內(nèi)存分配器,一般使用默認(rèn)的分配器kCFAllocatorDefault间护。
  2. order:事件源優(yōu)先級亦渗,當(dāng)Run Loop中有多個接收相同事件的事件源被標(biāo)記為待執(zhí)行時,那么就根據(jù)該優(yōu)先級判斷汁尺,0為最高優(yōu)先級別法精。
  3. context:事件源上下文。

Run Loop事件源上下文很重要痴突,我們來看看它的結(jié)構(gòu):

struct CFRunLoopSourceContext { 
    var version: CFIndex 
    var info: UnsafeMutablePointer<Void> 
    var retain: ((UnsafePointer<Void>) -> UnsafePointer<Void>)! 
    var release: ((UnsafePointer<Void>) -> Void)! 
    var copyDescription: ((UnsafePointer<Void>) -> Unmanaged<CFString>!)! 
    var equal: ((UnsafePointer<Void>, UnsafePointer<Void>) -> DarwinBoolean)! 
    var hash: ((UnsafePointer<Void>) -> CFHashCode)! 
    var schedule: ((UnsafeMutablePointer<Void>, CFRunLoop!, CFString!) -> Void)! 
    var cancel: ((UnsafeMutablePointer<Void>, CFRunLoop!, CFString!) -> Void)! 
    var perform: ((UnsafeMutablePointer<Void>) -> Void)! 
    init() 
    init(version version: CFIndex, info info: UnsafeMutablePointer<Void>, retain retain: ((UnsafePointer<Void>) -> UnsafePointer<Void>)!, release release: ((UnsafePointer<Void>) -> Void)!, copyDescription copyDescription: ((UnsafePointer<Void>) -> Unmanaged<CFString>!)!, equal equal: ((UnsafePointer<Void>, UnsafePointer<Void>) -> DarwinBoolean)!, hash hash: ((UnsafePointer<Void>) -> CFHashCode)!, schedule schedule: ((UnsafeMutablePointer<Void>, CFRunLoop!, CFString!) -> Void)!, cancel cancel: ((UnsafeMutablePointer<Void>, CFRunLoop!, CFString!) -> Void)!, perform perform: ((UnsafeMutablePointer<Void>) -> Void)!) 
}
  1. version:事件源上下文的版本搂蜓,必須設(shè)置為0。
  2. info:上下文中retain辽装、release帮碰、copyDescription、equal拾积、hash殉挽、schedule、cancel拓巧、perform這八個回調(diào)函數(shù)所有者對象的指針斯碌。
  3. schedule:該回調(diào)函數(shù)的作用是將該事件源與給它發(fā)送事件消息的線程進(jìn)行關(guān)聯(lián),也就是說如果主線程想要給該事件源發(fā)送事件消息肛度,那么首先主線程得能獲取到該事件源傻唾。
  4. cancel:該回調(diào)函數(shù)的作用是使該事件源失效。
  5. perform:該回調(diào)函數(shù)的作用是執(zhí)行其他線程或當(dāng)前線程給該事件源發(fā)來的事件消息承耿。
將事件源添加至Run Loop

事件源創(chuàng)建好之后冠骄,接下來就是將其添加到指定某個模式的Run Loop中伪煤,我們來看看這個方法:

func CFRunLoopAddSource(_ rl: CFRunLoop!, _ source: CFRunLoopSource!, _ mode: CFString!)

  1. rl:希望添加事件源的Run Loop對象,類型是CFRunLoop凛辣。
  2. source:我們創(chuàng)建好的事件源抱既。
  3. mode:Run Loop的模式。
標(biāo)記事件源及喚醒Run Loop
func CFRunLoopSourceSignal(_ source: CFRunLoopSource!)

func CFRunLoopWakeUp(_ rl: CFRunLoop!)

這里需要注意的是喚醒Run Loop并不等價與啟動Run Loop扁誓,因為啟動Run Loop時需要對Run Loop進(jìn)行模式蝙砌、時限的設(shè)置,而喚醒Run Loop只是當(dāng)已啟動的Run Loop休眠時重新讓其運行跋理。

三择克、線程的資源消耗

線程的資源消耗主要分為三類,一類是內(nèi)存空間的消耗前普、一類是創(chuàng)建線程消耗的時間肚邢、另一類是對開發(fā)人員開發(fā)成本的消耗。

內(nèi)存空間的消耗又分為兩部分拭卿,一部分是內(nèi)核內(nèi)存空間骡湖,另一部分是應(yīng)用程序使用的內(nèi)存空間,每個線程在創(chuàng)建時就會申請這兩部分的內(nèi)存空間峻厚。申請內(nèi)核內(nèi)存空間是用來存儲管理和協(xié)調(diào)線程的核心數(shù)據(jù)結(jié)構(gòu)的响蕴,而申請應(yīng)用程序的內(nèi)存空間是用來存儲線程棧和一些初始化數(shù)據(jù)的。對于用戶級別的二級線程來說惠桃,對應(yīng)用程序內(nèi)存空間的消耗是可以配置的浦夷,比如線程棧的空間大小等。

下面是兩種內(nèi)存空間通常的消耗情況:

  • 內(nèi)核內(nèi)存空間:主要存儲線程的核心數(shù)據(jù)結(jié)構(gòu)辜王,每個線程大約會占用1KB的空間劈狐。
  • 應(yīng)用程序內(nèi)存空間:主要存儲線程棧和初始化數(shù)據(jù),主線程在OS X中大約占8MB空間呐馆,在iOS中大約占1MB肥缔。二級線程在兩種系統(tǒng)中通常占大約512KB,但是上面提到二級線程在這塊是可以配置的汹来,所以可配置的最小空間為16KB续膳,而且配置的空間大小必須是4KB的倍數(shù)。

注意:二級線程在創(chuàng)建時只是申請了內(nèi)存程序空間收班,但還并沒有真正分配給二級線程坟岔,只有當(dāng)二級線程執(zhí)行代碼需要空間時才會真正分配。

四闺阱、創(chuàng)建線程

說到創(chuàng)建線程炮车,就得說說線程的兩種類型舵变,Joinable和Detach酣溃。Joinable類型的線程可以被其他線程回收其資源和終止瘦穆。舉個例子,如果一個Joinable的線程與主線程結(jié)合赊豌,那么當(dāng)主線程準(zhǔn)備結(jié)束而該二級線程還沒有結(jié)束的時候扛或,主線程會被阻塞等待該二級線程,當(dāng)二級線程結(jié)束后由主線程回收其占用資源并將其關(guān)閉碘饼。如果在主線程還沒有結(jié)束時熙兔,該二級線程結(jié)束了,那么它不但不會關(guān)閉艾恼,而且資源也不會被系統(tǒng)收回住涉,只是等待主線程處理。而Detach的線程則相反钠绍,會自行結(jié)束關(guān)閉線程并且有系統(tǒng)回收其資源舆声。

五、線程屬性配置

線程也是具有若干屬性的柳爽,自然一些屬性也是可配置的媳握,在啟動線程之前我們可以對其進(jìn)行配置,比如線程占用的內(nèi)存空間大小磷脯、線程持久層中的數(shù)據(jù)蛾找、設(shè)置線程類型、優(yōu)先級等赵誓。

1打毛、 配置線程的棧空間大小

  • Cocoa框架:在OS X v10.5之后的版本和iOS2.0之后的版本中俩功,我們可以通過修改NSThread類的stackSize屬性隘冲,改變二級線程的線程棧大小,不過這里要注意的是該屬性的單位是字節(jié)绑雄,并且設(shè)置的大小必須得是4KB的倍數(shù)展辞。
  • POSIX API:通過pthread_attr_- setstacksize函數(shù)給線程屬性pthread_attr_t結(jié)構(gòu)體設(shè)置線程棧大小,然后在使用pthread_create函數(shù)創(chuàng)建線程時將線程屬性傳入即可万牺。

注意:在使用Cocoa框架的前提下修改線程棧時罗珍,不能使用NSThread的detachNewThreadSelector: toTarget:withObject:方法,因為上文中說過脚粟,該方法先創(chuàng)建線程覆旱,即刻便啟動了線程,所以根本沒有機會修改線程屬性核无。

2扣唱、配置線程存儲字典

每一個線程,在整個生命周期里都會有一個字典,以key-value的形式存儲著在線程執(zhí)行過程中你希望保存下來的各種類型的數(shù)據(jù)噪沙,比如一個常駐線程的運行狀態(tài)炼彪,線程可以在任何時候訪問該字典里的數(shù)據(jù)。

在Cocoa框架中正歼,可以通過NSThread類的threadDictionary屬性辐马,獲取到NSMutableDictionary類型對象,然后自定義key值局义,存入任何里先儲存的對象或數(shù)據(jù)喜爷。如果使用POSIX線程,可以使用pthread_setspecific和pthread_getspecific函數(shù)設(shè)置獲取線程字典萄唇。

3檩帐、配置線程類型

在上文中提到過,線程有Joinable和Detached類型另萤,大多數(shù)非底層的線程默認(rèn)都是Detached類型的轿塔,相比Joinable類型的線程來說薛匪,Detached類型的線程不用與其他線程結(jié)合捶朵,并且在執(zhí)行完任務(wù)后可自動被系統(tǒng)回收資源,而且主線程不會因此而阻塞脉课,這著實要方便許多目养。

使用NSThread創(chuàng)建的線程默認(rèn)都是Detached類型俩由,而且似乎也不能將其設(shè)置為Joinable類型。而使用POSIX API創(chuàng)建的線程則默認(rèn)為Joinable類型癌蚁,而且這也是唯一創(chuàng)建Joinable類型線程的方式幻梯。通過POSIX API可以在創(chuàng)建線程前通過函數(shù)pthread_attr_setdetachstate更新線程屬性,將其設(shè)置為不同的類型努释,如果線程已經(jīng)創(chuàng)建碘梢,那么可以使用pthread_detach函數(shù)改變其類型。Joinable類型的線程還有一個特性伐蒂,那就是在終止之前可以將數(shù)據(jù)傳給與之相結(jié)合的線程煞躬,從而達(dá)到線程之間的交互。即將要終止的線程可以通過pthread_exit函數(shù)傳遞指針或者任務(wù)執(zhí)行的結(jié)果逸邦,然后與之結(jié)合的線程可以通過pthread_join函數(shù)接受數(shù)據(jù)恩沛。

雖然通過POSIX API創(chuàng)建的線程使用和管理起來較為復(fù)雜和麻煩,但這也說明這種方式更為靈活缕减,更能滿足不同的使用場景和需求雷客。比如當(dāng)執(zhí)行一些關(guān)鍵的任務(wù),不能被打斷的任務(wù)桥狡,像執(zhí)行I/O操作之類搅裙。

4皱卓、 設(shè)置線程優(yōu)先級

不論是通過NSThread創(chuàng)建線程還是通過POSIX API創(chuàng)建線程,他們都提供了設(shè)置線程優(yōu)先級的方法部逮。我們可以通過NSThread的類方法setThreadPriority:設(shè)置優(yōu)先級娜汁,因為線程的優(yōu)先級由0.0~1.0表示,所以設(shè)置優(yōu)先級時也一樣甥啄。我們也可以通過pthread_setschedparam函數(shù)設(shè)置線程優(yōu)先級。

注意:設(shè)置線程的優(yōu)先級時可以在線程運行時設(shè)置炬搭。

雖然我們可以調(diào)節(jié)線程的優(yōu)先級蜈漓,但不到必要時還是不建議調(diào)節(jié)線程的優(yōu)先級。因為一旦調(diào)高了某個線程的優(yōu)先級宫盔,與低優(yōu)先級線程的優(yōu)先等級差距太大融虽,就有可能導(dǎo)致低優(yōu)先級線程永遠(yuǎn)得不到運行的機會,從而產(chǎn)生性能瓶頸灼芭。比如說有兩個線程A和B有额,起初優(yōu)先級相差無幾,那么在執(zhí)行任務(wù)的時候都會相繼無序的運行彼绷,如果將線程A的優(yōu)先級調(diào)高巍佑,并且當(dāng)線程A不會因為執(zhí)行的任務(wù)而阻塞時,線程B就可能一直不能運行寄悯,此時如果線程A中執(zhí)行的任務(wù)需要與線程B中任務(wù)進(jìn)行數(shù)據(jù)交互萤衰,而遲遲得不到線程B中的結(jié)果,此時線程A就會被阻塞猜旬,那么程序的性能自然就會產(chǎn)生瓶頸脆栋。

六、參考:

  1. Threading Programming Guide
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末洒擦,一起剝皮案震驚了整個濱河市椿争,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌熟嫩,老刑警劉巖秦踪,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異掸茅,居然都是意外死亡洋侨,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門倦蚪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來希坚,“玉大人,你說我怎么就攤上這事陵且〔蒙” “怎么了个束?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長聊疲。 經(jīng)常有香客問我茬底,道長,這世上最難降的妖魔是什么获洲? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任阱表,我火速辦了婚禮,結(jié)果婚禮上贡珊,老公的妹妹穿的比我還像新娘最爬。我一直安慰自己,他們只是感情好门岔,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布爱致。 她就那樣靜靜地躺著,像睡著了一般寒随。 火紅的嫁衣襯著肌膚如雪糠悯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天妻往,我揣著相機與錄音互艾,去河邊找鬼。 笑死讯泣,一個胖子當(dāng)著我的面吹牛忘朝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播判帮,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼局嘁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了晦墙?” 一聲冷哼從身側(cè)響起悦昵,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎晌畅,沒想到半個月后但指,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡抗楔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年棋凳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片连躏。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡剩岳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出入热,到底是詐尸還是另有隱情拍棕,我是刑警寧澤晓铆,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站绰播,受9級特大地震影響骄噪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蠢箩,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一链蕊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谬泌,春花似錦滔韵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跨跨。三九已至潮峦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間勇婴,已是汗流浹背忱嘹。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留耕渴,地道東北人拘悦。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像橱脸,于是被迫代替她去往敵國和親础米。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

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