OC-多線程GCD

參考:
GCD源碼
深入理解 GCD
iOS多線程--徹底學(xué)會多線程之『GCD』
關(guān)于iOS多線程,我說,你聽,沒準(zhǔn)你就懂了

任務(wù)執(zhí)行方式(同步适荣、異步)

一般理解

1现柠、同步執(zhí)行(dispatch_sync):只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力弛矛。必須等到Block函數(shù)執(zhí)行完畢后够吩,dispatch函數(shù)才會返回。
2丈氓、異步執(zhí)行(dispatch_async):可以在新的線程中執(zhí)行任務(wù)周循,具備開啟新線程的能力。dispatch函數(shù)會立即返回, 然后Block在后臺異步執(zhí)行万俗。

高級理解

1湾笛、dispatch_sync 的實(shí)現(xiàn)略簡單一些,它不涉及線程池(因此一般都在當(dāng)前線程執(zhí)行)闰歪,而是利用與線程綁定的信號量來實(shí)現(xiàn)串行
2嚎研、dispatch_async 會把任務(wù)添加到隊(duì)列的一個(gè)鏈表中,添加完后會喚醒隊(duì)列库倘,根據(jù) vtable 中的函數(shù)指針临扮,調(diào)用 wakeup 方法。在 wakeup 方法中于樟,從線程池里取出工作線程(如果沒有就新建)公条,然后在工作線程中取出鏈表頭部指向的 block 并執(zhí)行。

任務(wù)管理方式(串行隊(duì)列迂曲、并行隊(duì)列)

1靶橱、串行隊(duì)列:按照FIFO(先進(jìn)先出)的原則,每次只能有一個(gè)任務(wù)執(zhí)行路捧,等待執(zhí)行完畢后才會執(zhí)行下一個(gè)任務(wù)
2关霸、并行隊(duì)列:按照FIFO(先進(jìn)先出)的原則,把任務(wù)拿出來執(zhí)行杰扫,不需要等待前一個(gè)任務(wù)執(zhí)行情況队寇。
外: dispatch_async 加入主隊(duì)列的任務(wù)由 runloop 處理,加入其他隊(duì)列由線程池處理

    // 主隊(duì)列--串行,所有放在主隊(duì)列中的任務(wù)章姓,都會放到主線程中執(zhí)行
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    // 全局隊(duì)列--并行佳遣,系統(tǒng)為我們創(chuàng)建好的一個(gè)并行隊(duì)列,使用起來與我們自己創(chuàng)建的并行隊(duì)列無本質(zhì)差別
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    
    // new串行隊(duì)列
    dispatch_queue_t queue1 = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
    
    // new并行隊(duì)列
    dispatch_queue_t queue2 = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);

注意:避免使用 GCD Global隊(duì)列創(chuàng)建Runloop常駐線程
全局隊(duì)列的底層是一個(gè)線程池(線程數(shù)有限)凡伊,向全局隊(duì)列中提交的 block零渐,都會被放到這個(gè)線程池中執(zhí)行,如果線程池已滿系忙,后續(xù)再提交 block 就不會再重新創(chuàng)建線程诵盼。等待有空閑的線程在執(zhí)行任務(wù)。
所以:
避免使用 GCD Global 隊(duì)列創(chuàng)建 Runloop 常駐線程,如果n條線程都被霸占了风宁,Global隊(duì)列就費(fèi)了洁墙。

dispatch_group (組)

dispatch_group 的本質(zhì)就是一個(gè) value 非常大的信號量,等待 group 完成實(shí)際上就是等待 value 恢復(fù)初始值戒财。而 notify 的作用是將所有注冊的回調(diào)組裝成一個(gè)鏈表热监,在 dispatch_async 完成時(shí)判斷 value 是不是恢復(fù)初始值,如果是則調(diào)用 dispatch_async 異步執(zhí)行所有注冊的回調(diào)固翰。

dispatch_once (單次)

dispatch_once 通過一個(gè)靜態(tài)變量來標(biāo)記 block 是否已被執(zhí)行狼纬,同時(shí)使用信號量確保只有一個(gè)線程能執(zhí)行,執(zhí)行完 block 后會喚醒其他所有等待的線程

dispatch_barrier_async (柵欄)

dispatch_barrier_async 改變了 block 的 vtable 標(biāo)記位骂际,當(dāng)它將要被取出執(zhí)行時(shí)疗琉,會等待前面的 block 都執(zhí)行完,然后在下一次循環(huán)中被執(zhí)行

任務(wù)+隊(duì)列

