iOS多線程- GCD詳解(二)

前言

該篇介紹并發(fā)及刻,串行,并行竞阐,異步和同步詳解和使用缴饭,以及其他方法,在理解多線程之前骆莹,必須先知道這幾個(gè)概念颗搂。

并發(fā): 單核設(shè)備,執(zhí)行多個(gè)線程操作幕垦,并非同步執(zhí)行丢氢,既當(dāng)有一個(gè)線程在執(zhí)行時(shí)傅联,其他線程處于掛起狀態(tài),會(huì)相互搶占資源疚察,或者一個(gè)線程中蒸走,不同隊(duì)列,在一個(gè)隊(duì)列中執(zhí)行時(shí)其他隊(duì)列阻塞稍浆。
并行:多核設(shè)備载碌,執(zhí)行多個(gè)線程操作嵌牺,并且同時(shí)執(zhí)行胸哥,不會(huì)相互搶占資源涕俗。
串行:任務(wù)在同一個(gè)線程執(zhí)行采记,且執(zhí)行完一個(gè)才能執(zhí)行下一個(gè)
同步:不開(kāi)辟新的線程比默,在同一個(gè)線程按順序執(zhí)行
異步:開(kāi)辟新的線程艇潭,在新的線程中執(zhí)行

1 侣诺、異步和同步執(zhí)行的方法介紹

dispatch_async(dispatch_queue_t queue, dispatch_block_t block)
//  在調(diào)度隊(duì)列上提交異步執(zhí)行塊并立即返回拳锚。
    
dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work)
// 以在調(diào)度隊(duì)列上進(jìn)行異步執(zhí)行并立即返回到自己定義的方法中益楼。
    
 dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block)
// 將塊排隊(duì)以在指定時(shí)間執(zhí)行猾漫。
    
dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue, void *context, dispatch_function_t work)
// 將應(yīng)用程序定義的函數(shù)排入隊(duì)列,以便在指定時(shí)間執(zhí)行感凤。
    
dispatch_function_t
// 提交給派遣隊(duì)列的函數(shù)原型悯周。
    
dispatch_block_t
// 提交給調(diào)度隊(duì)列的塊的原型,它不帶參數(shù)且沒(méi)有返回值陪竿。

dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
// 提交塊對(duì)象以便執(zhí)行禽翼,并在該塊完成同步執(zhí)行后返回。

dispatch_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work)
// 提交應(yīng)用程序定義的函數(shù)以在調(diào)度隊(duì)列上同步執(zhí)行同步族跛。

dispatch_queue_get_label
// 獲取隊(duì)列的標(biāo)簽

2 闰挡、異步和同步執(zhí)行的實(shí)現(xiàn)

在應(yīng)用過(guò)程中,同步和異步礁哄,并行和串行是組合使用的长酗,并且需要結(jié)合是在主線程還是全局線程。我們對(duì)各種情況進(jìn)行分析桐绒。

  • 串行(DISPATCH_QUEUE_SERIAL)
  • 并行(DISPATCH_QUEUE_CONCURRENT)
  • dispatch_queue_global(全局隊(duì)列夺脾,并發(fā)執(zhí)行)
  • dispatch_queue_main(主隊(duì)列,串行執(zhí)行)
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);
// 創(chuàng)建串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);
// 創(chuàng)建并發(fā)隊(duì)列
同步+串行

即不開(kāi)辟新的線程茉继,在同一個(gè)線程中劳翰,按順序執(zhí)行。當(dāng)執(zhí)行完一個(gè)之后才能執(zhí)行下一個(gè)馒疹。

dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);
        
dispatch_sync(queue, ^{
    NSLog(@"線程:1>>>%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
    NSLog(@"線程:2>>>%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
    NSLog(@"線程:3>>>%@",[NSThread currentThread]);
});

NSLog(@"線程:4>>>%@",[NSThread currentThread]);

打印之后的結(jié)果為:

2019-04-16 16:38:37.974577+0800[24573:3900925] 線程:1>>><NSThread: 0x100703540>{number = 1, name = main}
2019-04-16 16:38:37.974789+0800[24573:3900925] 線程:2>>><NSThread: 0x100703540>{number = 1, name = main}
2019-04-16 16:38:37.974810+0800[24573:3900925] 線程:3>>><NSThread: 0x100703540>{number = 1, name = main}
2019-04-16 16:38:37.974827+0800[24573:3900925] 線程:4>>><NSThread: 0x100703540>{number = 1, name = main}

可以看到佳簸,線程是順序執(zhí)行的,且都在主線程中執(zhí)行,沒(méi)有開(kāi)辟線程,驗(yàn)證我們的結(jié)論生均。

同步+并行

即不開(kāi)辟新的線程听想,在同一個(gè)線程中,只有一個(gè)線程马胧,也得按順序一個(gè)接一個(gè)汉买,因?yàn)楸旧砭筒荒芏鄤?chuàng)建線程,也就不存在并發(fā)佩脊。

dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);
        
