iOS面試備戰(zhàn)-多線程

image

iOS面試中多線程絕對(duì)是最重要的知識(shí)點(diǎn)之一,它在日常開發(fā)中會(huì)被廣泛使用嗤栓,而且多線程是有很多區(qū)分度很高的題目可供考察的佑刷。這篇文章會(huì)梳理下多線程和GCD相關(guān)的概念和幾個(gè)典型問題。因?yàn)镚CD相關(guān)的API用OC看著更直管一些钝计,所以這期實(shí)例就都用OC語(yǔ)言書寫恋博。

作為一個(gè)開發(fā)者,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要私恬,這是一個(gè)我的iOS開發(fā)交流群:130595548债沮,不管你是大牛還是小白都?xì)g迎入駐 ,讓我們一起進(jìn)步本鸣,共同發(fā)展R唏谩(群內(nèi)會(huì)免費(fèi)提供一些群主收藏的免費(fèi)學(xué)習(xí)書籍資料以及整理好的幾百道面試題和答案文檔!)

概念篇

在面對(duì)一些我們常見的概念時(shí)荣德,我們常有種這個(gè)東西我熟隧土,就認(rèn)為自己理解了,其實(shí)這種程度是不夠的命爬。當(dāng)我們可以清晰準(zhǔn)確的向別人描述一個(gè)東西曹傀,并能理解其官方定義的每個(gè)用語(yǔ)的含義,才算是我們熟悉理解了它饲宛。所以這里單獨(dú)抽一節(jié)講下多線程中的概念皆愉。

進(jìn)程,線程艇抠,任務(wù)幕庐,隊(duì)列

進(jìn)程:資源分配的最小單位。在iOS中一個(gè)應(yīng)用的啟動(dòng)就是開啟了一個(gè)進(jìn)程家淤。 線程:CPU調(diào)度的最小單位异剥。一個(gè)進(jìn)程里會(huì)有多個(gè)線程。 大家可以思考下絮重,進(jìn)程和線程為什么是從資源分配和CPU調(diào)度層面進(jìn)行定義的冤寿。

任務(wù):每次執(zhí)行的一段代碼,比如下載一張圖片青伤,觸發(fā)一個(gè)網(wǎng)絡(luò)請(qǐng)求督怜。 隊(duì)列:隊(duì)列是用來組織任務(wù)的,一個(gè)隊(duì)列包含多個(gè)任務(wù)狠角。

GCD

GCD(Grand Central Dispatch)是異步執(zhí)行任務(wù)的技術(shù)之一号杠。開發(fā)者只需要定義想執(zhí)行的任務(wù)并追加到適當(dāng)?shù)腄ispatch Queue中,GCD就能生成必要的線程執(zhí)行該任務(wù)。這里的線程管理是由系統(tǒng)處理的姨蟋,我們不必關(guān)心線程的創(chuàng)建銷毀屉凯,這大大方便了我們的開發(fā)效率。也可以說GCD是一種簡(jiǎn)化線程操作的多線程使用技術(shù)方案眼溶。

安卓沒有跟GCD完全相同的一套技術(shù)方案的悠砚,雖然它可以處理GCD實(shí)現(xiàn)的一系列效果。

串行偷仿,并行哩簿,并發(fā)

GCD的使用都是通過調(diào)度隊(duì)列(Dispatch Queue)的形式進(jìn)行的宵蕉,調(diào)度隊(duì)列有以下 幾種形式:

串行(serial):多任務(wù)中某時(shí)刻只能有一個(gè)任務(wù)被運(yùn)行酝静;

并行(parallel):相對(duì)于串行,某時(shí)刻有多個(gè)任務(wù)同時(shí)被執(zhí)行羡玛,需要多核能力别智;

并發(fā)(concurrent):引入時(shí)間片和搶占之后才有了并發(fā)的說法,某個(gè)時(shí)間片只有一個(gè)任務(wù)在執(zhí)行稼稿,執(zhí)行完時(shí)間片后進(jìn)行資源搶占薄榛,到下一個(gè)任務(wù)去執(zhí)行,即“微觀串行让歼,宏觀并發(fā)”敞恋,所以這種情況下只有一個(gè)空閑的某核,多核空閑就又可以實(shí)現(xiàn)并行運(yùn)行了谋右;

我們常用的調(diào)度隊(duì)列有以下幾種:

