iOS 如何高效的使用多線程

寫在前面

多線程技術(shù)在移動(dòng)端開發(fā)中應(yīng)用廣泛,GCD 讓 iOS 開發(fā)者能輕易的使用多線程凭语,然而這并不意味著代碼就一定高效和可靠蛾绎。深入理解其原理并經(jīng)常結(jié)合業(yè)務(wù)思考,才能在有限的線程控制 API 中最大化發(fā)揮并發(fā)編程的能力关斜,也能輕易的察覺到代碼可能存在的安全問題并優(yōu)雅的解決它示括。

本文不會(huì)講解 GCD 和各種“鎖”的基本用法,而是結(jié)合操作系統(tǒng)的一些知識(shí)和筆者的認(rèn)識(shí)講述偏“思維”的東西痢畜,當(dāng)然垛膝,最終也是為了能更高效的應(yīng)用多線程鳍侣。

行文可能有誤歡迎指出錯(cuò)誤。

一吼拥、多線程簡述

線程是程序執(zhí)行流的最小單元倚聚,一個(gè)線程包括:獨(dú)有ID,程序計(jì)數(shù)器 (Program Counter)凿可,寄存器集合惑折,堆棧。同一進(jìn)程可以有多個(gè)線程枯跑,它們共享進(jìn)程的全局變量和堆數(shù)據(jù)惨驶。

這里的 PC (Program Counter) 指向即將要執(zhí)行的下一條指令,通過 PC 的更新來運(yùn)行我們的程序敛助,一個(gè)線程同一時(shí)刻只能執(zhí)行一條指令粗卜。當(dāng)然我們知道線程和進(jìn)程都是虛擬的概念,實(shí)際上 PC 是 CPU 核心中的寄存器纳击,它是實(shí)際存在的续扔,所以也可以說一個(gè) CPU 核心同一時(shí)刻只能執(zhí)行一個(gè)線程。

不管是多處理器設(shè)備還是多核設(shè)備焕数,開發(fā)者往往只需要關(guān)心 CPU 的核心數(shù)量纱昧,而不需關(guān)心它們的物理構(gòu)成。CPU 核心數(shù)量是有限的堡赔,也就是說一個(gè)設(shè)備并發(fā)執(zhí)行的線程數(shù)量是有限的识脆,當(dāng)線程數(shù)量超過 CPU 核心數(shù)量時(shí),一個(gè) CPU 核心往往就要處理多個(gè)線程善已,這個(gè)行為叫做線程調(diào)度存璃。

線程調(diào)度簡單來說就是:一個(gè) CPU 核心輪流讓各個(gè)線程分別執(zhí)行一段時(shí)間。當(dāng)然這中間還包含著復(fù)雜的邏輯雕拼,后文再來分析纵东。

二、多線程的優(yōu)化思路

在移動(dòng)端開發(fā)中啥寇,因?yàn)橄到y(tǒng)的復(fù)雜性偎球,開發(fā)者往往不能期望所有線程都能真正的并發(fā)執(zhí)行,而且開發(fā)者也不清楚 XNU 何時(shí)切換內(nèi)核態(tài)線程辑甜、何時(shí)進(jìn)行線程調(diào)度衰絮,所以開發(fā)者要經(jīng)常考慮到線程調(diào)度的情況磷醋。

1猫牡、減少隊(duì)列切換

當(dāng)線程數(shù)量超過 CPU 核心數(shù)量,CPU 核心通過線程調(diào)度切換用戶態(tài)線程邓线,意味著有上下文的轉(zhuǎn)換(寄存器數(shù)據(jù)淌友、棧等)煌恢,過多的上下文切換會(huì)帶來資源開銷。雖然內(nèi)核態(tài)線程的切換理論上不會(huì)是性能負(fù)擔(dān)震庭,開發(fā)中還是應(yīng)該盡量減少線程的切換瑰抵。

注意:使用 GCD 是操作隊(duì)列,隊(duì)列切換并不總是意味著線程的切換(GCD 會(huì)做好 CPU 親和性)器联,代碼層面可以減少隊(duì)列切換來優(yōu)化二汛。