串行隊(duì)列 并行隊(duì)列 主隊(duì)列
同步(sync) 當(dāng)前線程歉铝,串行執(zhí)行 隊(duì)列當(dāng)前線程盈简,串行執(zhí)行 主線程,串行執(zhí)行(注意死鎖)
異步(async) 開1條新線程太示,串行執(zhí)行 開n條新線程柠贤,異步執(zhí)行(n在iphone7上面最大是幾十個(gè)) 主線程,串行執(zhí)行
判斷當(dāng)前隊(duì)列
    static char *queueKey = "queueKey";
    dispatch_queue_t queue1 = dispatch_queue_create(queueKey, nil);
    dispatch_queue_set_specific(queue, queueKey, &queueKey, NULL); // 設(shè)置標(biāo)識
    dispatch_sync(queue1, ^{
        
        if (dispatch_get_specific(queueKey)) {
            //說明當(dāng)前的隊(duì)列就是queue1
        }else{
            //說明當(dāng)前的隊(duì)列不是是queue1
        }
    });
容易誤解的概念

1类缤、主線程只會執(zhí)行主隊(duì)列的任務(wù)--(主線程也可以執(zhí)行其他隊(duì)列臼勉,比如sync執(zhí)行其他隊(duì)列)

dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

dispatch_queue_set_specific(mainQueue, "key", "main", NULL);
dispatch_sync(globalQueue, ^{
    BOOL res1 = [NSThread isMainThread];
    BOOL res2 = dispatch_get_specific("key") != NULL;
    
    NSLog(@"is main thread: %zd --- is main queue: %zd", res1, res2);
});

根據(jù)正常邏輯的理解來說,這里的兩個(gè)判斷結(jié)果應(yīng)該都是NO餐弱,但運(yùn)行后宴霸,第一個(gè)判斷為YES,后者為NO膏蚓,輸出說明了主線程此時(shí)執(zhí)行了work queue的任務(wù).

Dispatch Block

隊(duì)列執(zhí)行任務(wù)都是block的方式瓢谢,

創(chuàng)建block
- (void)createDispatchBlock {
    // 一般的block
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT);
    dispatch_block_t block = dispatch_block_create(0, ^{
        NSLog(@"run block");
    });
    dispatch_async(concurrentQueue, block);

    //QOS優(yōu)先級的block
    dispatch_block_t qosBlock = dispatch_block_create_with_qos_class(0, QOS_CLASS_USER_INITIATED, -1, ^{
        NSLog(@"run qos block");
    });
    dispatch_async(concurrentQueue, qosBlock);
}

dispatch_block_wait:可以根據(jù)dispatch block來設(shè)置等待時(shí)間,參數(shù)DISPATCH_TIME_FOREVER會一直等待block結(jié)束

- (void)dispatchBlockWaitDemo {
    dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_block_t block = dispatch_block_create(0, ^{
        NSLog(@"star");
        [NSThread sleepForTimeInterval:5.f];
        NSLog(@"end");
    });
    dispatch_async(serialQueue, block);
    //設(shè)置DISPATCH_TIME_FOREVER會一直等到前面任務(wù)都完成
    dispatch_block_wait(block, DISPATCH_TIME_FOREVER);
    NSLog(@"ok, now can go on");
}

dispatch_block_notify:可以監(jiān)視指定dispatch block結(jié)束驮瞧,然后再加入一個(gè)block到隊(duì)列中氓扛。三個(gè)參數(shù)分別為,第一個(gè)是需要監(jiān)視的block论笔,第二個(gè)參數(shù)是需要提交執(zhí)行的隊(duì)列采郎,第三個(gè)是待加入到隊(duì)列中的block

- (void)dispatchBlockNotifyDemo {
    dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_block_t firstBlock = dispatch_block_create(0, ^{
        NSLog(@"first block start");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"first block end");
    });
    dispatch_async(serialQueue, firstBlock);
    dispatch_block_t secondBlock = dispatch_block_create(0, ^{
        NSLog(@"second block run");
    });
    //first block執(zhí)行完才在serial queue中執(zhí)行second block
    dispatch_block_notify(firstBlock, serialQueue, secondBlock);
}

dispatch_block_cancel:iOS8之后可以調(diào)用dispatch_block_cancel來取消(需要注意必須用dispatch_block_create創(chuàng)建dispatch_block_t)
需要注意的是,未執(zhí)行的可以用此方法cancel掉狂魔,若已經(jīng)執(zhí)行則cancel不了
如果想中斷(interrupt)線程尉剩,可以使用dispatch_block_testcancel方法

- (void)dispatchBlockCancelDemo {
    dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_block_t firstBlock = dispatch_block_create(0, ^{
        NSLog(@"first block start");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"first block end");
    });
    dispatch_block_t secondBlock = dispatch_block_create(0, ^{
        NSLog(@"second block run");
    });
    dispatch_async(serialQueue, firstBlock);
    dispatch_async(serialQueue, secondBlock);
    //取消secondBlock
    dispatch_block_cancel(secondBlock);
}

1. 串行隊(duì)列 + 同步執(zhí)行

不會開啟新線程,在當(dāng)前線程執(zhí)行任務(wù)毅臊。任務(wù)是串行的,執(zhí)行完一個(gè)任務(wù),再執(zhí)行下一個(gè)任務(wù)