// 串行隊(duì)列
dispatch_queue_t serialQueue = dispatch_queue_create("com.gcd.serialQueue", DISPATCH_QUEUE_SERIAL);
// 并發(fā)隊(duì)列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.gcd.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
// 全局并發(fā)隊(duì)列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 主隊(duì)列
let mainQueue = DispatchQueue.main

注意GCD創(chuàng)建的是并發(fā)隊(duì)列而不是并行隊(duì)列硬猫。但這里的并發(fā)隊(duì)列是一個(gè)相對(duì)寬泛的定義,它包含并行的概念改执,GCD作為一個(gè)智能的中心調(diào)度系統(tǒng)會(huì)根據(jù)系統(tǒng)情況判斷當(dāng)前能否使用多核能力分?jǐn)偠鄠€(gè)任務(wù)啸蜜,如果滿足的話此時(shí)就是在并行的執(zhí)行隊(duì)列中的任務(wù)。

同步辈挂,異步

同步:函數(shù)會(huì)阻塞當(dāng)前線程直到任務(wù)完成返回才能進(jìn)行其它操作衬横;

異步:在任務(wù)執(zhí)行完成之前先將函數(shù)值返回,不會(huì)阻塞當(dāng)前線程终蒂;

串行蜂林、并發(fā)和同步、異步相互結(jié)合能否開啟新線程

串行隊(duì)列 并發(fā)隊(duì)列 主隊(duì)列
同步 不開啟新線程 不開啟新線程 不開啟新線程
異步 開啟新線程 開啟新線程 不開啟新線程

主線程和主隊(duì)列

主線程是一個(gè)線程拇泣,主隊(duì)列是指主線程上的任務(wù)組織形式悉尾。

主隊(duì)列只會(huì)在主線程執(zhí)行,但主線程上執(zhí)行的不一定就是主隊(duì)列挫酿,還有可能是別的同步隊(duì)列构眯。因?yàn)榍罢f過,同步操作不會(huì)開辟新的線程早龟,所以當(dāng)你自定義一個(gè)同步的串行或者并行隊(duì)列時(shí)都是還在主線程執(zhí)行惫霸。

判斷當(dāng)前是否是主線程:

BOOL isMainThread = [NSThread isMainThread];

判斷當(dāng)前是否在主隊(duì)列上:

static void *mainQueueKey = "mainQueueKey";
dispatch_queue_set_specific(dispatch_get_main_queue(), mainQueueKey, &mainQueueKey, NULL);
BOOL isMainQueue = dispatch_get_specific(mainQueueKey));

隊(duì)列與線程的關(guān)系

隊(duì)列是對(duì)任務(wù)的描述猫缭,它可以包含多個(gè)任務(wù),這是應(yīng)用層的一種描述壹店。線程是系統(tǒng)級(jí)的調(diào)度單位猜丹,它是更底層的描述。一個(gè)隊(duì)列(并行隊(duì)列)的多個(gè)任務(wù)可能會(huì)被分配到多個(gè)線程執(zhí)行硅卢。

問題

代碼分析

1射窒、分析下面代碼的執(zhí)行邏輯

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [self syncMainTask];
}

- (void)syncMainTask {
    dispatch_queue_main_t mainQueue = dispatch_get_main_queue();
    dispatch_sync(mainQueue, ^{
        NSLog(@"main queue task");
    });
}

這段代碼會(huì)輸出task1,然后發(fā)生死鎖将塑,導(dǎo)致crash脉顿。

追加問題一:為什么會(huì)死鎖?死鎖就會(huì)導(dǎo)致crash点寥?

我們先分析crash的情況艾疟,正常死鎖應(yīng)該就是卡死的情況,不應(yīng)該導(dǎo)致carsh敢辩。那為什么會(huì)carsh呢蔽莱,看崩潰信息:

image

是一個(gè)EXC_BAD_INSTRUCTION類型的crash,執(zhí)行了一個(gè)出錯(cuò)的命令戚长。

然后看__DISPATCH_WAIT_FOR_QUEUE__的調(diào)用棧信息:

image

右側(cè)匯編代碼給出了更詳細(xì)的crash信息:BUG IN CLIENT OF LIBDISPATCH: dispatch_sync called on queue already owned by current thread盗冷。

在當(dāng)前線程已經(jīng)擁有的隊(duì)列中執(zhí)行dispatch_sync同步操作會(huì)導(dǎo)致crash。