看一段簡單的代碼:

dispatch_queue_t queue = dispatch_queue_create("x.x.x", DISPATCH_QUEUE_CONCURRENT);
- (void)tast1 {
    dispatch_async(queue, ^{
        //執(zhí)行任務(wù)1
        dispatch_async(dispatch_get_main_queue(), ^{
            //任務(wù)1完成
            [self tast2];
        });
    });
}
- (void)tast2 {
    dispatch_async(queue, ^{
        //執(zhí)行任務(wù)2
        dispatch_async(dispatch_get_main_queue(), ^{
            //任務(wù)2完成
        });
    });
}

這里創(chuàng)建了一個(gè)并行隊(duì)列,調(diào)用-tast1會(huì)執(zhí)行兩個(gè)任務(wù)拨拓,任務(wù)2要等待任務(wù)1執(zhí)行完成肴颊,這里一共有四次隊(duì)列的切換,明顯是多余的渣磷,而且也不需要并行隊(duì)列來處理苫昌,優(yōu)化如下:

dispatch_queue_t queue = dispatch_queue_create("x.x.x", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
    //執(zhí)行任務(wù)1
    //執(zhí)行任務(wù)2
    dispatch_async(dispatch_get_main_queue(), ^{
        //任務(wù)1、2完成
    });
});

2幸海、控制線程數(shù)量

使用 GCD 并行隊(duì)列,當(dāng)任務(wù)過多且耗時(shí)較長時(shí)奥务,隊(duì)列會(huì)開辟大量的線程物独,而部分線程里面的耗時(shí)任務(wù)已經(jīng)耗盡了 CPU 資源,所以其他的線程也只能等待 CPU 時(shí)間片氯葬,過多的線程也會(huì)讓線程調(diào)度過于頻繁挡篓。

GCD 中并行隊(duì)列并不能限制線程數(shù)量,可以創(chuàng)建多個(gè)串行隊(duì)列來模擬并行的效果帚称,業(yè)界知名框架 YYKit 就做了這個(gè)邏輯官研,通過和 CPU 核心數(shù)量相同的串行隊(duì)列輪詢返回來達(dá)到并行隊(duì)列的效果:

static dispatch_queue_t YYAsyncLayerGetDisplayQueue() {
//最大隊(duì)列數(shù)量
#define MAX_QUEUE_COUNT 16
//隊(duì)列數(shù)量
    static int queueCount;
//使用棧區(qū)的數(shù)組存儲(chǔ)隊(duì)列
    static dispatch_queue_t queues[MAX_QUEUE_COUNT];
    static dispatch_once_t onceToken;
    static int32_t counter = 0;
    dispatch_once(&onceToken, ^{
//串行隊(duì)列數(shù)量和處理器數(shù)量相同
        queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount;
        queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;
//創(chuàng)建串行隊(duì)列,設(shè)置優(yōu)先級(jí)
        if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {
            for (NSUInteger i = 0; i < queueCount; i++) {
                dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);
                queues[i] = dispatch_queue_create("com.ibireme.yykit.render", attr);
            }
        } else {
            for (NSUInteger i = 0; i < queueCount; i++) {
                queues[i] = dispatch_queue_create("com.ibireme.yykit.render", DISPATCH_QUEUE_SERIAL);
                dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
            }
        }
    });
//輪詢返回隊(duì)列
    uint32_t cur = (uint32_t)OSAtomicIncrement32(&counter);
    return queues[cur % queueCount];
#undef MAX_QUEUE_COUNT
}

然而這樣會(huì)導(dǎo)致串行隊(duì)列比較少闯睹,若你的任務(wù)很多時(shí)戏羽,會(huì)導(dǎo)致 CPU 資源利用率不高。YYKit 在異步繪制時(shí)使用這段代碼楼吃,這是一個(gè)任務(wù)不算多始花、耗時(shí)較長的場景,所以是比較適合的孩锡。

3酷宵、線程優(yōu)先級(jí)權(quán)衡