- (void)serialQueueSync{
    
    NSLog(@"0========%@",[NSThread currentThread]);
    dispatch_queue_t serialQueue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serialQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
    });
    NSLog(@"4========%@",[NSThread currentThread]);
    
    /*
     NSLog輸出
     0========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     1========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     2========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     3========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     4========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     */
     
}
2. 串行隊(duì)列 + 異步執(zhí)行

開一個(gè)新線程管嬉,一個(gè)一個(gè)執(zhí)行任務(wù)

    NSLog(@"0========%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("", 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]);
    
    /*
     NSLog輸出
     0========<NSThread: 0x6000002616c0>{number = 1, name = main}
     4========<NSThread: 0x6000002616c0>{number = 1, name = main}
     1========<NSThread: 0x608000270540>{number = 3, name = (null)}
     2========<NSThread: 0x608000270540>{number = 3, name = (null)}
     3========<NSThread: 0x608000270540>{number = 3, name = (null)}
     */
3. 并行隊(duì)列 + 同步執(zhí)行

當(dāng)前線程皂林,一個(gè)一個(gè)執(zhí)行任務(wù)

    NSLog(@"0========%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("", 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]);
    
    /*
     NSLog輸出
     0========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     1========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     2========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     3========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     4========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     */
4. 并行隊(duì)列 + 異步執(zhí)行

開多個(gè)線程,異步執(zhí)行

    NSLog(@"0========%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    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]);
    
    /*
     NSLog輸出
     0========<NSThread: 0x608000070300>{number = 1, name = main}
     4========<NSThread: 0x608000070300>{number = 1, name = main}
     2========<NSThread: 0x608000264140>{number = 4, name = (null)}
     1========<NSThread: 0x60000007a800>{number = 3, name = (null)}
     3========<NSThread: 0x6080002642c0>{number = 5, name = (null)}
     */
5. 主隊(duì)列 + 異步執(zhí)行

主線程蚯撩,同步執(zhí)行

    NSLog(@"0========%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    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]);
    
    /*
     NSLog輸出
     0========<NSThread: 0x60000026e000>{number = 3, name = (null)}
     4========<NSThread: 0x60000026e000>{number = 3, name = (null)}
     1========<NSThread: 0x60000007e2c0>{number = 1, name = main}
     2========<NSThread: 0x60000007e2c0>{number = 1, name = main}
     3========<NSThread: 0x60000007e2c0>{number = 1, name = main}
     */
6. 主隊(duì)列 + 同步執(zhí)行 (不能在主隊(duì)列這么用础倍,死鎖)

主線程,同步執(zhí)行

    NSLog(@"0========%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    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]);
    
    /*
     NSLog輸出
     0========<NSThread: 0x600000263840>{number = 3, name = (null)}
     1========<NSThread: 0x608000078ec0>{number = 1, name = main}
     2========<NSThread: 0x608000078ec0>{number = 1, name = main}
     3========<NSThread: 0x608000078ec0>{number = 1, name = main}
     4========<NSThread: 0x600000263840>{number = 3, name = (null)}
     */

GCD其他用法

dispatch_after延時(shí)

1胎挎、time = 0沟启,是直接調(diào)用異步dispatch_async
2、time > 0, 只是延時(shí)提交block犹菇,不是延時(shí)執(zhí)行德迹。

    //2秒延時(shí)、在主隊(duì)列
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        
    });
dispatch_once與dispatch_once_t
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //
    });

1揭芍、dispatch_once并不是簡單的只執(zhí)行一次那么簡單
2胳搞、dispatch_once本質(zhì)上可以接受多次請求,會對此維護(hù)一個(gè)請求鏈表
3称杨、如果在block執(zhí)行期間肌毅,多次進(jìn)入調(diào)用同類的dispatch_once函數(shù)(即單例函數(shù)),會導(dǎo)致整體鏈表無限增長姑原,造成永久性死鎖悬而。

遞歸互相嵌套,如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{ // 鏈表無限增長
        [self viewDidLoad];
    });
}

dispatch_once源碼