libdispatch的源碼中我們可以找到該函數(shù)的定義:

DISPATCH_NOINLINE
static void
__DISPATCH_WAIT_FOR_QUEUE__(dispatch_sync_context_t dsc, dispatch_queue_t dq) {
    uint64_t dq_state = _dispatch_wait_prepare(dq);
    if (unlikely(_dq_state_drain_locked_by(dq_state, dsc->dsc_waiter))) {
    DISPATCH_CLIENT_CRASH((uintptr_t)dq_state,
            "dispatch_sync called on queue "
            "already owned by current thread");
    }
    /*...*/
}

所以我們知道了同廉,這個(gè)carsh是libdispatch內(nèi)部拋出的仪糖,當(dāng)它檢測(cè)到可能發(fā)生死鎖時(shí),就直接觸發(fā)崩潰恤溶,事實(shí)上它不能完全判斷出所有死鎖的情況乓诽。

我們分析這里為什么會(huì)發(fā)生死鎖。首先syncMainTask就是在主隊(duì)列中的咒程,我們?cè)谥麝?duì)列先添加dispatch_sync然后再添加其內(nèi)部的block鸠天。主隊(duì)列FIFO,只有sync執(zhí)行完了才會(huì)執(zhí)行內(nèi)部的block帐姻,而此時(shí)是一個(gè)同步隊(duì)列稠集,block執(zhí)行完才會(huì)退出sync,所以導(dǎo)致了死鎖饥瓷。

對(duì)于死鎖的解釋我也查了好幾篇文章剥纷,有些說法其實(shí)是經(jīng)不起推敲的,這個(gè)解釋是我認(rèn)為相對(duì)合理的呢铆。

附一篇參考文章:GCD死鎖

引出問題二:什么情況下會(huì)發(fā)生死鎖晦鞋?

GCD中發(fā)生死鎖需要滿足兩個(gè)條件:

  • 同步執(zhí)行串行隊(duì)列
  • 執(zhí)行sync的隊(duì)列和block所在隊(duì)列為同一個(gè)隊(duì)列

引出問題三:如何避免死鎖?這段代碼應(yīng)該如何修改?

根據(jù)上面提到的條件悠垛,我們可以將任務(wù)異步執(zhí)行线定,或者換成一個(gè)并發(fā)隊(duì)列。另外將block放到一個(gè)非主隊(duì)列里執(zhí)行也是可以的确买。

2斤讥、分析一下代碼執(zhí)行結(jié)果

int a = 0;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
while (a < 2) {
    dispatch_async(queue, ^{
        a++;
    });
}
NSLog(@"a = %d", a);

首先該段代碼會(huì)編譯不過,編譯器檢測(cè)到變量a被block截獲湾趾,并嘗試修改就報(bào)以下錯(cuò)誤:

Variable is not assignable (missing __block type specifier)芭商。如果我們要在block里對(duì)外界變量重新復(fù)制,需要添加__block的聲明:__block int a = 0;

我們分析這段代碼搀缠,在開始while之后加入一個(gè)異步任務(wù)铛楣,再之后呢,這個(gè)是不確定了胡嘿,可能是執(zhí)行a++也可能是因不滿足退出條件再次執(zhí)行加入異步任務(wù)蛉艾,直到滿足a<2才會(huì)退出while循環(huán)钳踊。那輸出結(jié)果也就是不確定了衷敌,因?yàn)榭赡茉谂袛嗵鲅h(huán)和輸出結(jié)果的時(shí)候另外的線程又執(zhí)行了一次a++

再擴(kuò)展下拓瞪,如果將那個(gè)并發(fā)隊(duì)列改成主隊(duì)列缴罗,執(zhí)行邏輯還是一樣的嗎?

首先主隊(duì)列是不會(huì)開啟新線程的祭埂,主隊(duì)列上的異步操作執(zhí)行時(shí)機(jī)是等別的任務(wù)都執(zhí)行完了面氓,再來執(zhí)行添加的a++。顯然在while循環(huán)里蛆橡,主隊(duì)列既有任務(wù)還未執(zhí)行完畢舌界,所以就不會(huì)執(zhí)行a++,也就導(dǎo)致while循環(huán)不會(huì)退出泰演,形成死循環(huán)呻拌。

其它問題

什么是線程安全,為什么UI操作必須在主線程執(zhí)行