通常來說,線程調(diào)度除了輪轉(zhuǎn)法以外躬窜,還有優(yōu)先級(jí)調(diào)度的方案浇垦,在線程調(diào)度時(shí),高優(yōu)先級(jí)的線程會(huì)更早的執(zhí)行荣挨。有兩個(gè)概念需要明確:

  • IO 密集型線程:頻繁等待的線程男韧,等待的時(shí)候會(huì)讓出時(shí)間片朴摊。
  • CPU 密集型線程:很少等待的線程,意味著長時(shí)間占用著 CPU煌抒。

特殊場景下仍劈,當(dāng)多個(gè) CPU 密集型線程霸占了所有 CPU 資源,而它們的優(yōu)先級(jí)都比較高寡壮,而此時(shí)優(yōu)先級(jí)較低的 IO 密集型線程將持續(xù)等待贩疙,產(chǎn)生線程餓死的現(xiàn)象。當(dāng)然况既,為了避免線程餓死这溅,系統(tǒng)會(huì)逐步提高被“冷落”線程的優(yōu)先級(jí),IO 密集型線程通常情況下比 CPU 密集型線程更容易獲取到優(yōu)先級(jí)提升棒仍。

雖然系統(tǒng)會(huì)自動(dòng)做這些事情悲靴,但是這總歸會(huì)造成時(shí)間等待,可能會(huì)影響用戶體驗(yàn)莫其。所以筆者認(rèn)為開發(fā)者需要從兩個(gè)方面權(quán)衡優(yōu)先級(jí)問題:

  • 讓 IO 密集型線程優(yōu)先級(jí)高于 CPU 密集型線程癞尚。
  • 讓緊急的任務(wù)擁有更高的優(yōu)先級(jí)。

比如一個(gè)場景:大量的圖片異步解壓的任務(wù)乱陡,解壓的圖片不需要立即反饋給用戶浇揩,同時(shí)又有大量的異步查詢磁盤緩存的任務(wù),而查詢磁盤緩存任務(wù)完成過后需要反饋給用戶憨颠。

圖片解壓屬于 CPU 密集型線程胳徽,查詢磁盤緩存屬于 IO 密集型線程,而后者需要反饋給用戶更加緊急爽彤,所以應(yīng)該讓圖片解壓線程的優(yōu)先級(jí)低一點(diǎn)养盗,查詢磁盤緩存的線程優(yōu)先級(jí)高一點(diǎn)。

值得注意的是适篙,這里是說大量的異步任務(wù)往核,意味著 CPU 很有可能滿負(fù)荷運(yùn)算,若 CPU 資源綽綽有余的情況下就沒那個(gè)必要去處理優(yōu)先級(jí)問題嚷节。

iOS 8 過后設(shè)置隊(duì)列優(yōu)先級(jí)的方法如下:

dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_BACKGROUND, 0);
dispatch_queue_t queue = dispatch_queue_create("x.x.x", attr);

這里就設(shè)置了一個(gè)QOS_CLASS_BACKGROUND優(yōu)先級(jí)铆铆,比較適合后臺(tái)異步下載大文件之類的業(yè)務(wù)。

4丹喻、主線程任務(wù)的優(yōu)化

有些業(yè)務(wù)只能寫在主線程薄货,比如 UI 類組件的初始化及其布局。其實(shí)這方面的優(yōu)化就比較多了碍论,業(yè)界所說的性能優(yōu)化大部分都是為了減輕主線程的壓力谅猾,似乎有些偏離了多線程優(yōu)化的范疇了,下面就基于主線程任務(wù)的管理大致羅列幾點(diǎn)吧:

內(nèi)存復(fù)用

通過內(nèi)存復(fù)用來減少開辟內(nèi)存的時(shí)間消耗,這在系統(tǒng) UI 類組件中應(yīng)用廣泛税娜,比如 UITableViewCell 的復(fù)用坐搔。同時(shí),減少開辟內(nèi)存意味著減少了內(nèi)存釋放敬矩,同樣能節(jié)約 CPU 資源概行。

懶加載任務(wù)

既然 UI 組件必須在主線程初始化,那么就需要用時(shí)再初始化吧弧岳,swift 的寫時(shí)復(fù)制也是類似的思路凳忙。