static void
dispatch_once_f_slow(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
#if DISPATCH_GATE_USE_FOR_DISPATCH_ONCE
    dispatch_once_gate_t l = (dispatch_once_gate_t)val;

    if (_dispatch_once_gate_tryenter(l)) {
        _dispatch_client_callout(ctxt, func);
        _dispatch_once_gate_broadcast(l);
    } else {
        _dispatch_once_gate_wait(l);
    }
#else
    _dispatch_once_waiter_t volatile *vval = (_dispatch_once_waiter_t*)val;
    struct _dispatch_once_waiter_s dow = { };
    _dispatch_once_waiter_t tail = &dow, next, tmp;
    dispatch_thread_event_t event;


    if (os_atomic_cmpxchg(vval, NULL, tail, acquire)) {
        
        // 第一次dispatch_once,原子性操作
        
        // 當(dāng)前線程
        dow.dow_thread = _dispatch_tid_self();
        // 執(zhí)行block
        _dispatch_client_callout(ctxt, func);

        // 第一次執(zhí)行完了锭汛,設(shè)置token = DISPATCH_ONCE_DONE
        next = (_dispatch_once_waiter_t)_dispatch_once_xchg_done(val);
        while (next != tail) {
            
            // 繼續(xù)去下一個(gè)
            tmp = (_dispatch_once_waiter_t)_dispatch_wait_until(next->dow_next);
            event = &next->dow_event;
            next = tmp;
            
            // 信號量++
            _dispatch_thread_event_signal(event);
        }
    } else {
        
        // 第二次dispatch_once進(jìn)來
        _dispatch_thread_event_init(&dow.dow_event);
        next = *vval;
        for (;;) {
            if (next == DISPATCH_ONCE_DONE) { // token是否等于DISPATCH_ONCE_DONE
                // 第一次執(zhí)行完之后笨奠,都是走這里
                break;
            }
            // 如果是嵌套使用,第一次沒有完成店乐,又要執(zhí)行一次
            if (os_atomic_cmpxchgv(vval, next, tail, &next, release)) {
                // 原子性
                dow.dow_thread = next->dow_thread;
                dow.dow_next = next;
                if (dow.dow_thread) {
                    pthread_priority_t pp = _dispatch_get_priority();
                    _dispatch_thread_override_start(dow.dow_thread, pp, val);
                }
                // 等待信號量
                _dispatch_thread_event_wait(&dow.dow_event);
                if (dow.dow_thread) {
                    _dispatch_thread_override_end(dow.dow_thread, val);
                }
                break;
            }
        }
        _dispatch_thread_event_destroy(&dow.dow_event);
    }
#endif
}
dispatch_apply(count,queue,block(index))迭代方法

該函數(shù)按指定的次數(shù)將指定的block追加到指定的隊(duì)列艰躺;使用的地方,阻塞當(dāng)前線程

    NSLog(@"CurrentThread------%@", [NSThread currentThread]);
    //dispatch_queue_t queue = dispatch_get_global_queue(0,0);
    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
    // 6是次數(shù)
    dispatch_apply(6, queue, ^(size_t index) {
        NSLog(@"%zd------%@",index, [NSThread currentThread]);
    });
    /*
     并發(fā)隊(duì)列:開多線程異步執(zhí)行
     NSLogx信息
     CurrentThread------<NSThread: 0x600000268a40>{number = 3, name = (null)}
     0------<NSThread: 0x600000268a40>{number = 3, name = (null)}
     1------<NSThread: 0x608000266e40>{number = 4, name = (null)}
     2------<NSThread: 0x608000266f00>{number = 5, name = (null)}
     3------<NSThread: 0x608000266f40>{number = 6, name = (null)}
     4------<NSThread: 0x600000268a40>{number = 3, name = (null)}
     5------<NSThread: 0x608000266e40>{number = 4, name = (null)}
     */
    
    
    /*
     同步隊(duì)列:當(dāng)前線程同步執(zhí)行
     NSLogx信息
     CurrentThread------<NSThread: 0x608000072c00>{number = 3, name = (null)}
     0------<NSThread: 0x6000000694c0>{number = 1, name = main}
     1------<NSThread: 0x6000000694c0>{number = 1, name = main}
     2------<NSThread: 0x6000000694c0>{number = 1, name = main}
     3------<NSThread: 0x6000000694c0>{number = 1, name = main}
     4------<NSThread: 0x6000000694c0>{number = 1, name = main}
     5------<NSThread: 0x6000000694c0>{number = 1, name = main}
     */

dispatch_apply能避免線程爆炸眨八,因?yàn)镚CD會管理并發(fā)

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 999; i++){
      dispatch_async(queue, ^{
         NSLog(@"%d,%@",i,[NSThread currentThread]);// 能開多大線程就開多大線程(幾十個(gè))
      });
}
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(999, queue, ^(size_t i){
     NSLog(@"%d,%@",i,[NSThread currentThread]); // 只開一定數(shù)量的線程(幾個(gè))
});
dispatch_suspend腺兴、dispatch_resume (用在dispatch_get_global_queue主隊(duì)列無效)

dispatch_suspend,dispatch_resume提供了“掛起廉侧、恢復(fù)”隊(duì)列的功能页响,簡單來說,就是可以暫停段誊、恢復(fù)隊(duì)列上的任務(wù)闰蚕。但是這里的“掛起”,并不能保證可以立即停止隊(duì)列上正在運(yùn)行的block

注意點(diǎn):

1连舍、如果隊(duì)列沒有使用dispatch_suspend没陡,使用dispatch_resume會crash
- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    dispatch_resume(queue); // crash
}
2、如果queue被掛起,queue銷毀時(shí)候沒有被喚醒盼玄,會crash
        dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
        dispatch_suspend(queue);// 如果queue被掛起贴彼,queue銷毀時(shí)候沒有被喚醒,會crash
