OC-多線程

多線程

官方文檔:線程編程指南
GCD源碼:https://github.com/apple/swift-corelibs-libdispatch

iOS 中常見的多線程方案

iOS 中常將的多線程方案如下:


iOS多線程方案

GCD 多線程基本概念

  • 同步/異步: 是否有開啟新線程的能力
  • 串行隊(duì)列/并行隊(duì)列: 是否具有并行執(zhí)行任務(wù)的能力乌逐。主隊(duì)列是一種特殊的串行隊(duì)列
隊(duì)列概念

Note:
在主線程執(zhí)行同步串行任務(wù)竭讳,會(huì)卡死主線程。
原理: 在串行隊(duì)列里面執(zhí)行同步任務(wù)浙踢,就會(huì)產(chǎn)生死鎖绢慢。

多線程安全問題與解決

多線程安全問題在于:多個(gè)線程同時(shí)訪問并修改同一變量值,會(huì)造成最終值不正確洛波。
例如:存取錢問題呐芥、售票問題

多線程同時(shí)修改資源導(dǎo)致異常

解決方案: 使用線程同步技術(shù)(就是協(xié)同步調(diào),按照預(yù)定的先后次序進(jìn)行)奋岁。常見的線程同步技術(shù)為:加鎖

加鎖同步多線程資源競爭

原則: 對(duì)于修改同一個(gè)變量值,需要用同一個(gè)鎖。如果只是讀取荸百,則無需加鎖

常用的鎖(效率從高到底):

  • os_unfair_lock
  • OSSpinLock
  • dispatch_semaphore
  • pthread_mutex
  • dispatch_queue(DISPATCH_QUEUE_SERIAL)
  • NSLock
  • NSCondition
  • pthread_mutex(recursive)
  • NSRecursiveLock
  • NSConditionLock
  • @synchronized

OSSpinLock

OSSpinLock 自旋鎖, 等待鎖的線程會(huì)處于忙等狀態(tài)(busy-wait),一直占用著CPU資源

目前已經(jīng)不再安全闻伶,可能會(huì)出現(xiàn)線程優(yōu)先級(jí)翻轉(zhuǎn)問題。表現(xiàn)上也類似死鎖:如果等待鎖的線程優(yōu)先級(jí)較高够话,它就會(huì)一直占用CPU資源蓝翰,優(yōu)先級(jí)低的線程就無法釋放鎖

已經(jīng)在iOS10開始被廢棄光绕。需要引入頭文件#import <libkern/OSAtomic.h>,使用如下:

#import <libkern/OSAtomic.h>


// 初始化鎖
OSSpinLock lock = OS_SPINLOCK_INIT;

// 加鎖
OSSpinLockLock(&lock);

    // 中間需要做的操作...
    
// 解鎖
OSSpinLockUnlock(&lock); 

/////////////////////////////////////////////
iOS 10之后替代 os_unfair_lock 頭文件<os/lock.h>

os_unfair_lock

os_unfair_lock 作為 OSSpinLock 的替代品,解決了優(yōu)先級(jí)反轉(zhuǎn)問題畜份,能做到讓等待的線程處于真正的休眠狀態(tài)诞帐,其接口與OSSpinLock 相似。需導(dǎo)入頭文文件<os/lock.h>

#import <os/lock.h> 

// 初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;

// 加鎖/嘗試加鎖
void os_unfair_lock_lock(os_unfair_lock_t lock);
bool os_unfair_lock_trylock(os_unfair_lock_t lock);

// 解鎖
void os_unfair_lock_unlock(os_unfair_lock_t lock);

// 判斷是否鎖的擁有者是自己爆雹,
void os_unfair_lock_assert_owner(os_unfair_lock_t lock);
void os_unfair_lock_assert_not_owner(os_unfair_lock_t lock);

pthread_mutex

pthread_mutex 能做到讓等待的線程處于休眠狀態(tài)停蕉。需要引入頭文件 <pthread.h>

互斥鎖/遞歸鎖/條件鎖

// 普通互斥鎖,屬性傳NULL
pthread_mutex_init(&_mutex, NULL);
pthread_mutex_lock(&_mutex);
    // 中間需要的操作
pthread_mutex_unlock(&_mutex);

---遞歸鎖 -----------------------
// 遞歸鎖:允許同一個(gè)線程對(duì)一把鎖進(jìn)行重復(fù)加鎖
// 初始化屬性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    // 初始化鎖
    pthread_mutex_init(mutex, &attr);
    // 銷毀屬性
    pthread_mutexattr_destroy(&attr);