任務(wù)拆分排隊(duì)執(zhí)行

通過監(jiān)聽 Runloop 即將結(jié)束等通知,將大量的任務(wù)拆分開來禽炬,在每次 Runloop 循環(huán)周期執(zhí)行少量任務(wù)涧卵。其實(shí)在實(shí)踐這種優(yōu)化思路之前,應(yīng)該想想能不能將任務(wù)放到異步線程腹尖,而不是用這種比較極端的優(yōu)化手段柳恐。

參考:iOS 任務(wù)調(diào)度器:為 CPU 和內(nèi)存減負(fù)

主線程空閑時(shí)執(zhí)行任務(wù)

//這里是主線程上下文
dispatch_async(dispatch_get_main_queue(), ^{
    //等到主線程空閑執(zhí)行該任務(wù)
});

這種手法挺巧,可以讓 block 中的任務(wù)延遲到主線程空閑再執(zhí)行热幔,不過也不適合計(jì)算量過大的任務(wù)乐设,因?yàn)槭冀K是在主線程嘛。

三绎巨、關(guān)于“鎖”

多線程會(huì)帶來線程安全問題近尚,當(dāng)原子操作不能滿足業(yè)務(wù)時(shí),往往需要使用各種“鎖”來保證內(nèi)存的讀寫安全认烁。

常用的鎖有互斥鎖、讀寫鎖介汹、空轉(zhuǎn)鎖却嗡,通常情況下,iOS 開發(fā)中互斥鎖pthread_mutex_t嘹承、dispatch_semaphore_t窗价,讀寫鎖pthread_rwlock_t就能滿足大部分需求,并且性能不錯(cuò)叹卷。

在讀取鎖失敗時(shí)撼港,線程有可能有兩種狀態(tài):

  • 空轉(zhuǎn)狀態(tài):線程執(zhí)行空任務(wù)循環(huán)等待,當(dāng)鎖可用時(shí)立即獲取鎖骤竹。
  • 掛起狀態(tài):線程掛起帝牡,當(dāng)鎖可用時(shí)需要其他線程喚醒。

喚醒線程比較耗時(shí)蒙揣,線程空轉(zhuǎn)需要消耗 CPU 資源并且時(shí)間越長消耗越多靶溜,由此可知空轉(zhuǎn)適合少量任務(wù)、掛起適合大量任務(wù)。

實(shí)際上互斥鎖和讀寫鎖都有空轉(zhuǎn)鎖的特性罩息,它們?cè)讷@取鎖失敗時(shí)會(huì)先空轉(zhuǎn)一段時(shí)間嗤详,然后才會(huì)掛起,而空轉(zhuǎn)鎖也不會(huì)永遠(yuǎn)的空轉(zhuǎn)瓷炮,在特定的空轉(zhuǎn)時(shí)間過后仍然會(huì)掛起葱色,所以通常情況下不用刻意去使用空轉(zhuǎn)鎖,Casa Taloyum 在博客中有詳細(xì)的解釋娘香。

1苍狰、OSSpinLock 優(yōu)先級(jí)反轉(zhuǎn)問題

優(yōu)先級(jí)反轉(zhuǎn)概念:比如兩個(gè)線程 A 和 B,優(yōu)先級(jí) A < B茅主。當(dāng) A 獲取鎖訪問共享資源時(shí)舞痰,B 嘗試獲取鎖,那么 B 就會(huì)進(jìn)入忙等狀態(tài)诀姚,忙等時(shí)間越長對(duì) CPU 資源的占用越大响牛;而由于 A 的優(yōu)先級(jí)低于 B,A 無法與高優(yōu)先級(jí)的線程爭奪 CPU 資源赫段,從而導(dǎo)致任務(wù)遲遲完成不了呀打。解決優(yōu)先級(jí)反轉(zhuǎn)的方法有“優(yōu)先級(jí)天花板”和“優(yōu)先級(jí)繼承”,它們的核心操作都是提升當(dāng)前正在訪問共享資源的線程的優(yōu)先級(jí)糯笙。