dispatch_sync(queue, ^{
    NSLog(@"線程:1>>>%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
    NSLog(@"線程:2>>>%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
    NSLog(@"線程:3>>>%@",[NSThread currentThread]);
});
NSLog(@"線程:4>>>%@",[NSThread currentThread]);
2019-04-16 17:05:26.950802+0800 block_test[25020:3949022] 線程:1>>><NSThread: 0x100503b00>{number = 1, name = main}
2019-04-16 17:05:26.951073+0800 block_test[25020:3949022] 線程:2>>><NSThread: 0x100503b00>{number = 1, name = main}
2019-04-16 17:05:26.951094+0800 block_test[25020:3949022] 線程:3>>><NSThread: 0x100503b00>{number = 1, name = main}
2019-04-16 17:05:26.951146+0800 block_test[25020:3949022] 線程:4>>><NSThread: 0x100503b00>{number = 1, name = main}

可以看到蛙粘,都是在同一個(gè)線程順序執(zhí)行

異步+串行

即會(huì)開(kāi)辟且只開(kāi)辟一個(gè)新的線程,并且在新的線程中按順序一個(gè)一個(gè)執(zhí)行威彰,并且與舊線程并行執(zhí)行

dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);

dispatch_async(queue, ^{
    NSLog(@"線程:1>>>%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"線程:2>>>%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"線程:3>>>%@",[NSThread currentThread]);
});
NSLog(@"線程:4>>>%@",[NSThread currentThread]);
2019-04-16 17:18:32.885431+0800[25146:3971063] 線程:1>>><NSThread: 0x102812890>{number = 2, name = (null)}
2019-04-16 17:18:32.885464+0800[25146:3971032] 線程:4>>><NSThread: 0x100506350>{number = 1, name = main}
2019-04-16 17:18:32.885637+0800[25146:3971063] 線程:2>>><NSThread: 0x102812890>{number = 2, name = (null)}
2019-04-16 17:18:32.885658+0800[25146:3971063] 線程:3>>><NSThread: 0x102812890>{number = 2, name = (null)}

可以看到出牧,結(jié)果開(kāi)辟了且只有一個(gè)線程,在這個(gè)線程中順序執(zhí)行歇盼,并且與主線程并行執(zhí)行舔痕,在主線程之后。

異步+并行

即會(huì)開(kāi)辟且只開(kāi)辟一個(gè)新的線程豹缀,并且在新的線程中按順序一個(gè)一個(gè)執(zhí)行伯复,并且與舊線程并行執(zhí)行

dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);

dispatch_async(queue, ^{
    NSLog(@"線程:1>>>%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"線程:2>>>%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"線程:3>>>%@",[NSThread currentThread]);
});
NSLog(@"線程:4>>>%@",[NSThread currentThread]);
2019-04-16 17:10:51.809839+0800[25074:3958365] 線程:4>>><NSThread: 0x100703550>{number = 1, name = main}
2019-04-16 17:10:51.809845+0800[25074:3958399] 線程:1>>><NSThread: 0x100629510>{number = 2, name = (null)}
2019-04-16 17:10:51.810150+0800[25074:3958399] 線程:2>>><NSThread: 0x100629510>{number = 2, name = (null)}
2019-04-16 17:10:51.810171+0800[25074:3958399] 線程:3>>><NSThread: 0x100629510>{number = 2, name = (null)}

可以看到,結(jié)果開(kāi)辟了多個(gè)線程邢笙,在這個(gè)線程中并發(fā)執(zhí)行啸如,并且與舊線程并行執(zhí)行。

3氮惯、死鎖

GCD同步異步组底,并行和串行中,可以實(shí)現(xiàn)嵌套的使用筐骇,但是在嵌套使用時(shí),不理解的人在隨便亂用GCD嵌套使用會(huì)造成程序執(zhí)行中斷的嚴(yán)重問(wèn)題江滨,造成這個(gè)問(wèn)題的原因就是死鎖铛纬。之前提到過(guò),隊(duì)列是FIFO先進(jìn)先出的形式唬滑,一個(gè)線程可能有多個(gè)隊(duì)列告唆,在同一個(gè)線程的同一個(gè)隊(duì)列中,先進(jìn)入隊(duì)列的任務(wù)要先出去晶密,后面的隊(duì)列才能出去擒悬,如圖所示:


image.png

接下來(lái)我們來(lái)用代碼看看幾種會(huì)出現(xiàn)問(wèn)題的可能性和分析具體原因:

案例一
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"開(kāi)始");
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"線程:1>>>%@",[NSThread currentThread]);
        });
        NSLog(@"線程:2>>>%@",[NSThread currentThread]);
    }
    return 0;
}

當(dāng)在主線程執(zhí)行完,發(fā)現(xiàn)執(zhí)行崩潰稻艰,這是為什么呢懂牧?我們剛才說(shuō)過(guò)隊(duì)列是順序執(zhí)行,意味著一個(gè)隊(duì)列還沒(méi)結(jié)束,后面的隊(duì)列就無(wú)法結(jié)束僧凤。當(dāng)在主線程執(zhí)行同步隊(duì)列時(shí)畜侦,就表示主線程的主隊(duì)列又創(chuàng)建了一個(gè)任務(wù)。線程1和線程2都在主線程中的同一個(gè)隊(duì)列里躯保,并且這個(gè)任務(wù)是順序執(zhí)行旋膳,表示線程1創(chuàng)建的同時(shí)需要立馬就執(zhí)行,但是線程2還沒(méi)執(zhí)行完畢途事,因此線程1沒(méi)辦法先進(jìn)線程原則執(zhí)行所以線程1需要等待線程2執(zhí)行验懊。但是線程2要執(zhí)行完也需要經(jīng)過(guò)線程1,因此線程2也要等待線程1執(zhí)行完尸变。這樣互相等著造成線程無(wú)法釋放义图,阻塞主線程導(dǎo)致死鎖。

案例二
NSLog(@"開(kāi)始");
dispatch_async(dispatch_get_main_queue(), ^{
      NSLog(@"線程:1>>>%@",[NSThread currentThread]);
});
NSLog(@"線程:2>>>%@",[NSThread currentThread]);
2019-04-16 17:44:36.446608+0800[25372:4016930] 開(kāi)始
2019-04-16 17:44:36.447153+0800[25372:4016930] 線程:2>>><NSThread: 0x10050d420>{number = 1, name = main}
2019-04-16 17:44:36.447153+0800[25372:4016930] 線程:1>>><NSThread: 0x10050d420>{number = 1, name = main}