pthread_mutex_lock(&_mutex);
    // 中間需要的操作
pthread_mutex_unlock(&_mutex);

----條件鎖--------------------------
當(dāng)多線程執(zhí)行任務(wù)有條件依賴的是可以用條件鎖钙态。
- (void)__remove
{
    pthread_mutex_lock(&_mutex);
    
    if (self.data.count == 0) {
        // 等待
        pthread_cond_wait(&_cond, &_mutex);
    }
    
    [self.data removeLastObject];
    pthread_mutex_unlock(&_mutex);
}

// 線程2
// 往數(shù)組中添加元素
- (void)__add
{
    pthread_mutex_lock(&_mutex);
    sleep(1);
    
    [self.data addObject:@"Test"];
    
    // 信號(hào) - 喚醒被該條件加的鎖
    pthread_cond_signal(&_cond);
    // 廣播
//    pthread_cond_broadcast(&_cond);
    pthread_mutex_unlock(&_mutex);
}

NSLock慧起、NSRecursiveLock、NSCondition册倒、NSConditionLock

這幾個(gè)鎖是基于 pthread_mutex 的 OC 封裝蚓挤。其使用更加簡單、更加面向?qū)ο蟆?/p>

// NSLock - 封裝自 pthread_mutex_lock 默認(rèn)鎖
self.lock = [[NSLock alloc] init]; 
[self.ticketLock lock];
    // 加鎖代碼
[self.ticketLock unlock];

// NSCondition -- 封裝自 pthread_mutex_lock 默認(rèn)條件鎖
self.condition = [[NSCondition alloc] init];
[self.condition lock];
// 等待
[self.condition wait];
// 信號(hào)
[self.condition signal];    
// 廣播
[self.condition broadcast];
[self.condition unlock];

// NSConditionLock -- 封裝自 pthread_mutex_lock 條件鎖驻子,可加自定義條件
self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
// 以下三段代碼可以按順序執(zhí)行
[self.conditionLock lock];
NSLog(@"__one");
[self.conditionLock unlockWithCondition:2];

[self.conditionLock lockWhenCondition:2];
NSLog(@"__two");
[self.conditionLock unlockWithCondition:3];

[self.conditionLock lockWhenCondition:3];
NSLog(@"__three");
[self.conditionLock unlock];

// 

dispatch_queue(DISPATCH_QUEUE_SERIAL)

使用串行隊(duì)列也能解決多線程資源競爭問題灿意,將線程加入到串行隊(duì)列按順序執(zhí)行。

self.serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);

dispatch_sync(self.serialQueue, ^{
        // 處理變量賦值等核心功能代碼
    });

dispatch_semaphore

semaphore 叫做“信號(hào)量”,用來控制線程的最大并發(fā)數(shù)量崇呵。

如果信號(hào)量的值 > 0缤剧,就讓信號(hào)量的值減1,然后繼續(xù)往下執(zhí)行代碼演熟。
如果信號(hào)量的值 <= 0鞭执,就會(huì)休眠等待,直到信號(hào)量的值變成>0芒粹,就讓信號(hào)量的值減1兄纺,然后繼續(xù)往下執(zhí)行代碼。

dispatch_semaphore_signal(); 給對(duì)對(duì)應(yīng)的信號(hào)量 +1

semaphore 初始值為1時(shí)候化漆,非常適合做線程同步

// 設(shè)置最大允許并發(fā)數(shù) 5
self.semaphore = dispatch_semaphore_create(5);

- (void)test
{
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    
    // 相關(guān)代碼
    
    // 讓信號(hào)量的值+1
    dispatch_semaphore_signal(self.semaphore);
}

@synchronized

@synchronized 是對(duì) mutex 遞歸鎖的封裝估脆。可以參考 runtime 源碼 objc_sync源碼座云。

// 參數(shù)即要設(shè)置為鎖的值疙赠,就是一個(gè)指針
@synchronized([self class]) {
        [super __drawMoney];
    }

鎖的使用小技巧: 宏

#define SemaphoreBegin \
static dispatch_semaphore_t semaphore; \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
    semaphore = dispatch_semaphore_create(1); \
}); \
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

#define SemaphoreEnd \
dispatch_semaphore_signal(semaphore);

--------------
SemaphoreBegin;
// .....    
SemaphoreEnd;

atomic 原子操作

寫屬性的時(shí)候常用 atomic、nonatomic

給屬性加上 atomic 修飾朦拖,可以保證屬性 setter 和 getter 方法都是原子性操作圃阳,也就是保證 setter 和 getter 內(nèi)部都是線程同步的。這里可以參考 runtime 源碼 objc-accessors璧帝。本質(zhì)上也是加鎖捍岳,源碼如下