線程安全:當(dāng)多個(gè)線程訪問某個(gè)方法時(shí)睦焕,不管你通過怎樣的調(diào)用方式或者說這些線程如何交替的執(zhí)行藐握,我們?cè)谥鞒绦蛑胁恍枰プ鋈魏蔚耐剑@個(gè)類的結(jié)果行為都是我們?cè)O(shè)想的正確行為垃喊,那么我們就可以說這個(gè)類時(shí)線程安全的猾普。

為什么UI操作必須放到主線程:首先UIKit不是線程安全的,多線程訪問會(huì)導(dǎo)致UI效果不可預(yù)期本谜,所以我們不能使用多個(gè)線程去處理UI初家。那既然要單線程處理UI為什么是在主線程呢,這是因?yàn)閁IApplication作為程序的起點(diǎn)是在主線程初始化的,所以我們后續(xù)的UI操作也都要放到主線程處理溜在。

關(guān)于這個(gè)問題展開討論可以參閱這篇文章:iOS拾遺——為什么必須在主線程操作UI

開啟新的線程有哪些方法

1评架、NSThread

2、NSOperationQueue

3炕泳、GCD

4纵诞、NSObject的performSelectorInBackground方法

5、pthread

多線程任務(wù)要實(shí)現(xiàn)順序執(zhí)行有哪些方法

1培遵、dispatch_group

2浙芙、dispatch_barrier

3、dispatch_semaphore_t

4籽腕、NSOperation的addDependency方法

如何實(shí)現(xiàn)一個(gè)多讀單寫的功能嗡呼?

多讀單寫的意思就是可以有多個(gè)線程同時(shí)參與讀取數(shù)據(jù),但是寫數(shù)據(jù)時(shí)不能有讀操作的參與切只有一個(gè)線程在寫數(shù)據(jù)皇耗。

我們寫一個(gè)示例程序南窗,看下在不做限制的多讀多寫程序中會(huì)發(fā)生什么。

// 計(jì)數(shù)器
self.count = 0;
// 并發(fā)隊(duì)列
self.concurrentQueue = dispatch_get_global_queue(0, 0);
for (int i = 0; i< 10; i++) {
    dispatch_async(self.concurrentQueue, ^{
        [self read];
    });
    dispatch_async(self.concurrentQueue, ^{
        [self write];
    });
}
// 讀寫操作
- (void)read {
    NSLog(@"read---- %d", self.count);
}

- (void)write {
    self.count += 1;
    NSLog(@"write---- %d", self.count);
}

// 輸出內(nèi)容
2020-07-18 11:47:03.612175+0800 GCD_OC[76121:1709312] read---- 0
2020-07-18 11:47:03.612273+0800 GCD_OC[76121:1709311] read---- 1
2020-07-18 11:47:03.612230+0800 GCD_OC[76121:1709314] write---- 1
2020-07-18 11:47:03.612866+0800 GCD_OC[76121:1709312] write---- 2
2020-07-18 11:47:03.612986+0800 GCD_OC[76121:1709311] write---- 3
2020-07-18 11:47:03.612919+0800 GCD_OC[76121:1709314] read---- 2
2020-07-18 11:47:03.613252+0800 GCD_OC[76121:1709312] read---- 3
2020-07-18 11:47:03.613346+0800 GCD_OC[76121:1709314] write---- 4
2020-07-18 11:47:03.613423+0800 GCD_OC[76121:1709311] read---- 4

每次運(yùn)行的輸出結(jié)果都會(huì)不一樣郎楼,根據(jù)這個(gè)輸出內(nèi)容万伤,我們可以看到在還沒有執(zhí)行到輸出write----1的時(shí)候,就已經(jīng)執(zhí)行了read----1呜袁,在write---- 3之后 read的結(jié)果卻是2敌买。這絕對(duì)是我們所不期望的。其實(shí)在程序設(shè)計(jì)中我們是不應(yīng)該設(shè)計(jì)出多讀多寫這種行為阶界,因?yàn)檫@個(gè)結(jié)果是不可控虹钮。

解決方案之一是對(duì)讀寫操作都加上鎖做成單獨(dú)單寫,這樣是沒問題但有些浪費(fèi)性能膘融,正常寫操作確定之后結(jié)果就確定了芙粱,讀的操作可以多線程同時(shí)進(jìn)行,而不需要等別的線程讀完它才能讀氧映,所以有了多讀單寫的需求春畔。