發(fā)現(xiàn)雖然都在主線程振惰,因?yàn)槭钱惒降母韪龋虼司€程2不用等待線程1執(zhí)行,因此不會(huì)造成死鎖骑晶。

案例三

接下來(lái)我們?cè)谥骶€程中痛垛,加入一個(gè)同步的全局隊(duì)列,我們來(lái)看看結(jié)果:

NSLog(@">>>>1>>>%@",[NSThread currentThread]);
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
    NSLog(@">>>>2>>>%@",[NSThread currentThread]);
});
NSLog(@">>>>3>>>%@",[NSThread currentThread]);
2019-04-18 10:00:31.070717+0800 block_test[43552:5006132] >>>>1>>><NSThread: 0x100705db0>{number = 1, name = main}
2019-04-18 10:00:31.071007+0800 block_test[43552:5006132] >>>>2>>><NSThread: 0x100705db0>{number = 1, name = main}
2019-04-18 10:00:31.071027+0800 block_test[43552:5006132] >>>>3>>><NSThread: 0x100705db0>{number = 1, name = main}

發(fā)現(xiàn)結(jié)果不會(huì)報(bào)錯(cuò)桶蛔,正常按順序打印匙头,因?yàn)橹骶€程的主隊(duì)列和主線程的全局隊(duì)列不在同一個(gè)隊(duì)列中,因此不會(huì)出現(xiàn)死鎖的現(xiàn)象仔雷。當(dāng)執(zhí)行dispatch_sync的時(shí)候蹂析,系統(tǒng)阻塞主隊(duì)列,并且執(zhí)行全局隊(duì)列碟婆,當(dāng)全局隊(duì)列執(zhí)行完电抚,再回到主隊(duì)列中繼續(xù)執(zhí)行。

案例四

為了驗(yàn)證串行和并行是否是死鎖的一個(gè)條件竖共,我們?cè)趤?lái)看兩個(gè)例子蝙叛,首先是在同一個(gè)線程中使用串行隊(duì)列去執(zhí)行。

NSLog(@">>>>1>>>%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
    NSLog(@">>>>2>>>%@",[NSThread currentThread]);
    dispatch_sync(queue, ^{
        NSLog(@">>>>3>>>%@",[NSThread currentThread]);
    });
});
NSLog(@">>>>4>>>%@",[NSThread currentThread]);

結(jié)果直接崩潰公给,說(shuō)明了串行隊(duì)列確實(shí)是會(huì)造成死鎖借帘。

NSLog(@">>>>1>>>%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
    NSLog(@">>>>2>>>%@",[NSThread currentThread]);
    dispatch_sync(queue, ^{
        NSLog(@">>>>3>>>%@",[NSThread currentThread]);
    });
});
NSLog(@">>>>4>>>%@",[NSThread currentThread]);
2019-04-18 10:07:51.532307+0800 block_test[43623:5018697] >>>>1>>><NSThread: 0x100605db0>{number = 1, name = main}
2019-04-18 10:07:51.532575+0800 block_test[43623:5018697] >>>>2>>><NSThread: 0x100605db0>{number = 1, name = main}
2019-04-18 10:07:51.532620+0800 block_test[43623:5018697] >>>>3>>><NSThread: 0x100605db0>{number = 1, name = main}
2019-04-18 10:07:51.532665+0800 block_test[43623:5018697] >>>>4>>><NSThread: 0x100605db0>{number = 1, name = main}

發(fā)現(xiàn)結(jié)果打印出來(lái)的是正常的,正好驗(yàn)證了我們的猜想淌铐。

因此滿足死鎖需要具備兩個(gè)條件:

  • 在同一個(gè)線程中肺然,出現(xiàn)多個(gè)同步任務(wù),并且任務(wù)之間存在嵌套關(guān)系
  • 存在嵌套關(guān)系的任務(wù)腿准,是在同一個(gè)隊(duì)列中

4际起、dispatch_once

說(shuō)到單例相信大家都不會(huì)陌生,在項(xiàng)目中,經(jīng)常性的使用到單例加叁,在應(yīng)用程序的生命周期內(nèi)僅執(zhí)行一次塊對(duì)象倦沧。網(wǎng)上有非常多的對(duì)于單例的使用這里就不提。
在使用單例中它匕,可以通過(guò)dispatch_once或者dispatch_once_f的方式展融,兩者的區(qū)別只是在于,前者是以block的方式去設(shè)置單例的方法豫柬;后者則是可以添加C函數(shù)的方式設(shè)置告希,dispatch _function _t類型。

+(Person *)sharedInstance
{
    static Person *sharedManager;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedManager = [[Person alloc] init];
    });
 //   dispatch_once_f(&onceToken, nil, test);
    return sharedManager;
}

void test(void *context) {
    int *c = context;
    NSLog(@"%d", *c);
}
dispatch_once也會(huì)造成死鎖
+(TestA *)sharedInstance
{
    static TestA *sharedManager;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedManager = [[TestA alloc] init];
        TestB *test = [self sharedInstance2];
    });
    return sharedManager;
}

+(TestB *)sharedInstance2
{
    static TestB *sharedManager;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedManager = [[TestB alloc] init];
        TestA *test = [self sharedInstance];
    });
    
    return sharedManager;
}