// 獲取屬性對(duì)象
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
    if (offset == 0) { // 對(duì)象本質(zhì)為結(jié)構(gòu)體,根據(jù)屬性的在結(jié)構(gòu)體內(nèi)的 offset 獲取。如果offset == 0 即獲取結(jié)構(gòu)體首地址锣夹,即 isa 地址
        return object_getClass(self);
    }

    // Retain release world
    // 根據(jù) offset 獲取結(jié)構(gòu)體內(nèi) 屬性指針
    id *slot = (id*) ((char*)self + offset);
    // 如果是非原子屬性页徐,就直接返回屬性指針
    if (!atomic) return *slot;
        
    // Atomic retain release world
    spinlock_t& slotlock = PropertyLocks[slot];
    slotlock.lock();
    id value = objc_retain(*slot);
    slotlock.unlock();
    
    // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
    return objc_autoreleaseReturnValue(value);
}

atomic 給 setter/getter 內(nèi)部加鎖保證了屬性存取的安全,但是不能保證屬性取出之后的操作安全银萍。

因?yàn)榇嫒》椒ㄊ褂眠^于頻繁变勇,所以 atomic 顯得過于消耗性能。

iOS 中的讀寫安全方案

IO 操作 -> 文件讀寫操作 -> 【多度贴唇,單寫】

實(shí)際操作條件:

  1. 同一時(shí)間搀绣,只能有一個(gè)線程進(jìn)行寫操作
  2. 同一時(shí)間,允許有多個(gè)線程進(jìn)行讀操作
  3. 同一時(shí)間滤蝠,不允許既有寫操作豌熄,又有讀操作

方案如下:

  1. pthread_rwlock
  2. dispatch_barrier_sync

pthread_rwlock

pthread_rwlock 也是互斥鎖,等待鎖的進(jìn)程會(huì)進(jìn)入休眠物咳。使用如下

// 創(chuàng)建讀寫鎖屬性
pthread_rwlockattr_t rwAttr;
pthread_rwlockattr_init(&rwAttr);
    
// 初始化鎖
pthread_rwlock_t lock;
pthread_rwlock_init(&lock, &rwAttr); // pthread_rwlock_init(&lock, NULL);
// 讀-加鎖
pthread_rwlock_rdlock(&lock);
// 讀-嘗試加鎖
pthread_rwlock_tryrdlock(&lock);
// 寫-加鎖
pthread_rwlock_wrlock(&lock);
// 寫-嘗試加鎖
pthread_rwlock_trywrlock(&lock);
// 解鎖
pthread_rwlock_unlock(&lock);
// 銷毀
pthread_rwlock_destroy(&lock);

dispatch_barrier_sync

  • 這個(gè)函數(shù)闖入的并發(fā)隊(duì)列锣险,必須是自己通過dispatch_queue_create創(chuàng)建的
  • 如果傳入的是一個(gè)串行或者全局并發(fā)隊(duì)列,那就相當(dāng)于調(diào)用dispatch_async函數(shù)
// 創(chuàng)建隊(duì)列
dispatch_queue_t  _Nonnull queue = dispatch_queue_create("barrierQueue", DISPATCH_QUEUE_CONCURRENT);
    
// 讀 - 異步線程览闰,可以多線程同時(shí)訪問
dispatch_barrier_async(queue, ^{
    
});
    
// 寫 - 同步任務(wù)芯肤,只有一個(gè)線程可以寫
dispatch_barrier_sync(queue, ^{
    
});

面試題

  • 你理解的多線程?
線程是應(yīng)用程序內(nèi)部實(shí)現(xiàn)多個(gè)執(zhí)行路徑的相對(duì)輕量的方法压鉴。

系統(tǒng)->并行執(zhí)行進(jìn)程->進(jìn)程執(zhí)行一個(gè)或者多個(gè)線程崖咨。 
這些線程可以同時(shí)或者幾乎同時(shí)的方式執(zhí)行不同的任務(wù)。
系統(tǒng)本身實(shí)際上管理這些執(zhí)行的線程油吭,安排它們?cè)诳捎玫膬?nèi)核上運(yùn)行击蹲,并根據(jù)需要中斷它們,將執(zhí)行時(shí)間分配給其他線程婉宰。

多線程有點(diǎn):
1. 可以提高程序的感知響應(yīng)能力歌豺,
2. 可以提高應(yīng)用程序在多核系統(tǒng)上的實(shí)時(shí)性能