解決多讀單寫常見有兩種方案,第一種是使用讀寫鎖pthread_rwlock_t屯耸。

讀寫鎖具有一些幾個(gè)特性:

  • 同一時(shí)間拐迁,只能有一個(gè)線程進(jìn)行寫的操作
  • 同一時(shí)間,允許有多個(gè)線程進(jìn)行讀的操作疗绣。
  • 同一時(shí)間线召,不允許既有寫的操作,又有讀的操作多矮。

這跟我們的多讀單寫需求完美吻合缓淹,也可以說讀寫鎖的設(shè)計(jì)就是為了實(shí)現(xiàn)這一需求的哈打。它的實(shí)現(xiàn)方式如下:

// 執(zhí)行讀寫操作之前需要定義一個(gè)讀寫鎖
@property (nonatomic,assign) pthread_rwlock_t lock;
pthread_rwlock_init(&_lock,NULL);
// 讀寫操作
- (void)read {
    pthread_rwlock_rdlock(&_lock);
    NSLog(@"read---- %d", self.count);
    pthread_rwlock_unlock(&_lock);
}

- (void)write {
    pthread_rwlock_wrlock(&_lock);
    _count += 1;
    NSLog(@"write---- %d", self.count);
    pthread_rwlock_unlock(&_lock);
}
// 輸出內(nèi)容
2020-07-18 12:00:29.363875+0800 GCD_OC[77172:1722472] read---- 0
2020-07-18 12:00:29.363875+0800 GCD_OC[77172:1722471] read---- 0
2020-07-18 12:00:29.364195+0800 GCD_OC[77172:1722469] write---- 1
2020-07-18 12:00:29.364325+0800 GCD_OC[77172:1722472] write---- 2
2020-07-18 12:00:29.364450+0800 GCD_OC[77172:1722470] read---- 2
2020-07-18 12:00:29.364597+0800 GCD_OC[77172:1722471] write---- 3
2020-07-18 12:00:29.366490+0800 GCD_OC[77172:1722469] read---- 3
2020-07-18 12:00:29.366703+0800 GCD_OC[77172:1722472] write---- 4
2020-07-18 12:00:29.366892+0800 GCD_OC[77172:1722489] read---- 4

我們查看輸出日志,所以的讀操作結(jié)果都是最近一次寫操作所賦的值讯壶,這是符合我們預(yù)期的料仗。

還有一種實(shí)現(xiàn)多讀單寫的方案是使用GCD中的柵欄函數(shù)dispatch_barrier。柵欄函數(shù)的目的就是保證在同一隊(duì)列中它之前的操作全部執(zhí)行完畢再執(zhí)行后面的操作伏蚊。為了保證寫操作的互斥行立轧,我們要對(duì)寫操作執(zhí)行「柵欄」:

// 我們定義一個(gè)用于讀寫的并發(fā)對(duì)列
self.rwQueue = dispatch_queue_create("com.rw.queue", DISPATCH_QUEUE_CONCURRENT);

- (void)read {
    dispatch_sync(self.rwQueue, ^{
        NSLog(@"read---- %d", self.count);
    });
}

- (void)write {
    dispatch_barrier_async(self.rwQueue, ^{
        self.count += 1;
        NSLog(@"write---- %d", self.count);
    });
}

這個(gè)輸出結(jié)果跟讀寫鎖實(shí)現(xiàn)是一樣的,也是符合預(yù)期的躏吊。

這里多說幾句氛改,這里的讀和寫分別使用syncasync。讀操作要用同步是為了阻塞線程盡快返回結(jié)果比伏,不用擔(dān)心無法實(shí)現(xiàn)多讀胜卤,因?yàn)槲覀兪褂昧瞬l(fā)隊(duì)列,是可以實(shí)現(xiàn)多讀的赁项。至于寫操作使用異步的柵欄函數(shù)葛躏,是為了寫時(shí)不阻塞線程,通過柵欄函數(shù)實(shí)現(xiàn)單寫悠菜。如果我們將讀寫都改成sync或者async舰攒,由于柵欄函數(shù)的機(jī)制是會(huì)順序先讀后寫。如果反過來李剖,讀操作異步芒率,寫操作同步也是可以達(dá)到多讀單寫的目的的囤耳,但讀的時(shí)候不立即返回結(jié)果篙顺,網(wǎng)上有人說只能使用異步方式,防止發(fā)生死鎖充择,這個(gè)說法其實(shí)不對(duì)德玫,因?yàn)橥疥?duì)列是不會(huì)發(fā)生死鎖的。