死鎖的原因是因?yàn)閐ispatch_once底層是用信號(hào)量去控制線程的釋放烧给,首先會(huì)先判斷是否是第一個(gè)運(yùn)行燕偶,如果是的情況,執(zhí)行block內(nèi)部的方法础嫡,并做一個(gè)死循環(huán)去監(jiān)控是否有其他線程進(jìn)入該block函數(shù)中指么,如果有就建立信號(hào)鏈表,用兩個(gè)指針指向信號(hào)頭尾榴鼎,在死循環(huán)中伯诬,逐一的處理信號(hào)。但是發(fā)現(xiàn)兩個(gè)方法互相調(diào)用過(guò)程巫财,信號(hào)量是互相等待盗似,造成死鎖的情況。
順便說(shuō)下平项,當(dāng)非第一個(gè)調(diào)用dispatch_once赫舒,則進(jìn)入另一個(gè)死循環(huán),如果block的請(qǐng)求還沒(méi)有結(jié)束闽瓢,則將后續(xù)的操作添加到鏈表中進(jìn)行處理接癌,處理完跳出死循環(huán)。

5扣讼、dispatch_apply

dispatch_apply函數(shù)是dispatch_sync函數(shù)和Dispatch Group的關(guān)聯(lián)API,將單個(gè)塊提交到調(diào)度隊(duì)列缺猛,并使塊執(zhí)行指定的次數(shù)。該函數(shù)按指定的次數(shù)將指定的Block追加到指定的Dispatch Queue中,并等到全部的處理執(zhí)行結(jié)束

void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));

dispatch_apply是并行的調(diào)度方法届谈,用戶使用該方法,可以進(jìn)行無(wú)序多次循環(huán)的操作弯汰,并且會(huì)阻斷主線程的運(yùn)行等待所有循環(huán)都操作完之后才會(huì)繼續(xù)接下去的主線程操作:

NSLog(@"開(kāi)始");
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_apply(10, queue, ^(size_t size) {
    NSLog(@">>>>  %zu  >>>%@",size,[NSThread currentThread]);
});
NSLog(@"結(jié)束");
2019-04-24 09:32:23.261395+0800 block_test[13160:426551] 開(kāi)始
2019-04-24 09:32:23.262115+0800 block_test[13160:426551] >>>>  0  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:32:23.262180+0800 block_test[13160:426551] >>>>  4  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:32:23.262215+0800 block_test[13160:426551] >>>>  5  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:32:23.262226+0800 block_test[13160:426587] >>>>  1  >>><NSThread: 0x102807010>{number = 2, name = (null)}
2019-04-24 09:32:23.262232+0800 block_test[13160:426551] >>>>  6  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:32:23.262232+0800 block_test[13160:426595] >>>>  3  >>><NSThread: 0x10070a6b0>{number = 3, name = (null)}
2019-04-24 09:32:23.262251+0800 block_test[13160:426551] >>>>  7  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:32:23.262258+0800 block_test[13160:426587] >>>>  8  >>><NSThread: 0x102807010>{number = 2, name = (null)}
2019-04-24 09:32:23.262265+0800 block_test[13160:426595] >>>>  9  >>><NSThread: 0x10070a6b0>{number = 3, name = (null)}
2019-04-24 09:32:23.262275+0800 block_test[13160:426590] >>>>  2  >>><NSThread: 0x10070aa90>{number = 4, name = (null)}
2019-04-24 09:32:23.282560+0800 block_test[13160:426551] 結(jié)束

從打印的結(jié)果上艰山,可以看出dispatch_apply是在多線程上操作,并且會(huì)再新線程全部結(jié)束后咏闪,再做主線程刷新操作曙搬。
在官方文檔的,提供了DISPATCH_APPLY_AUTO,并建議使用此參數(shù)作為queue纵装,因?yàn)檫@會(huì)導(dǎo)致任務(wù)在其服務(wù)質(zhì)量類(QOS)最適合當(dāng)前執(zhí)行上下文的隊(duì)列上運(yùn)行征讲。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        
        dispatch_apply(10, queue, ^(size_t index) {
            NSLog(@">>>>  %zu  >>>%@",index,[NSThread currentThread]);
        });
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"結(jié)束");
        });
    });

當(dāng)然如果隊(duì)列是串行的方法也是等到串行結(jié)束后再執(zhí)行

NSLog(@"開(kāi)始");
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);
dispatch_apply(10, queue, ^(size_t index) {
    NSLog(@">>>>  %zu  >>>%@",index,[NSThread currentThread]);
});
NSLog(@"結(jié)束");
2019-04-24 09:54:06.183587+0800 block_test[13419:450661] 開(kāi)始
2019-04-24 09:54:06.184322+0800 block_test[13419:450661] >>>>  0  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184432+0800 block_test[13419:450661] >>>>  1  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184464+0800 block_test[13419:450661] >>>>  2  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184496+0800 block_test[13419:450661] >>>>  3  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184511+0800 block_test[13419:450661] >>>>  4  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184550+0800 block_test[13419:450661] >>>>  5  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184593+0800 block_test[13419:450661] >>>>  6  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184607+0800 block_test[13419:450661] >>>>  7  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184622+0800 block_test[13419:450661] >>>>  8  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184636+0800 block_test[13419:450661] >>>>  9  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.188585+0800 block_test[13419:450661] 結(jié)束

6、dispatch_queue_get_label 和 dispatch_set_target_queue 和 dispatch_queue_set_specific

dispatch_queue_get_label 是用來(lái)獲取隊(duì)列標(biāo)簽橡娄,并且系統(tǒng)也提供一個(gè)關(guān)鍵詞DISPATCH_CURRENT_QUEUE_LABEL用來(lái)獲取當(dāng)前隊(duì)列的標(biāo)簽诗箍,使用如下:

dispatch_queue_t queue = dispatch_queue_create("this is a queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
    NSLog(@">> 1 >> %s", dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
});
NSLog(@">> 2 >> %s", dispatch_queue_get_label(queue));
NSLog(@">> 3 >> %s", dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));