3埃儿、dispatch_suspend后面執(zhí)行dispatch_sync器仗,阻塞當(dāng)前線程,需要其他線程恢復(fù)隊(duì)列
        queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
        dispatch_suspend(queue);
        // 后面執(zhí)行dispatch_sync童番,阻塞當(dāng)前線程精钮,需要其他線程恢復(fù)隊(duì)列
        dispatch_sync(queue, ^{
            NSLog(@"=====%@",@"111111");
        });
        NSLog(@"=====%@",@"22222");

GCD的隊(duì)列組dispatch_group_t,其實(shí)就是封裝了一個(gè)無限大的信號量,

注意事項(xiàng)
1剃斧、dispatch_group_async(只有async轨香,無sync)等價(jià)于{dispatch_group_enter() + async}, async調(diào)用完了會執(zhí)行dispatch_group_leave()。
2悯衬、dispatch_group_enter()就是信號量--弹沽;
3、dispatch_group_leave()就是信號量++
4筋粗、dispatch_group_enter() 必須運(yùn)行在 dispatch_group_leave() 之前策橘。
5、dispatch_group_enter() 和 dispatch_group_leave() 需要成對出現(xiàn)的

    //1.創(chuàng)建隊(duì)列組
    dispatch_group_t group = dispatch_group_create();

    //2.1.全局隊(duì)列
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (NSInteger i = 0; i < 3; i++) {
            NSLog(@"group-01 - %@", [NSThread currentThread]);
        }
    });
    
    //2.2.主隊(duì)列
    dispatch_group_async(group, dispatch_get_main_queue(), ^{
        for (NSInteger i = 0; i < 3; i++) {
            NSLog(@"group-02 - %@", [NSThread currentThread]);
        }
    });
   
    //2.3.自建串行隊(duì)列
    dispatch_group_async(group, dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL), ^{
        for (NSInteger i = 0; i < 3; i++) {
            NSLog(@"group-03 - %@", [NSThread currentThread]);
        }
    });
    
    //3.都完成后會自動(dòng)通知,不阻塞當(dāng)前線程
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"完成 - %@", [NSThread currentThread]);
    });
    NSLog(@"當(dāng)前線程=====%@",[NSThread currentThread]);
    
     /*
      并行隊(duì)列娜亿、自建串行隊(duì)列的任務(wù)多線程異步執(zhí)行
      主隊(duì)列的任務(wù)主線程同步執(zhí)行丽已,且排在全部任務(wù)的最后
      
      NSLog信息
      當(dāng)前線程=====<NSThread: 0x60000007c240>{number = 1, name = main}
      group-01 - <NSThread: 0x60800026d180>{number = 3, name = (null)}
      group-01 - <NSThread: 0x60800026d180>{number = 3, name = (null)}
      group-03 - <NSThread: 0x60000026c7c0>{number = 4, name = (null)}
      group-01 - <NSThread: 0x60800026d180>{number = 3, name = (null)}
      group-03 - <NSThread: 0x60000026c7c0>{number = 4, name = (null)}
      group-03 - <NSThread: 0x60000026c7c0>{number = 4, name = (null)}
      group-02 - <NSThread: 0x60000007c240>{number = 1, name = main}
      group-02 - <NSThread: 0x60000007c240>{number = 1, name = main}
      group-02 - <NSThread: 0x60000007c240>{number = 1, name = main}
      完成 - <NSThread: 0x60000007c240>{number = 1, name = main}
      */
手動(dòng)標(biāo)記group完成
  • dispatch_group_enter(group)
  • dispatch_group_leave(group);
dispatch_group_notify(不阻塞)相當(dāng)于把block任務(wù)加在最后
    NSLog(@"start");
    //1.創(chuàng)建隊(duì)列組
    dispatch_group_t group = dispatch_group_create();
    for (int i=0; i< 5; i++) {
        dispatch_group_enter(group); // enter
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            // something
            NSLog(@"something===%zd",i);
            dispatch_group_leave(group); // eave
        });
    }
    // 都完成后會自動(dòng)通知,不阻塞當(dāng)前線程
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"完成 - %@", [NSThread currentThread]);
    });
    NSLog(@"end");

     /*
      NSLog信息:
      start
      end
      something===1
      something===0
      something===2
      something===3
      something===4
      完成 - <NSThread: 0x60800006e900>{number = 1, name = main}
      */
    