缺點(diǎn):
1. 增加代碼復(fù)雜性,它們可以訪問同樣的資源心包,多個(gè)線程需協(xié)同合作类咧,防止破壞程序的狀態(tài)信息
2. 線程間的資源競爭問題,需要線程同步的技術(shù)來額外處理
  • 以下代碼執(zhí)行情況如何?正確執(zhí)行/奔潰蟹腾?why?
- (void)interview01
{
    // 會(huì)產(chǎn)生死鎖痕惋!卡死主線程
    NSLog(@"執(zhí)行任務(wù)1");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"執(zhí)行任務(wù)2");
    });
    
    NSLog(@"執(zhí)行任務(wù)3");
    
    // dispatch_sync立馬在當(dāng)前線程同步執(zhí)行任務(wù)
}

- (void)interview02
{
    // 問題:以下代碼是在主線程執(zhí)行的,會(huì)不會(huì)產(chǎn)生死鎖娃殖?不會(huì)值戳!
    NSLog(@"執(zhí)行任務(wù)1");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"執(zhí)行任務(wù)2");
    });
    
    NSLog(@"執(zhí)行任務(wù)3");
    
    // dispatch_async不要求立馬在當(dāng)前線程同步執(zhí)行任務(wù)
}

- (void)interview03
{
    // 問題:以下代碼是在主線程執(zhí)行的,會(huì)不會(huì)產(chǎn)生死鎖炉爆?會(huì)述寡!
    NSLog(@"執(zhí)行任務(wù)1");
    
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{ // 0
        NSLog(@"執(zhí)行任務(wù)2");
        
        dispatch_sync(queue, ^{ // 1
            NSLog(@"執(zhí)行任務(wù)3");
        });
    
        NSLog(@"執(zhí)行任務(wù)4");
    });
    
    NSLog(@"執(zhí)行任務(wù)5");
}

- (void)interview04
{
    // 問題:以下代碼是在主線程執(zhí)行的柿隙,會(huì)不會(huì)產(chǎn)生死鎖?不會(huì)鲫凶!
    NSLog(@"執(zhí)行任務(wù)1");
    
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
//    dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{ // 0
        NSLog(@"執(zhí)行任務(wù)2");
        
        dispatch_sync(queue2, ^{ // 1
            NSLog(@"執(zhí)行任務(wù)3");
        });
        
        NSLog(@"執(zhí)行任務(wù)4");
    });
    
    NSLog(@"執(zhí)行任務(wù)5");
}

- (void)interview05
{
    // 問題:以下代碼是在主線程執(zhí)行的,會(huì)不會(huì)產(chǎn)生死鎖衩辟?不會(huì)螟炫!
    NSLog(@"執(zhí)行任務(wù)1");
    
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{ // 0
        NSLog(@"執(zhí)行任務(wù)2");
        
        dispatch_sync(queue, ^{ // 1
            NSLog(@"執(zhí)行任務(wù)3");
        });
        
        NSLog(@"執(zhí)行任務(wù)4");
    });
    
    NSLog(@"執(zhí)行任務(wù)5");
}
  • 下面代碼打印什么?為什么艺晴?
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self test2];
}

- (void)test
{
    NSLog(@"2");
}

- (void)test2
{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        NSLog(@"1");
        [self performSelector:@selector(test) withObject:nil afterDelay:.0];
        NSLog(@"3");
    });
}

// 打印 1昼钻、3
// 原因: performSelector:withObject:afterDelay 這個(gè)方法本質(zhì)上是給Runloop 添加定時(shí)器。而子線程雖然已經(jīng)創(chuàng)建了 runloop 但是并沒有運(yùn)行封寞,所以不會(huì)打印然评,處理方式就是運(yùn)行子線程的 runloop,讓子線程北肪浚活

// 以下代碼同理
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        NSLog(@"1");

        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }];
    [thread start];

    [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
  • 如何實(shí)現(xiàn)如首頁多個(gè)網(wǎng)絡(luò)請(qǐng)求碗淌,最后一個(gè)請(qǐng)求基于前面的網(wǎng)絡(luò)請(qǐng)求的情況
// 使用 dispatch_group 的方式。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 創(chuàng)建隊(duì)列組
    dispatch_group_t group = dispatch_group_create();
    // 創(chuàng)建并發(fā)隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT);
    
    // 添加異步任務(wù)
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)1-%@", [NSThread currentThread]);
        }
    });
    
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)2-%@", [NSThread currentThread]);
        }
    });
    
    // 等前面的任務(wù)執(zhí)行完畢后抖锥,會(huì)自動(dòng)執(zhí)行這個(gè)任務(wù)