dispatch_set_target_queue 這個(gè)參數(shù),將給一個(gè)隊(duì)列設(shè)置目標(biāo)隊(duì)列挽唉,object是要修改的對(duì)象(不能指定主隊(duì)列和全局隊(duì)列)滤祖,queue是目標(biāo)對(duì)象隊(duì)列。如果希望系統(tǒng)提供適合當(dāng)前對(duì)象的隊(duì)列瓶籽,請(qǐng)指定DISPATCH_TARGET_QUEUE_DEFAULT

void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);

那么設(shè)置成目標(biāo)對(duì)象的隊(duì)列即object參數(shù)隊(duì)列會(huì)變成什么樣呢匠童?
對(duì)于一個(gè)已經(jīng)存在的隊(duì)列去改變隊(duì)列的優(yōu)先級(jí)和qos的設(shè)定,使其與目標(biāo)隊(duì)列一致塑顺。

dispatch_queue_t targetQueue = dispatch_queue_create("targetQueue", DISPATCH_QUEUE_SERIAL);//目標(biāo)隊(duì)列
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);//串行隊(duì)列
    dispatch_queue_t queue2 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并發(fā)隊(duì)列
    //設(shè)置參考
    dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(queue2, targetQueue);
    
    dispatch_async(queue2, ^{
        NSLog(@"job3 in %@",[NSThread currentThread]);
    });
    dispatch_async(queue2, ^{
        NSLog(@"job2 in %@",[NSThread currentThread]);
    });
    dispatch_async(queue1, ^{
        NSLog(@"job1 in %@",[NSThread currentThread]);
    });

打印出來(lái)的結(jié)果是串行同步輸出:

2019-04-24 10:54:32.406594+0800 test_1[1514:2485209] job3 in <NSThread: 0x1c0660600>{number = 3, name = (null)}
2019-04-24 10:54:32.406735+0800 test_1[1514:2485209] job2 in <NSThread: 0x1c0660600>{number = 3, name = (null)}
2019-04-24 10:54:32.407308+0800 test_1[1514:2485209] job1 in <NSThread: 0x1c0660600>{number = 3, name = (null)}

設(shè)置目標(biāo)隊(duì)列時(shí)汤求,請(qǐng)勿在隊(duì)列層次結(jié)構(gòu)中創(chuàng)建周期。換句話說(shuō)严拒,不要將隊(duì)列A的目標(biāo)設(shè)置為隊(duì)列B扬绪,并將隊(duì)列B的目標(biāo)設(shè)置為隊(duì)列A.例如:

// 以下是錯(cuò)誤的使用方式
dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(targetQueue, queue1);

dispatch_queue_set_specific 給當(dāng)前的隊(duì)列設(shè)定一個(gè)標(biāo)識(shí),使用方法:

static void *queueKey1 = "queueKey1";
dispatch_queue_t queue1 = dispatch_queue_create(queueKey1, DISPATCH_QUEUE_SERIAL);
dispatch_queue_set_specific(queue1, queueKey1, &queueKey1, NULL);

NSLog(@"當(dāng)前隊(duì)列是: %@ 糙俗。",dispatch_get_current_queue());
dispatch_sync(queue1, ^{
    [NSThread sleepForTimeInterval:1];
    
    if (dispatch_get_specific(queueKey1)) {
        //當(dāng)前隊(duì)列是queue1隊(duì)列勒奇,所以能取到queueKey1對(duì)應(yīng)的值,故而執(zhí)行
        NSLog(@"當(dāng)前隊(duì)列是: %@ 巧骚。",dispatch_get_current_queue());
    }else{
        NSLog(@"當(dāng)前隊(duì)列是: %@ 赊颠。",dispatch_get_current_queue());
    }
});

6、DispatchWorkItem(dispatch_block_t)派遣工作調(diào)度塊

GCD允許對(duì)調(diào)度回調(diào)block工作進(jìn)行劈彪,以附加或執(zhí)行依賴項(xiàng)的方式封裝觸發(fā)(dispatch_block_t)竣蹦。調(diào)度工作項(xiàng)封裝要在調(diào)度隊(duì)列queue或調(diào)度組group內(nèi)執(zhí)行的工作。您還可以將工作項(xiàng)用作調(diào)度源事件沧奴,注冊(cè)或取消處理程序痘括。

dispatch_block_t dispatch_block_create(dispatch_block_flags_t flags, dispatch_block_t block);
//使用現(xiàn)有塊和給定標(biāo)志在堆上創(chuàng)建新的調(diào)度塊。

dispatch_block_t dispatch_block_create_with_qos_class(dispatch_block_flags_t flags, dispatch_qos_class_t qos_class, int relative_priority, dispatch_block_t block);
//從現(xiàn)有塊和給定標(biāo)志創(chuàng)建新的調(diào)度塊滔吠,并為其分配指定的服務(wù)質(zhì)量類和相對(duì)優(yōu)先級(jí)纲菌。

dispatch_block_t
//提交給調(diào)度隊(duì)列的塊的原型,它不帶參數(shù)且沒(méi)有返回值疮绷。

dispatch_block_flags_t
DISPATCH_ENUM(dispatch_block_flags, unsigned long,
    DISPATCH_BLOCK_BARRIER = 0x1,
    DISPATCH_BLOCK_DETACHED = 0x2,
    DISPATCH_BLOCK_ASSIGN_CURRENT = 0x4,
    DISPATCH_BLOCK_NO_QOS_CLASS = 0x8,
    DISPATCH_BLOCK_INHERIT_QOS_CLASS = 0x10,
    DISPATCH_BLOCK_ENFORCE_QOS_CLASS = 0x20,
);
// 創(chuàng)建派遣標(biāo)志翰舌,來(lái)區(qū)分派遣調(diào)度block的設(shè)置處理事務(wù)