用GCD如何實(shí)現(xiàn)一個(gè)控制最大并發(fā)數(shù)且執(zhí)行任務(wù)FIFO的功能椎麦?

這個(gè)相對(duì)簡(jiǎn)單宰僧,通過信號(hào)量實(shí)現(xiàn)并發(fā)數(shù)的控制,通過并發(fā)隊(duì)列實(shí)現(xiàn)任務(wù)的FIFO的執(zhí)行

int maxConcurrent = 3;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(maxConcurrent);
dispatch_async(queue, ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    // task
    dispatch_semaphore_signal(semaphore);
});

作為一個(gè)開發(fā)者观挎,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要琴儿,這是一個(gè)我的iOS開發(fā)交流群:130595548,不管你是大牛還是小白都?xì)g迎入駐 嘁捷,讓我們一起進(jìn)步造成,共同發(fā)展!(群內(nèi)會(huì)免費(fèi)提供一些群主收藏的免費(fèi)學(xué)習(xí)書籍資料以及整理好的幾百道面試題和答案文檔P巯)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末晒屎,一起剝皮案震驚了整個(gè)濱河市喘蟆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鼓鲁,老刑警劉巖蕴轨,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異骇吭,居然都是意外死亡橙弱,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門燥狰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膘螟,“玉大人,你說我怎么就攤上這事碾局【2校” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵净当,是天一觀的道長(zhǎng)内斯。 經(jīng)常有香客問我,道長(zhǎng)像啼,這世上最難降的妖魔是什么俘闯? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮忽冻,結(jié)果婚禮上真朗,老公的妹妹穿的比我還像新娘豁陆。我一直安慰自己槽华,他們只是感情好匣掸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布桂肌。 她就那樣靜靜地躺著厘熟,像睡著了一般迅栅。 火紅的嫁衣襯著肌膚如雪捎谨。 梳的紋絲不亂的頭發(fā)上曼库,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天慈省,我揣著相機(jī)與錄音臀防,去河邊找鬼。 笑死边败,一個(gè)胖子當(dāng)著我的面吹牛袱衷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播笑窜,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼致燥,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了怖侦?” 一聲冷哼從身側(cè)響起篡悟,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤谜叹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后搬葬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體荷腊,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年急凰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了女仰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抡锈,死狀恐怖疾忍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情床三,我是刑警寧澤一罩,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站撇簿,受9級(jí)特大地震影響聂渊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜四瘫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一汉嗽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧找蜜,春花似錦饼暑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至竭望,卻和暖如春邪码,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背咬清。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留奴潘,地道東北人旧烧。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像画髓,于是被迫代替她去往敵國(guó)和親掘剪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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

  • 1奈虾、進(jìn)程 1)進(jìn)程是一個(gè)具有一定獨(dú)立功能的程序關(guān)于某次數(shù)據(jù)集合的一次運(yùn)行活動(dòng)夺谁,它是操作系統(tǒng)分配資源的基本單...
    Crics閱讀 465評(píng)論 0 0
  • 多線程技術(shù)方案 GCD NSOperation NSThread GCD 同步/異步 和串行/并發(fā) dispatc...
    愛玩游戲的iOS菜鳥閱讀 368評(píng)論 0 0
  • 計(jì)算機(jī)多進(jìn)程發(fā)展的背景: 早期計(jì)算機(jī)工作是單道批處理程序處理廉赔,講寫好的作業(yè)卡依次放進(jìn)計(jì)算機(jī)的批處理程序執(zhí)行,效率非...
    Gaizka閱讀 738評(píng)論 0 3
  • 同步串行 先看一個(gè)頭條的面試真題,下面這段代碼有什么問題? 這是一個(gè)同步串行的問題匾鸥,這段代碼會(huì)造成程序死鎖,下面分...
    PetitBread閱讀 999評(píng)論 0 6
  • 前言: 最近公司項(xiàng)目不怎么忙, 閑暇時(shí)間把iOS 在面試中可能會(huì)遇到的問題整理了一番, 一部分題目是自己面試遇到...
    Leon_520閱讀 6,126評(píng)論 2 23