dispatch_group_wait就是等待group的信號量回到初始值(阻塞當(dāng)前線程)
    NSLog(@"start");
    //1.創(chuàng)建隊(duì)列組
    dispatch_group_t group = dispatch_group_create();
    
    for (int i=0; i< 5; i++) {
        
        dispatch_group_enter(group); // enter
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            // something
            
            [NSThread sleepForTimeInterval:2];
            NSLog(@"something===%zd",i);
            dispatch_group_leave(group); // eave
        });
        
    }
    // 阻塞當(dāng)前線程的、等待5秒
    dispatch_time_t waitTime = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC);
    dispatch_group_wait(group, waitTime);//特殊值有:DISPATCH_TIME_FOREVER是無限等买决,DISPATCH_TIME_NOW是不等
    NSLog(@"end");
    
    
     /*
      等待時(shí)間 < 執(zhí)行需要時(shí)間
      NSLog信息:
      start
      end
      something===0
      something===1
      something===3
      something===2
      something===4
      */
    
    /*
     等待時(shí)間 > 執(zhí)行需要時(shí)間
     NSLog信息:
     start
     something===1
     something===0
     something===4
     something===2
     something===3
     end
     */
dispatch_semaphore_create & dispatch_semaphore_signal & dispatch_semaphore_wait

信號量是控制任務(wù)執(zhí)行的重要條件沛婴,當(dāng)信號量為0時(shí),所有任務(wù)等待督赤,信號量越大嘁灯,允許可并行執(zhí)行的任務(wù)數(shù)量越多。

  • dispatch_semaphore_create(long value);創(chuàng)建信號量躲舌,初始值不能小于0丑婿;value信號數(shù)值
  • dispatch_semaphore_wait(semaphore, timeout);等待降低信號量,也就是信號量-1没卸;timeout不是調(diào)用dispatch_semaphore_wait后等待的時(shí)間,而是信號量創(chuàng)建后的時(shí)間
  • dispatch_semaphore_signal(semaphore);提高信號量羹奉,也就是信號量+1;
  • dispatch_semaphore_wait和dispatch_semaphore_signal通常配對使用约计。
    // 相當(dāng)于控制新建的線程數(shù)
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
    for (int i=0; i< 10; i++) {
        
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            [NSThread sleepForTimeInterval:1];
            NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
            dispatch_semaphore_signal(semaphore);
        });
        
    }
    
    
     /*
      dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
      只有5條線程
      NSLog信息:
      第0次_<NSThread: 0x608000268b00>{number = 4, name = (null)}
      第1次_<NSThread: 0x600000269240>{number = 6, name = (null)}
      第3次_<NSThread: 0x600000269100>{number = 5, name = (null)}
      第2次_<NSThread: 0x608000268ac0>{number = 3, name = (null)}
      第4次_<NSThread: 0x600000269780>{number = 7, name = (null)}
      第8次_<NSThread: 0x608000268ac0>{number = 3, name = (null)}
      第7次_<NSThread: 0x600000269100>{number = 5, name = (null)}
      第6次_<NSThread: 0x608000268b00>{number = 4, name = (null)}
      第5次_<NSThread: 0x600000269240>{number = 6, name = (null)}
      第9次_<NSThread: 0x600000269780>{number = 7, name = (null)}
      */
    
    /*
     dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
     10條線程
     NSLog信息:
     第2次_<NSThread: 0x6080000661c0>{number = 3, name = (null)}
     第4次_<NSThread: 0x608000073e40>{number = 7, name = (null)}
     第1次_<NSThread: 0x600000079dc0>{number = 4, name = (null)}
     第5次_<NSThread: 0x6000000721c0>{number = 8, name = (null)}
     第3次_<NSThread: 0x608000073dc0>{number = 6, name = (null)}
     第0次_<NSThread: 0x608000073d40>{number = 5, name = (null)}
     第6次_<NSThread: 0x608000073d80>{number = 9, name = (null)}
     第9次_<NSThread: 0x608000073e00>{number = 10, name = (null)}
     第7次_<NSThread: 0x6000000717c0>{number = 11, name = (null)}
     第8次_<NSThread: 0x600000066b40>{number = 12, name = (null)}
     */
dispatch_barrier_async对省、dispatch_barrier_sync (承上啟下--用于自建的并行隊(duì)列)

保證此前的任務(wù)都先于自己執(zhí)行左刽,此后的任務(wù)也遲于自己執(zhí)行商佑。
dispatch_barrier_async 不阻塞當(dāng)前線程;
dispatch_barrier_sync 阻塞當(dāng)前線程细卧;

注意:dispatch_barrier_(a)sync只在自己創(chuàng)建的并發(fā)隊(duì)列上有效,在全局(Global)并發(fā)隊(duì)列俗孝、串行隊(duì)列上酒甸,效果跟dispatch_(a)sync效果一樣。

- (void)test{

    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    dispatch_async(globalQueue, ^{
        NSLog(@"任務(wù)1");
    });
    dispatch_async(globalQueue, ^{
        NSLog(@"任務(wù)2");
    });
    dispatch_barrier_async(globalQueue, ^{
        NSLog(@"任務(wù)barrier");
    });
    dispatch_async(globalQueue, ^{
        NSLog(@"任務(wù)3");
    });
    dispatch_async(globalQueue, ^{
        NSLog(@"任務(wù)4");
    });
    /*
     2017-09-02 21:03:40.255 NSThreadTest[28856:21431532] 任務(wù)2
     2017-09-02 21:03:40.255 NSThreadTest[28856:21431535] 任務(wù)1
     2017-09-02 21:03:40.255 NSThreadTest[28856:21431533] 任務(wù)barrier
     2017-09-02 21:03:40.255 NSThreadTest[28856:21431551] 任務(wù)3
     2017-09-02 21:03:40.256 NSThreadTest[28856:21431550] 任務(wù)4
     */
}