OSSpinLock 由于這個(gè)問題導(dǎo)致很多開源庫都放棄使用了贬丛,有興趣可以看看一篇文章:不再安全的 OSSpinLock

2给涕、避免死鎖

很常見的場景是豺憔,同一線程重復(fù)獲取鎖導(dǎo)致的死鎖,這種情況可以使用遞歸鎖來處理够庙,pthread_mutex_t使用pthread_mutex_init_recursive()方法初始化就能擁有遞歸鎖的特性恭应。

使用pthread_mutex_trylock()等嘗試獲取鎖的方法能有效的避免死鎖的情況,在 YYCache 源碼中有一段處理就比較精致:

while (!finish) {
        if (pthread_mutex_trylock(&_lock) == 0) {
            ...
            finish = YES;
            ...
            pthread_mutex_unlock(&_lock);
        } else {
            usleep(10 * 1000); //10 ms
        }
    }

這段代碼除了避免潛在的死鎖情況外耘眨,還做了一個(gè)10ms的掛起操作然后循環(huán)嘗試昼榛,而不是直接讓線程空轉(zhuǎn)浪費(fèi)過多的 CPU 資源。雖然掛起線程“浪費(fèi)了”互斥鎖的空轉(zhuǎn)期剔难,增加了喚醒線程的資源消耗胆屿,降低了鎖的性能,但是考慮到 YYCache 此處的業(yè)務(wù)是修剪內(nèi)存偶宫,并非是對(duì)鎖性能要求很高的業(yè)務(wù)非迹,并且修剪的任務(wù)量可能比較大,出現(xiàn)線程競爭的幾率較大纯趋,所以這里放棄線程空轉(zhuǎn)直接掛起線程是一個(gè)不錯(cuò)的處理方式彻秆。

3、最小化臨界區(qū)

開發(fā)者應(yīng)該充分的理解業(yè)務(wù),將臨界區(qū)盡量縮小唇兑,不會(huì)出現(xiàn)線程安全問題的代碼就不要用鎖來保護(hù)了酒朵,這樣才能提高并發(fā)時(shí)鎖的性能。

4扎附、時(shí)刻注意不可重入方法的安全

當(dāng)一個(gè)方法是可重入的時(shí)候蔫耽,可以放心大膽的使用,若一個(gè)方法不可重入留夜,開發(fā)者應(yīng)該多留意匙铡,思考這個(gè)方法會(huì)不會(huì)有多個(gè)線程訪問的情況,若有就老老實(shí)實(shí)的加上線程鎖碍粥。

5鳖眼、編譯器的過度優(yōu)化

編譯器可能會(huì)為了提高效率將變量寫入寄存器而暫時(shí)不寫回,方便下次使用嚼摩,我們知道一句代碼轉(zhuǎn)換為指令不止一條钦讳,所以在變量寫入寄存器沒來得及寫回的過程中,可能這個(gè)變量被其它線程讀寫了枕面。編譯器同樣會(huì)為了提高效率對(duì)它認(rèn)為順序無關(guān)的指令調(diào)換順序愿卒。

以上都可能會(huì)導(dǎo)致合理使用鎖的地方仍然線程不安全,而volatile關(guān)鍵字就可以解決這類問題潮秘,它能阻止編譯器為了效率將變量緩存到寄存器而不及時(shí)寫回琼开,也能阻止編譯器調(diào)整操作volatile修飾變量的指令順序。

原子自增函數(shù)就有類似的應(yīng)用:int32_t OSAtomicIncrement32( volatile int32_t *__theValue )枕荞。

6柜候、CPU 亂序執(zhí)行

CPU 也可能為了提高效率而去交換指令的順序,導(dǎo)致加鎖的代碼也不安全躏精,解決這類問題可以使用內(nèi)存屏障渣刷,CPU 越過內(nèi)存屏障后會(huì)刷新寄存器對(duì)變量的分配。

OC 實(shí)現(xiàn)單例模式的方法:

void
_dispatch_once(dispatch_once_t *predicate,
        DISPATCH_NOESCAPE dispatch_block_t block)
{
    if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {
        dispatch_once(predicate, block);
    } else {
        dispatch_compiler_barrier();
    }
    DISPATCH_COMPILER_CAN_ASSUME(*predicate == ~0l);
}