//    dispatch_group_notify(group, queue, ^{
//        dispatch_async(dispatch_get_main_queue(), ^{
//            for (int i = 0; i < 5; i++) {
//                NSLog(@"任務(wù)3-%@", [NSThread currentThread]);
//            }
//        });
//    });
    
//    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//        for (int i = 0; i < 5; i++) {
//            NSLog(@"任務(wù)3-%@", [NSThread currentThread]);
//        }
//    });
    
    dispatch_group_notify(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)3-%@", [NSThread currentThread]);
        }
    });
    
    dispatch_group_notify(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)4-%@", [NSThread currentThread]);
        }
    });  
}
  • iOS 的多線程有幾種方案亿眠,你更傾向于哪一種?
pthread
NSThread
GCD  ---> 更傾向
NSOperation
  • 你在項(xiàng)目中用過 GCD 嗎磅废?
用過
如: 
dispatch_semaphore -> 信號(hào)量
dispatch_barrier
dispatch_queue
dispatch_group
dispatch_sync & dispatch_async
...
  • GCD 的隊(duì)列類型
串行隊(duì)列 & 并行隊(duì)列
  • 說一下 OperationQueue 和 GCD 的區(qū)別纳像,以及各自優(yōu)勢(shì)?
GCD:
    基于C語言的API拯勉,旨在替代 NSThread 的線程技術(shù)竟趾,可以高效利用設(shè)備多核。

OperationQueue:
    底層封裝自 GCD宫峦,增加了很多使用功能岔帽,更加面向?qū)ο蟆?
  • 線程安全處理的手段有哪些?
1. 加鎖
2. 使用 GCD 串行隊(duì)列
3. 使用 GCD 信號(hào)量
  • OC 你了解的鎖有哪些斗遏?在你的回答基礎(chǔ)上進(jìn)行二次提問
    • 1.自旋鎖和互斥鎖的對(duì)比
    • 2.使用以上鎖需要注意哪些山卦?
    • 3.使用 C/OC/C++,任選其一,實(shí)現(xiàn)自旋或互斥诵次?口述即可
了解的鎖:
OSSpinLock账蓉、os_unfair_lock、pthread_mutex逾一、NSLock铸本、NSCondition、NSRecursiveLock遵堵、NSConditionLock箱玷、@synchronized

自旋鎖適合的場景
1. 預(yù)計(jì)線程等待鎖的時(shí)間很短
2. 加鎖的代碼(臨界區(qū))經(jīng)常被調(diào)用怨规,但競爭情況不是很激烈
3. CPU 資源不是很緊張
4. 多核處理器

互斥鎖比較適合的場景
1. 預(yù)計(jì)線程等待的時(shí)間較長
2. 單核處理器(減少CPU占用)
3. 臨界區(qū)有 IO 操作(IO 操作本身占CPU)
4. 臨界區(qū)代碼復(fù)雜或者循環(huán)量很大
5. 臨界區(qū)競爭激烈
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市锡足,隨后出現(xiàn)的幾起案子波丰,更是在濱河造成了極大的恐慌,老刑警劉巖舶得,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掰烟,死亡現(xiàn)場離奇詭異,居然都是意外死亡沐批,警方通過查閱死者的電腦和手機(jī)纫骑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來九孩,“玉大人先馆,你說我怎么就攤上這事√杀颍” “怎么了煤墙?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長顾患。 經(jīng)常有香客問我番捂,道長,這世上最難降的妖魔是什么江解? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任设预,我火速辦了婚禮,結(jié)果婚禮上犁河,老公的妹妹穿的比我還像新娘鳖枕。我一直安慰自己,他們只是感情好桨螺,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布宾符。 她就那樣靜靜地躺著,像睡著了一般灭翔。 火紅的嫁衣襯著肌膚如雪魏烫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天肝箱,我揣著相機(jī)與錄音哄褒,去河邊找鬼。 笑死煌张,一個(gè)胖子當(dāng)著我的面吹牛呐赡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播骏融,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼链嘀,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼萌狂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起怀泊,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤茫藏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后包个,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刷允,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年碧囊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纤怒。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡糯而,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泊窘,到底是詐尸還是另有隱情熄驼,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布烘豹,位于F島的核電站瓜贾,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏携悯。R本人自食惡果不足惜祭芦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望憔鬼。 院中可真熱鬧龟劲,春花似錦、人聲如沸轴或。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽照雁。三九已至蚕愤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間饺蚊,已是汗流浹背萍诱。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卸勺,地道東北人砂沛。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像曙求,于是被迫代替她去往敵國和親碍庵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子映企,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348