dispatch_block_perform
//從指定的塊和標(biāo)志創(chuàng)建,同步執(zhí)行和釋放調(diào)度塊冬骚。

添加完成處理程序
dispatch_block_notify
//在完成指定的調(diào)度塊的執(zhí)行時(shí)椅贱,計(jì)劃要提交到隊(duì)列的通知塊懂算。

延遲執(zhí)行工作項(xiàng)
dispatch_block_wait
//同步等待,直到指定的調(diào)度塊的執(zhí)行完成或者直到指定的超時(shí)已經(jīng)過(guò)去庇麦。

取消工作項(xiàng)
dispatch_block_cancel
//異步取消指定的調(diào)度塊计技。

dispatch_block_testcancel
//測(cè)試是否已取消給定的調(diào)度塊。

使用的方式山橄,創(chuàng)建dispatch_block_t垮媒,并在隊(duì)列執(zhí)行中,添加block:

dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并發(fā)隊(duì)列
dispatch_block_t block = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
    NSLog(@">>>>>1");
    sleep(2);
    NSLog(@">>>>>>2");
});
dispatch_async(queue, block);

當(dāng)使用dispatch_block_cancel驾胆,如果block還未執(zhí)行時(shí)允許取消block執(zhí)行的方法涣澡,正在執(zhí)行的block不能取消。例如下丧诺,最后沒(méi)有打印數(shù)據(jù)因?yàn)槿牍穑呀?jīng)取消執(zhí)行block的方法:

dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并發(fā)隊(duì)列
dispatch_block_t block = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
    NSLog(@">>>>>1");
    sleep(2);
    NSLog(@">>>>>>2");
});
dispatch_async(queue, block);
dispatch_block_cancel(block);

dispatch_block_wait是阻塞當(dāng)前線程去執(zhí)行block的操作,其規(guī)則是計(jì)時(shí)器的方式驳阎,設(shè)定計(jì)時(shí)器dispatch_time_t抗愁,在計(jì)時(shí)器結(jié)束之前,如果block執(zhí)行完畢呵晚,則返回0蜘腌,或者計(jì)時(shí)器計(jì)算后執(zhí)行當(dāng)前線程返回1,可用來(lái)設(shè)置在指定時(shí)間內(nèi)是否請(qǐng)求超時(shí):

dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并發(fā)隊(duì)列
dispatch_block_t block = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
    NSLog(@">>>>>1");
    sleep(2);
    NSLog(@">>>>>>2");
});
dispatch_async(queue, block);

dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
long result = dispatch_block_wait(block, timeout);
if (result == 0) {
    NSLog(@"執(zhí)行成功");
} else {
    NSLog(@"執(zhí)行超時(shí)");
}

舉一個(gè)例子饵隙,在一個(gè)dispatch_block_t對(duì)象撮珠,并且設(shè)置2秒超時(shí),并在超時(shí)中做取消block的操作金矛,看看結(jié)果:

dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并發(fā)隊(duì)列
dispatch_block_t block = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
    NSLog(@">>>>>1");
    sleep(5);
    NSLog(@">>>>>>2");
});
dispatch_async(queue, block);

dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
long resutl = dispatch_block_wait(block, timeout);
if (resutl == 0) {
    NSLog(@"執(zhí)行成功");
} else {
    NSLog(@"執(zhí)行超時(shí)");
    dispatch_block_cancel(block);
}
2019-04-24 14:29:18.940770+0800 test_1[1603:2562465] >>>>>1
2019-04-24 14:29:20.941842+0800 test_1[1603:2562400] 執(zhí)行超時(shí)
2019-04-24 14:29:23.946158+0800 test_1[1603:2562465] >>>>>>2
2019-04-24 14:29:23.946454+0800 test_1[1603:2562458] 結(jié)束

打印的結(jié)果是請(qǐng)求超時(shí)了芯急,因?yàn)閎lock內(nèi)部執(zhí)行了5秒,dispatch_block_cancel在執(zhí)行過(guò)程中也沒(méi)辦法關(guān)閉block正好驗(yàn)證我們的答案驶俊。

dispatch_block_notify

是在block執(zhí)行結(jié)束后會(huì)調(diào)用該函數(shù)進(jìn)行后面的處理娶耍,即使調(diào)用了dispatch_block_cancel,也會(huì)進(jìn)入該方法中饼酿,通常用來(lái)阻塞當(dāng)前線程榕酒,等待block操作完,DISPATCH_TIME_NOW故俐,DISPATCH_TIME_FOREVER表示計(jì)時(shí)器無(wú)限時(shí)間想鹰,即永遠(yuǎn)阻塞必須等到group執(zhí)行完才能執(zhí)行。药版。

dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并發(fā)隊(duì)列
dispatch_block_t block = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
    NSLog(@">>>>>1");
    sleep(5);
    NSLog(@">>>>>>2");
});
dispatch_async(queue, block);

dispatch_block_notify(block, queue, ^{
    NSLog(@"結(jié)束");
});

7辑舷、Dispatch Group(調(diào)度組)

創(chuàng)建調(diào)度組
dispatch_group_create
//創(chuàng)建一個(gè)可以為其分配塊對(duì)象的新組。

dispatch_group_t
//提交到隊(duì)列以進(jìn)行異步調(diào)用的一組塊對(duì)象刚陡。

OS_dispatch_group
將工作添加到組中
dispatch_group_async
//異步調(diào)度塊以執(zhí)行惩妇,同時(shí)將其與指定的調(diào)度組關(guān)聯(lián)。

dispatch_group_async_f
//將應(yīng)用程序定義的函數(shù)提交給調(diào)度隊(duì)列筐乳,并將其與指定的調(diào)度組關(guān)聯(lián)歌殃。