GCD創(chuàng)建Timer

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //創(chuàng)建隊(duì)列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    //1.創(chuàng)建一個(gè)GCD定時(shí)器
    /*
     第一個(gè)參數(shù):表明創(chuàng)建的是一個(gè)定時(shí)器
     第四個(gè)參數(shù):隊(duì)列
     */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    // 需要對timer進(jìn)行強(qiáng)引用赋铝,保證其不會被釋放掉,才會按時(shí)調(diào)用block塊
    // 局部變量沽瘦,讓指針強(qiáng)引用
    self.timer = timer;
    //2.設(shè)置定時(shí)器的開始時(shí)間,間隔時(shí)間,精準(zhǔn)度
    /*
     第1個(gè)參數(shù):要給哪個(gè)定時(shí)器設(shè)置
     第2個(gè)參數(shù):開始時(shí)間
     第3個(gè)參數(shù):間隔時(shí)間
     第4個(gè)參數(shù):精準(zhǔn)度 一般為0 在允許范圍內(nèi)增加誤差可提高程序的性能
     GCD的單位是納秒 所以要*NSEC_PER_SEC
     */
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);

    //3.設(shè)置定時(shí)器要執(zhí)行的事情
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"---%@--",[NSThread currentThread]);
    });
    // 啟動(dòng)
    dispatch_resume(timer);
}

GCD各種死鎖的情況
1革骨、最常見的(主線中+主隊(duì)列+同步)
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"任務(wù)1========%@",[NSThread currentThread]);// 當(dāng)前是主線程、主隊(duì)列
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"任務(wù)2========%@",[NSThread currentThread]);
    });
    NSLog(@"任務(wù)3========%@",[NSThread currentThread]);
    
    /*
     NSLog信息
    任務(wù)1========<NSThread: 0x60000007c5c0>{number = 1, name = main}
     
     解析
     1.viewDidLoad是主隊(duì)列析恋,dispatch_sync也屬于主隊(duì)列良哲,
     2.dispatch_sync是viewDidLoad里面的代碼,viewDidLoad需要等待dispatch_sync執(zhí)行完助隧,dispatch_sync需要等待viewDidLoad執(zhí)行完筑凫,這就死鎖了
     */
    
}
主線程中:主隊(duì)列+同步
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"0========%@",[NSThread currentThread]);// 當(dāng)前是主線程、主隊(duì)列
    // 改成dispatch_get_global_queue或者new出來的隊(duì)列
    dispatch_sync(dispatch_get_global_queue(0,0), ^{
        NSLog(@"1========%@",[NSThread currentThread]);
    });
    NSLog(@"2========%@",[NSThread currentThread]);
    
    /*
     NSLog信息
     0========<NSThread: 0x6000000690c0>{number = 1, name = main}
     1========<NSThread: 0x6000000690c0>{number = 1, name = main}
     2========<NSThread: 0x6000000690c0>{number = 1, name = main}
     
     解析
     1.viewDidLoad是主隊(duì)列并村,dispatch_sync是global_queue巍实,不在同一隊(duì)列
     2.dispatch_sync是viewDidLoad里面的代碼,viewDidLoad需要等待dispatch_sync執(zhí)行完返回哩牍,但是dispatch_sync不需要等待viewDidLoad執(zhí)行完棚潦,立即執(zhí)行完返回
     */
    
}
2、串行隊(duì)列膝昆,各種嵌套異步情況

死鎖的原因:是同一個(gè)串行隊(duì)列任務(wù)內(nèi)部代碼繼續(xù)嵌套同步sync的任務(wù)

        // 串行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
    
    // 同步嵌異步----執(zhí)行OK
    dispatch_sync(queue, ^{
        dispatch_async(queue, ^{
        });
    });
    
    // 異步嵌異步----執(zhí)行OK
    dispatch_async(queue, ^{
        dispatch_async(queue, ^{
        });
    });
    
    // 異步嵌同步----死鎖
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
        });
    });
    
    // 同步嵌同步----死鎖
    dispatch_sync(queue, ^{
        dispatch_sync(queue, ^{
        });
    });

3丸边、并行隊(duì)列,各種嵌套異步情況

并行 隊(duì)列各種嵌套都不會死鎖

    // 并行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    
    // 同步嵌異步----執(zhí)行OK
    dispatch_sync(queue, ^{
        dispatch_async(queue, ^{
        });
    });
    
    // 異步嵌異步----執(zhí)行OK
    dispatch_async(queue, ^{
        dispatch_async(queue, ^{
        });
    });
    
    // 異步嵌同步----執(zhí)行OK
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
        });
    });
    
    // 同步嵌同步----執(zhí)行OK
    dispatch_sync(queue, ^{
        dispatch_sync(queue, ^{
        });
    });