其中就能看到內(nèi)存屏障的宏:#define dispatch_compiler_barrier() __asm__ __volatile__("" ::: "memory")玉控;還有一個(gè)分支預(yù)測(cè)減少指令跳轉(zhuǎn)的優(yōu)化宏(減少跳轉(zhuǎn)指令能提高 CPU 流水線執(zhí)行的效率):#define DISPATCH_EXPECT(x, v) __builtin_expect((x), (v))飞主。

結(jié)語

偏底層原理的東西比較抽象狮惜,筆者認(rèn)為搞清楚它為什么要這么做比它做了什么更為重要高诺,更能提升一個(gè)人的思維∧氪郏基礎(chǔ)技術(shù)往往在業(yè)務(wù)中的作用不是那么大虱而,但是卻能讓你更從容的編碼,超越普通開發(fā)者的思維也能讓你在較復(fù)雜的業(yè)務(wù)中選擇更合理更高效的方案开泽,你的代碼才能可靠牡拇。

共勉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市惠呼,隨后出現(xiàn)的幾起案子导俘,更是在濱河造成了極大的恐慌,老刑警劉巖剔蹋,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件旅薄,死亡現(xiàn)場離奇詭異,居然都是意外死亡泣崩,警方通過查閱死者的電腦和手機(jī)少梁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來矫付,“玉大人凯沪,你說我怎么就攤上這事÷蛴牛” “怎么了妨马?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長而叼。 經(jīng)常有香客問我身笤,道長,這世上最難降的妖魔是什么葵陵? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任液荸,我火速辦了婚禮,結(jié)果婚禮上脱篙,老公的妹妹穿的比我還像新娘娇钱。我一直安慰自己,他們只是感情好绊困,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布文搂。 她就那樣靜靜地躺著,像睡著了一般秤朗。 火紅的嫁衣襯著肌膚如雪煤蹭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天取视,我揣著相機(jī)與錄音硝皂,去河邊找鬼。 笑死作谭,一個(gè)胖子當(dāng)著我的面吹牛稽物,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播折欠,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼贝或,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼吼过!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起咪奖,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤盗忱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后羊赵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體售淡,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年慷垮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了揖闸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡料身,死狀恐怖汤纸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情芹血,我是刑警寧澤贮泞,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站幔烛,受9級(jí)特大地震影響啃擦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜饿悬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一令蛉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧狡恬,春花似錦珠叔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至兔乞,卻和暖如春汇鞭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背庸追。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工霍骄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锚国。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓腕巡,卻偏偏與公主長得像玄坦,于是被迫代替她去往敵國和親血筑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子绘沉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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

  • iOS多線程編程 基本知識(shí) 1. 進(jìn)程(process) 進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序,就是一段程序的執(zhí)...
    陵無山閱讀 6,004評(píng)論 1 14
  • GCD簡介 GCD 是 libdispatch 的市場名稱豺总,而 libdispatch 作為 Apple 的一個(gè)庫...
    獨(dú)木舟的木閱讀 1,235評(píng)論 0 5
  • 又來到了一個(gè)老生常談的問題车伞,應(yīng)用層軟件開發(fā)的程序員要不要了解和深入學(xué)習(xí)操作系統(tǒng)呢? 今天就這個(gè)問題開始喻喳,來談?wù)劜?..
    tangsl閱讀 4,088評(píng)論 0 23
  • 單任務(wù) 單任務(wù)的特點(diǎn)是排隊(duì)執(zhí)行另玖,也就是同步,就像再cmd輸入一條命令后表伦,必須等待這條命令執(zhí)行完才可以執(zhí)行下一條命令...
    Steven1997閱讀 1,166評(píng)論 0 6
  • 有一種寂寞谦去,身邊添一個(gè)可談的人,一條知心的狗蹦哼,或許就可以消減鳄哭。有一種寂寞,茫茫天地之間余舟一芥的無邊無際無著落纲熏,人...
    我來自遠(yuǎn)方閱讀 140評(píng)論 0 0