添加完成處理程序
dispatch_group_notify
//當(dāng)一組先前提交的塊對(duì)象完成時(shí),計(jì)劃將塊對(duì)象提交到隊(duì)列蝙云。

dispatch_group_notify_f
//計(jì)劃在一組先前提交的塊對(duì)象完成時(shí)將應(yīng)用程序定義的函數(shù)提交到隊(duì)列氓皱。

等待任務(wù)完成執(zhí)行
dispatch_group_wait
//同步等待先前提交的塊對(duì)象完成; 如果塊在指定的超時(shí)時(shí)間段之前未完成,則返回勃刨。

手動(dòng)更新組
dispatch_group_enter
//顯式指示塊已進(jìn)入組波材。

dispatch_group_leave
//顯式指示組中的塊已完成執(zhí)行。

在平時(shí)簡(jiǎn)單的使用過(guò)程中身隐,前面的一些方式基本可以完成廷区,但是在針對(duì)復(fù)雜的請(qǐng)求,為了能在多線程中體現(xiàn)和更好的去操作贾铝,GCD提供的組隊(duì)列的方式隙轻,對(duì)用戶的邏輯操作使用dispatch_group_t,可以解決一些問(wèn)題垢揩。

dispatch_group允許您合并一組任務(wù)并同步group上的行為玖绿。將多個(gè)塊附加到dispatch_group_t并將它們安排在同一隊(duì)列或不同隊(duì)列上進(jìn)行異步執(zhí)行。當(dāng)所有塊完成執(zhí)行后叁巨,該組將執(zhí)行其完成處理程序斑匪。您也可以同步等待組中的所有塊完成執(zhí)行。

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并發(fā)隊(duì)列
for (int index = 0; index < 10; index ++) {
        dispatch_group_async(group, queue, ^{
            // 里面不能使用異步锋勺,除非與dispatch_group_enter一起用
            NSLog(@"開(kāi)始請(qǐng)求 >> %d",index);
        });
}
dispatch_group_notify(group, queue, ^{
    NSLog(@"———————————— 打印結(jié)果 ——————————————");
});

第二種dispatch_group_enter 和dispatch_group_leave的方式去控制group是否全部執(zhí)行完蚀瘸。

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并發(fā)隊(duì)列
    for (int index = 0; index < 10; index ++) {
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            NSLog(@"%d",index);
            sleep(2);
            NSLog(@"%d >> 結(jié)束",index);
            dispatch_group_leave(group);
        });
    }
dispatch_group_notify(group, queue, ^{
    NSLog(@"———————————— 打印結(jié)果 ——————————————");
});

dispatch_group_notify是在執(zhí)行完之后的通知,為了能執(zhí)行完畢宙刘,dispatch_group_enter 和dispatch_group_leave必須成對(duì)出現(xiàn)否則會(huì)崩潰苍姜。
dispatch_group_wait 同樣和dispatch_block_wait一樣,是用來(lái)阻塞當(dāng)前線程的悬包,只有當(dāng)計(jì)時(shí)器結(jié)束(返回1)衙猪,或者group執(zhí)行完畢(返回0)才會(huì)繼續(xù)執(zhí)行線程后的操作,DISPATCH_TIME_NOW,DISPATCH_TIME_FOREVER表示計(jì)時(shí)器無(wú)限時(shí)間布近,即永遠(yuǎn)阻塞必須等到group執(zhí)行完才能執(zhí)行垫释。

8、Dispatch Semaphore(信號(hào)量)和 Dispatch Barrier

信號(hào)量是什么撑瞧,信號(hào)量是為了控制等待功能可以提前釋放而存在的棵譬,用來(lái)對(duì)線程的控制計(jì)數(shù)器。dispatch_semaphore_wait(等待预伺,信號(hào)量遞減) 或 dispatch_semaphore_signal (發(fā)送订咸,信號(hào)量遞增)阻塞線程的同時(shí)曼尊,可以在用戶認(rèn)為操作完后可以將阻塞線程手動(dòng)釋放而存在的。
調(diào)度信號(hào)量是傳統(tǒng)計(jì)數(shù)信號(hào)量的有效實(shí)現(xiàn)脏嚷。僅當(dāng)需要阻止調(diào)用線程時(shí)骆撇,Dispatch信號(hào)量才會(huì)調(diào)用內(nèi)核。如果調(diào)用信號(hào)量不需要阻塞父叙,則不進(jìn)行內(nèi)核調(diào)用神郊。

    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);