4荚孵、dispatch_apply阻塞當(dāng)前線程
// 主隊(duì)列使用妹窖,死鎖
dispatch_apply(5, dispatch_get_main_queue(), ^(size_t i) {
        NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
    });
// 嵌套使用,死鎖
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t) {
    // 任務(wù)
    ...
    dispatch_apply(10, queue, ^(size_t) {
        // 任務(wù)
        ...
    });
});

dispatch_barrier
dispatch_barrier_sync在串行隊(duì)列和全局并行隊(duì)列里面和dispatch_sync同樣的效果收叶,所以需考慮同dispatch_sync一樣的死鎖問題骄呼。

5、 信號量阻塞主線程
- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    NSLog(@"semaphore create!");
    dispatch_async(dispatch_get_main_queue(), ^{
        dispatch_semaphore_signal(semaphore);
        NSLog(@"semaphore plus 1");
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore minus 1");
}

原因:
如果當(dāng)前執(zhí)行的線程是主線程滔驾,以上代碼就會出現(xiàn)死鎖谒麦。
因?yàn)閐ispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)阻塞了當(dāng)前線程,而且等待時(shí)間是DISPATCH_TIME_FOREVER——永遠(yuǎn)等待哆致,這樣它就永遠(yuǎn)的阻塞了當(dāng)前線程——主線程绕德。導(dǎo)致主線中的dispatch_semaphore_signal(semaphore)沒有執(zhí)行,
而dispatch_semaphore_wait一直在等待dispatch_semaphore_signal改變信號量摊阀,這樣就形成了死鎖耻蛇。

解決方法:
應(yīng)該將信號量移到并行隊(duì)列中踪蹬,如全局調(diào)度隊(duì)列。以下場景臣咖,移到串行隊(duì)列也是可以的跃捣。但是串行隊(duì)列還是有可能死鎖的(如果執(zhí)行dispatch_semaphore_signal方法還是在對應(yīng)串行隊(duì)列中的話,即之前提到的串行隊(duì)列嵌套串行隊(duì)列的場景)夺蛇。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        NSLog(@"semaphore create!");
        dispatch_async(dispatch_get_main_queue(), ^{
            dispatch_semaphore_signal(semaphore); // +1
            NSLog(@"semaphore plus 1");
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"semaphore minus 1");
    });
}
一些嵌套使用問題
    NSLog(@"1");
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"2");
        dispatch_async(dispatch_get_main_queue(), ^{
           sleep(1);
            NSLog(@"3");
        });
    });
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"4");
    });
    NSLog(@"5");

    //結(jié)果是:12534
    //解析:“2”并發(fā)隊(duì)列同步任務(wù)疚漆,所以125;“3”刁赦、“4”是兩個(gè)主隊(duì)列異步娶聘,串行執(zhí)行任務(wù)34;最終就是12534
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_sync(queue, ^{
        
        dispatch_sync(queue, ^{
            NSLog(@"B");
        });
        NSLog(@"A");
    });
    // 結(jié)果是:BA (并發(fā)隊(duì)列不會死鎖) 并行隊(duì)列甚脉,任務(wù)A加入隊(duì)列執(zhí)行中丸升,然后任務(wù)B加入隊(duì)列也立即執(zhí)行,但是任務(wù)A會等任務(wù)B先執(zhí)行完牺氨。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末狡耻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子猴凹,更是在濱河造成了極大的恐慌夷狰,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件精堕,死亡現(xiàn)場離奇詭異孵淘,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)歹篓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門瘫证,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人庄撮,你說我怎么就攤上這事背捌。” “怎么了洞斯?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵毡庆,是天一觀的道長。 經(jīng)常有香客問我烙如,道長么抗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任亚铁,我火速辦了婚禮蝇刀,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘徘溢。我一直安慰自己吞琐,他們只是感情好捆探,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著站粟,像睡著了一般黍图。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上奴烙,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天助被,我揣著相機(jī)與錄音,去河邊找鬼切诀。 笑死恰起,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的趾牧。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼肯污,長吁一口氣:“原來是場噩夢啊……” “哼翘单!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蹦渣,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤哄芜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后柬唯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體认臊,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年锄奢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了失晴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拘央,死狀恐怖涂屁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情灰伟,我是刑警寧澤拆又,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站栏账,受9級特大地震影響帖族,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜挡爵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一竖般、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧了讨,春花似錦捻激、人聲如沸制轰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽垃杖。三九已至,卻和暖如春丈屹,著一層夾襖步出監(jiān)牢的瞬間调俘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工旺垒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留彩库,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓先蒋,卻偏偏與公主長得像骇钦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子竞漾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評論 2 348

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