dispatch_semaphore_signal(sem);
dispatch_async(queue, ^{
    NSLog(@"111");
    sleep(2);
    dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

dispatch_async(queue, ^{
    NSLog(@"222");
    sleep(2);
    dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

dispatch_async(queue, ^{
    NSLog(@"333");
    sleep(2);
});
2019-04-24 17:35:29.780870+0800 test_1[1667:2627449] 111
2019-04-24 17:35:31.786097+0800 test_1[1667:2627449] 222
2019-04-24 17:35:33.800574+0800 test_1[1667:2627450] 333

根據(jù)打印結(jié)果可以知道,當(dāng)執(zhí)行并行異步的時(shí)候趾唱,原本是同步進(jìn)行的線程涌乳,因?yàn)閳?zhí)行了dispatch_semaphore_wait阻塞了線程后面的執(zhí)行,又使用dispatch_semaphore_signal去回復(fù)線程執(zhí)行甜癞,將原本并行的線程夕晓,通過(guò)這種方式變成串行的線程。
對(duì)信號(hào)量的使用在復(fù)雜的需求上經(jīng)常性的會(huì)出現(xiàn)悠咱,后面我會(huì)根據(jù)一些實(shí)戰(zhàn)讓大家更了解运授。

Dispatch Barrier

使用屏障同步調(diào)度隊(duì)列中一個(gè)或多個(gè)任務(wù)的執(zhí)行。向并發(fā)調(diào)度隊(duì)列添加屏障時(shí)乔煞,隊(duì)列會(huì)延遲屏障塊(以及屏障后提交的任何任務(wù))的執(zhí)行吁朦,直到所有先前提交的任務(wù)完成執(zhí)行。在前面的任務(wù)完成執(zhí)行之后渡贾,隊(duì)列自己執(zhí)行屏障塊逗宜。一旦屏障塊完成,隊(duì)列將恢復(fù)其正常執(zhí)行行為空骚。

dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);
for (int i=0; i<3; i++) {
    dispatch_async(queue, ^{
        NSLog(@"111 >>> %@", [NSThread currentThread]);
    });
}
dispatch_barrier_async(queue, ^{
    NSLog(@"barrier結(jié)束了");
});
dispatch_async(queue, ^{
    NSLog(@"333 >>> %@", [NSThread currentThread]);
});    
2019-04-24 17:57:28.864959+0800 test_1[1696:2637202] 111 >>> <NSThread: 0x1c4079a00>{number = 4, name = (null)}
2019-04-24 17:57:28.864959+0800 test_1[1696:2637201] 111 >>> <NSThread: 0x1c4079880>{number = 3, name = (null)}
2019-04-24 17:57:28.865091+0800 test_1[1696:2637202] 111 >>> <NSThread: 0x1c4079a00>{number = 4, name = (null)}
2019-04-24 17:57:28.865637+0800 test_1[1696:2637199] barrier結(jié)束了
2019-04-24 17:57:28.865707+0800 test_1[1696:2637199] 333 >>> <NSThread: 0x1c04682c0>{number = 5, name = (null)}

從打印的結(jié)果可以看出纺讲,雖然是異步并行的隊(duì)列,但是前面的循環(huán)在沒(méi)執(zhí)行完之前囤屹,后面的都不會(huì)執(zhí)行熬甚,就是因?yàn)閐ispatch_barrier_async起到了作用,會(huì)阻隔后面的操作肋坚。

dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);
for (int i=0; i<3; i++) {
    dispatch_async(queue, ^{
        NSLog(@"111 >>> %@", [NSThread currentThread]);
    });
}
dispatch_barrier_sync(queue, ^{
    NSLog(@"barrier結(jié)束了");
});
dispatch_sync(queue, ^{
    NSLog(@"333 >>> %@", [NSThread currentThread]);
});
2019-04-24 18:05:36.530328+0800 test_1[1712:2641128] 111 >>> <NSThread: 0x1c0279300>{number = 3, name = (null)}
2019-04-24 18:05:36.530422+0800 test_1[1712:2641128] 111 >>> <NSThread: 0x1c0279300>{number = 3, name = (null)}
2019-04-24 18:05:36.530460+0800 test_1[1712:2641128] 111 >>> <NSThread: 0x1c0279300>{number = 3, name = (null)}
2019-04-24 18:05:36.530498+0800 test_1[1712:2641110] barrier結(jié)束了
2019-04-24 18:05:36.530642+0800 test_1[1712:2641110] 333 >>> <NSThread: 0x1c006a300>{number = 1, name = main}

即使是同步串行的barrier 依然會(huì)阻塞當(dāng)前線程和后面的隊(duì)列乡括,等待前面隊(duì)列結(jié)束。

結(jié)束語(yǔ)

到此GCD的常用的對(duì)象和方法都介紹完了智厌,養(yǎng)兵千日用兵一時(shí)诲泌,自己根據(jù)上面的理解帶入到開(kāi)發(fā)中。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末铣鹏,一起剝皮案震驚了整個(gè)濱河市敷扫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌诚卸,老刑警劉巖葵第,帶你破解...
    沈念sama閱讀 222,627評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绘迁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡卒密,警方通過(guò)查閱死者的電腦和手機(jī)脊髓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)栅受,“玉大人,你說(shuō)我怎么就攤上這事恭朗∑聊鳎” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 169,346評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵痰腮,是天一觀的道長(zhǎng)而芥。 經(jīng)常有香客問(wèn)我,道長(zhǎng)膀值,這世上最難降的妖魔是什么棍丐? 我笑而不...
    開(kāi)封第一講書人閱讀 60,097評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮沧踏,結(jié)果婚禮上歌逢,老公的妹妹穿的比我還像新娘。我一直安慰自己翘狱,他們只是感情好秘案,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著潦匈,像睡著了一般阱高。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上茬缩,一...
    開(kāi)封第一講書人閱讀 52,696評(píng)論 1 312
  • 那天赤惊,我揣著相機(jī)與錄音,去河邊找鬼凰锡。 笑死未舟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的掂为。 我是一名探鬼主播处面,決...
    沈念sama閱讀 41,165評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼菩掏!你這毒婦竟也來(lái)了魂角?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 40,108評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤智绸,失蹤者是張志新(化名)和其女友劉穎野揪,沒(méi)想到半個(gè)月后访忿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,646評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡斯稳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評(píng)論 3 342
  • 正文 我和宋清朗相戀三年海铆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挣惰。...
    茶點(diǎn)故事閱讀 40,861評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡卧斟,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出憎茂,到底是詐尸還是另有隱情珍语,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布竖幔,位于F島的核電站板乙,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏拳氢。R本人自食惡果不足惜募逞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望馋评。 院中可真熱鬧放接,春花似錦、人聲如沸留特。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,698評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)磕秤。三九已至乳乌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間市咆,已是汗流浹背汉操。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,804評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蒙兰,地道東北人磷瘤。 一個(gè)月前我還...
    沈念sama閱讀 49,287評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像搜变,于是被迫代替她去往敵國(guó)和親采缚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評(píng)論 2 361

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