iOS-關(guān)于鎖的總結(jié)

前言

對(duì)于iOS中各種鎖的學(xué)習(xí)總結(jié),供日后查閱

引子

日常開(kāi)發(fā)中,@property (nonatomic, strong) *foo是我們不厭其煩的使用頻率最高的聲明方式,也很清楚atomicnonatomic屬性的區(qū)別,這里再?gòu)?fù)習(xí)一下這兩個(gè)關(guān)鍵字:

  • atomic:原子性,這個(gè)屬性是默認(rèn)的祈远,通過(guò)在settergetter中加鎖保證數(shù)據(jù)的讀寫(xiě)安全
  • nonatomic:非原子性商源,就是不加鎖车份。優(yōu)點(diǎn)是速度優(yōu)于使用atomic,大多數(shù)場(chǎng)景不會(huì)出現(xiàn)問(wèn)題

作為編譯器標(biāo)識(shí)符牡彻,@property的作用是幫助我們快速生成成員變量及其getter/setter方法扫沼,并通過(guò)屬性關(guān)鍵字出爹,幫助我們管理內(nèi)存及安全等繁雜的事務(wù),那么atomic是如何幫助我們保證成員變量的讀寫(xiě)安全呢缎除?下面我們看一段代碼:

//@property(retain) UITextField *userName;
//示例代碼如下:
- (UITextField *) userName {
    UITextField *retval = nil;
    @synchronized(self) {
        retval = [userName retain];
        _userName = retval;
    }
    return retval;
}
- (void) setUserName:(UITextField *)userName {
    @synchronized(self) {
      [_userName release];
      _userName = [userName retain];
    }
}

我們可以很容易的看出严就,編譯器是通過(guò)加鎖,來(lái)保證當(dāng)前成員變量_userName的讀寫(xiě)安全器罐,不至于生成臟數(shù)據(jù)梢为,這便是atomic背后,編譯器幫我們做的事情轰坊。事實(shí)上铸董,如果深究下去編譯器幫我們加了什么鎖,其實(shí)并非@synchronized(object)

自旋鎖不會(huì)使線程狀態(tài)發(fā)生切換肴沫,一直處于用戶態(tài)粟害,即線程一直都是active;不會(huì)使線程進(jìn)入阻塞狀態(tài)颤芬,減少了不必要的上下文切換悲幅,執(zhí)行速度快
非自旋鎖在獲取不到鎖的時(shí)候會(huì)進(jìn)入阻塞狀態(tài),從而進(jìn)入內(nèi)核態(tài)驻襟,當(dāng)獲取到鎖的時(shí)候需要從內(nèi)核態(tài)恢復(fù)夺艰,需要線程上下文切換,影響鎖的性能

為什么atomic會(huì)做為默認(rèn)屬性芋哭,我們不難看出沉衣,蘋(píng)果這么設(shè)計(jì)是想告訴我們,很多情況下减牺,效率換安全是值得的

如何使用鎖

下面一段簡(jiǎn)單代碼豌习,考慮一下輸出結(jié)果

- (void)unlockTest {
    NSMutableString *string = [@"Mike" mutableCopy];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        [string appendString:@"-Locked"];
        NSLog(@"%@",string);
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [string appendString:@"-JailBreaked"];
        NSLog(@"%@",string);
    });
}  

書(shū)寫(xiě)這樣一段代碼,是想在不同線程中在改變變量后拔疚,使用這個(gè)變量

控制臺(tái)輸出:

2019-11-11 16:52:43.019128+0800 DiscoverLock_iOS[89763:11225442] Mike-Locked-JailBreaked
2019-11-11 16:52:43.019128+0800 DiscoverLock_iOS[89763:11225441] Mike-Locked-JailBreaked

這顯然不是想要的結(jié)果肥隆,如何保證我們?cè)诓煌€程中使用的變量,都是我們希望的值呢稚失?答案之一栋艳,就是加鎖

<NSLocking>

OC為我們提供了四種遵循<NSLocking>的類,分別是NSLock/NSCondtionLock/NSRecursiveLock/NSCondition句各,滿足面向?qū)ο缶幊痰男枨?/p>

@protocol NSLocking

- (void)lock;// 阻塞線程吸占,線程休眠
- (void)unlock;

@end

加鎖的基本流程: 【加鎖】->【操作】->【解鎖】
以上提到的4個(gè)類,均可以實(shí)現(xiàn)這個(gè)基礎(chǔ)功能凿宾,下文中不再贅述

- (void)lockedTest {
    NSMutableString *string = [@"Mike" mutableCopy];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        // [鎖 lock];
        [string appendString:@"-Locked"];
        NSLog(@"%@",string);
        // [鎖 unlock];

    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // [鎖 lock];
        [string appendString:@"-JailBreaked"];
        NSLog(@"%@",string);
        // [鎖 unlock];
    });
}

控制臺(tái)輸出:

DiscoverLock_iOS[90562:11303793] Mike-Locked
DiscoverLock_iOS[90562:11303799] Mike-Locked-JailBreaked
DiscoverLock_iOS[90562:11303793] Mike-JailBreaked
DiscoverLock_iOS[90562:11303799] Mike-JailBreaked-Locked

這里的輸出矾屯,結(jié)果不太一樣,側(cè)面說(shuō)明了DISPATCH_QUEUE_PRIORITY并不能保證線程的執(zhí)行順序初厚,如果要明確執(zhí)行順序件蚕,屬于線程同步的范疇,本文不展開(kāi)討論,只會(huì)在NSConditionLock部分簡(jiǎn)單示例如何使用該類做到同步

NSLock

  • - (BOOL)tryLock;:嘗試加鎖排作,如果失敗返回NO牵啦,不會(huì)阻塞線程
  • - (BOOL)lockBeforeDate:(NSDate *)limit;:指定時(shí)間前嘗試加鎖,如果失敗返回NO妄痪,到時(shí)間前阻塞線程

示例代碼:

- (void)lockTest {

    NSMutableString *string = [@"Mike" mutableCopy];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    LOCK(
         [string appendString:@"-Locked"];
         NSLog(@"%@",string);
         sleep(5);
        )
    });

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    TRYLOCK(
            [string appendString:@"-UnLock"];
            NSLog(@"%@",string);
            sleep(3);
        )
    });

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    TRYLOCKINDURATION(2,
                      [string appendString:@"-Ending"];
                      NSLog(@"%@",string);
                      );
    NSLog(@"-=-=-=-=-");
    });
}

控制臺(tái)輸出:

2019-11-11 19:54:08.807763+0800 DiscoverLock_iOS[92986:11465678] Mike-Locked
2019-11-11 19:54:08.807763+0800 DiscoverLock_iOS[92986:11465679] TryLock-NO
2019-11-11 19:54:08.807889+0800 DiscoverLock_iOS[92986:11465679] Mike-Locked-UnLock
2019-11-11 19:54:10.810165+0800 DiscoverLock_iOS[92986:11465677] TryLockBefore-NO
2019-11-11 19:54:10.810523+0800 DiscoverLock_iOS[92986:11465677] Mike-Locked-UnLock-Ending
2019-11-11 19:54:10.810810+0800 DiscoverLock_iOS[92986:11465677] -=-=-=-=-

通過(guò)上面示例代碼輸出可以看到蕾久,- (BOOL)tryLock;并不會(huì)阻塞線程,在嘗試加鎖失敗時(shí)拌夏,立即返回了NO,但是- (BOOL)lockBeforeDate:(NSDate *)limit;則在時(shí)間到之前阻塞了線程操作僧著,在等待相應(yīng)時(shí)間后,返回了NO障簿,并執(zhí)行了下一句打印盹愚,很明顯是在等待期間阻塞了線程

上面代碼中用到的幾個(gè)宏定義,建議以后使用鎖時(shí)站故,盡量保持頭腦清醒或者干脆定義一些便利方法皆怕,保證【上鎖】-【解鎖】的成對(duì)出現(xiàn),避免線程阻塞或死鎖的情況

#define LOCK(...) \
[_lock lock]; \
__VA_ARGS__; \
[_lock unlock]; \

#define TRYLOCK(...) \
BOOL locked = [_lock tryLock]; \
NSLog(@"%@",locked?@"TryLock-YES":@"TryLock-NO"); \
__VA_ARGS__; \
if (locked) [_lock unlock]; \

#define TRYLOCKINDURATION(duration,...) \
BOOL locked = [_lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:duration]]; \
NSLog(@"%@",locked?@"TryLockBefore-YES":@"TryLockBefore-NO"); \
__VA_ARGS__; \
if (locked) [_lock unlock]; \

NSConditionLock

  • - (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;:便利構(gòu)造方法西篓,傳入條件鎖的初始值
  • @property (readonly) NSInteger condition;:當(dāng)前條件鎖的值
  • - (void)lockWhenCondition:(NSInteger)condition;:當(dāng)鎖的條件值與傳入值相等時(shí)愈腾,執(zhí)行接下來(lái)的操作,否則阻塞線程
  • - (BOOL)tryLock;:嘗試加鎖岂津,如果失敗返回NO虱黄,不會(huì)阻塞線程
  • - (BOOL)tryLockWhenCondition:(NSInteger)condition;:嘗試加鎖,當(dāng)鎖的條件值與傳入值相等吮成,則加鎖成功橱乱,否則失敗返回NO,不會(huì)阻塞線程
  • - (void)unlockWithCondition:(NSInteger)condition;:解鎖操作粱甫,同時(shí)變更鎖的條件值為傳入值
  • - (BOOL)lockBeforeDate:(NSDate *)limit;:指定時(shí)間前嘗試加鎖泳叠,如果失敗返回NO,到時(shí)間前阻塞線程
  • - (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;:指定時(shí)間前嘗試加鎖茶宵,當(dāng)鎖的條件值與傳入值相等危纫,則加鎖成功返回YES,否則失敗返回NO乌庶,到時(shí)間前阻塞線程

NSConditionLockNSLock方法類似种蝶,多了一個(gè)condition屬性,以及每個(gè)操作都多了一個(gè)關(guān)于condition屬性的方法安拟,- (void)lockWhenCondition:(NSInteger)condition;只有condition參數(shù)與初始化時(shí)候的condition相等蛤吓,lock才能正確進(jìn)行加鎖操作。而- (void)unlockWithCondition:(NSInteger)condition;并不是當(dāng)條件值符合條件時(shí)才解鎖糠赦,而是解鎖之后,修改當(dāng)前鎖的條件值
假如不使用condition相關(guān)的方法会傲,NSConditionLockNSLock并無(wú)二致

上文中我們提到了線程同步問(wèn)題锅棕,這里一起看一下下面這段代碼

- (void)conditionLockUnordered {
    NSMutableString *conditionString = [[NSMutableString alloc] init];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [conditionString appendString:@"-1-"];
        NSLog(@">>> 1 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [conditionString appendString:@"-2-"];
        NSLog(@">>> 2 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [conditionString appendString:@"-3-"];
        NSLog(@">>> 3 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [conditionString appendString:@"-4-"];
        NSLog(@">>> 4 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [conditionString appendString:@"-5-"];
        NSLog(@">>> 5 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
    });
}

控制臺(tái)輸出:

2019-11-11 20:34:16.875479+0800 DiscoverLock_iOS[93895:11551560] >>> 2 -1--2--4--3- threadInfo:<NSThread: 0x600003905640>{number = 4, name = (null)}<<<
2019-11-11 20:34:16.875525+0800 DiscoverLock_iOS[93895:11551562] >>> 3 -1--2--4--3- threadInfo:<NSThread: 0x600003903680>{number = 6, name = (null)}<<<
2019-11-11 20:34:16.875530+0800 DiscoverLock_iOS[93895:11551561] >>> 1 -1--2- threadInfo:<NSThread: 0x600003908bc0>{number = 3, name = (null)}<<<
2019-11-11 20:34:16.875543+0800 DiscoverLock_iOS[93895:11551559] >>> 4 -1--2--4--3- threadInfo:<NSThread: 0x6000039175c0>{number = 5, name = (null)}<<<
2019-11-11 20:34:16.875628+0800 DiscoverLock_iOS[93895:11551560] >>> 5 -1--2--4--3--5- threadInfo:<NSThread: 0x600003905640>{number = 4, name = (null)}<<<

依然是混亂狀態(tài),上文中NSLock部分已經(jīng)通過(guò)加鎖淌山,控制了讀寫(xiě)的穩(wěn)定性裸燎,那么如果我們想要按照標(biāo)號(hào)依次執(zhí)行,該如何操作泼疑?

熟悉GCD的小伙伴會(huì)說(shuō)這還不簡(jiǎn)單德绿,dispatch_barrier解千愁,當(dāng)然這么寫(xiě)沒(méi)問(wèn)題退渗,但是這里多說(shuō)一嘴移稳,dispatch_barrier只能針對(duì)同一個(gè)并發(fā)隊(duì)列起作用,注意正確初始化的姿勢(shì)dispatch_queue_t thread = dispatch_queue_create("barrier", DISPATCH_QUEUE_CONCURRENT);,而不是干啥都是一句dispatch_get_global_queue(0,0),如果使用Global_Queue,這個(gè)barrier就同普通的dispatch_async沒(méi)什么區(qū)別了

我們要是想在不同線程搞定順序這個(gè)事兒会油,怎么辦呢个粱?這個(gè)時(shí)候NSConditionLock自帶的條件方法,便能幫你實(shí)現(xiàn)這個(gè)功能翻翩,具體看下面的示例代碼

- (void)conditionLockOrdered {
    // NSConditionLock
    NSInteger conditionTag = 0;
    _conditionLock = [[NSConditionLock alloc] initWithCondition:conditionTag];
    
    NSMutableString *conditionString = [[NSMutableString alloc] init];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSLog(@">>> handle 1 <<<");
        [_conditionLock lockWhenCondition:conditionTag];
        [conditionString appendString:@"-1-"];
        NSLog(@">>> 1 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
        [_conditionLock unlockWithCondition:1];
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSLog(@">>> handle 2 <<<");
        [_conditionLock lockWhenCondition:1];
        [conditionString appendString:@"-2-"];
        NSLog(@">>> 2 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
        [_conditionLock unlockWithCondition:2];
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSLog(@">>> handle 3 <<<");
        [_conditionLock lockWhenCondition:2];
        [conditionString appendString:@"-3-"];
        NSLog(@">>> 3 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
        [_conditionLock unlockWithCondition:3];
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@">>> handle 4 <<<");
        [_conditionLock lockWhenCondition:3];
        [conditionString appendString:@"-4-"];
        NSLog(@">>> 4 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
        [_conditionLock unlockWithCondition:4];
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@">>> handle 5 <<<");
        [_conditionLock lockWhenCondition:4];
        [conditionString appendString:@"-5-"];
        NSLog(@">>> 5 %@ threadInfo:%@<<<",conditionString,[NSThread currentThread]);
        [_conditionLock  unlock];
        NSLog(@"-=-=-=-=-=-=-");
    });
    NSLog(@"??");
}

控制臺(tái)輸出:

2019-11-11 20:53:58.237847+0800 DiscoverLock_iOS[94374:11586439] ??
2019-11-11 20:53:58.237862+0800 DiscoverLock_iOS[94374:11586488] >>> handle 1 <<<
2019-11-11 20:53:58.237877+0800 DiscoverLock_iOS[94374:11586489] >>> handle 3 <<<
2019-11-11 20:53:58.237868+0800 DiscoverLock_iOS[94374:11586490] >>> handle 2 <<<
2019-11-11 20:53:58.237887+0800 DiscoverLock_iOS[94374:11586491] >>> handle 4 <<<
2019-11-11 20:53:58.237892+0800 DiscoverLock_iOS[94374:11586495] >>> handle 5 <<<
2019-11-11 20:53:58.238111+0800 DiscoverLock_iOS[94374:11586488] >>> 1 -1- threadInfo:<NSThread: 0x6000014c3380>{number = 3, name = (null)}<<<
2019-11-11 20:53:58.238488+0800 DiscoverLock_iOS[94374:11586490] >>> 2 -1--2- threadInfo:<NSThread: 0x6000014dac40>{number = 4, name = (null)}<<<
2019-11-11 20:53:58.238605+0800 DiscoverLock_iOS[94374:11586489] >>> 3 -1--2--3- threadInfo:<NSThread: 0x6000014daf00>{number = 5, name = (null)}<<<
2019-11-11 20:53:58.239269+0800 DiscoverLock_iOS[94374:11586491] >>> 4 -1--2--3--4- threadInfo:<NSThread: 0x6000014c6740>{number = 6, name = (null)}<<<
2019-11-11 20:53:58.239410+0800 DiscoverLock_iOS[94374:11586495] >>> 5 -1--2--3--4--5- threadInfo:<NSThread: 0x6000014c3480>{number = 7, name = (null)}<<<
2019-11-11 20:53:58.239552+0800 DiscoverLock_iOS[94374:11586495] -=-=-=-=-=-=-

可以看到都许,不同的線程,雖然被調(diào)度的時(shí)機(jī)不同嫂冻,但是因?yàn)?code>NSConditionLock的存在胶征,后續(xù)對(duì)數(shù)據(jù)具體的操作,我們預(yù)想的順序得到了保證桨仿。這種用法筆者并認(rèn)為在任務(wù)耗時(shí)較少的情況下沒(méi)有明顯問(wèn)題的睛低,但是假如存在長(zhǎng)時(shí)間的耗時(shí)操作,還是建議使用dispatch_barrier蹬敲,因?yàn)檫@樣不會(huì)占用過(guò)多資源

NSRecursiveLock

  • - (BOOL)tryLock;:嘗試加鎖暇昂,如果失敗返回NO莺戒,不會(huì)阻塞線程
  • - (BOOL)lockBeforeDate:(NSDate *)limit;:指定時(shí)間前嘗試加鎖伴嗡,如果失敗返回NO,到時(shí)間前阻塞線程
    Api同NSLock完全一樣从铲,區(qū)別在于NSRecursiveLock(遞歸鎖)可以在同一線程中重復(fù)加鎖而不死鎖瘪校,它會(huì)記錄【上鎖】和【解鎖】的次數(shù),當(dāng)這兩個(gè)值平衡時(shí)名段,才會(huì)釋放鎖阱扬,其他線程才可以上鎖成功

先看下一段代碼,會(huì)存在什么問(wèn)題:

@property (nonatomic, assign) NSInteger recursiveNum;// 5
- (void)test_unrecursiveLock {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self recursiveTest];
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        self.recursiveNum = 7;
        NSLog(@">>> changed %ld <<<",self.recursiveNum);
    });
}

- (void)recursiveTest {
    if (self.recursiveNum > 0) {
        self.recursiveNum -= 1;
        NSLog(@">>> %ld <<<",self.recursiveNum);
        [self recursiveTest];
    }
}

控制臺(tái)輸出:

2019-11-11 21:27:13.451703+0800 DiscoverLock_iOS[95105:11645279] >>> 4 <<<
2019-11-11 21:27:13.451709+0800 DiscoverLock_iOS[95105:11645277] >>> changed 7 <<<
2019-11-11 21:27:13.451812+0800 DiscoverLock_iOS[95105:11645279] >>> 6 <<<
2019-11-11 21:27:13.451883+0800 DiscoverLock_iOS[95105:11645279] >>> 5 <<<
2019-11-11 21:27:13.451940+0800 DiscoverLock_iOS[95105:11645279] >>> 4 <<<
2019-11-11 21:27:13.452004+0800 DiscoverLock_iOS[95105:11645279] >>> 3 <<<
2019-11-11 21:27:13.452068+0800 DiscoverLock_iOS[95105:11645279] >>> 2 <<<
2019-11-11 21:27:13.452130+0800 DiscoverLock_iOS[95105:11645279] >>> 1 <<<
2019-11-11 21:27:13.452241+0800 DiscoverLock_iOS[95105:11645279] >>> 0 <<<

同時(shí)存在兩個(gè)線程伸辟,對(duì)已知的recursiveNum的值進(jìn)行寫(xiě)操作麻惶,其中一個(gè)線程使用遞歸調(diào)用,對(duì)該值進(jìn)行了操作信夫,但是同時(shí)另一個(gè)線程改變了這個(gè)值窃蹋,在不加鎖的情況下卡啰,這種操作問(wèn)題很多,如果遞歸中含有重要的邏輯處理警没,競(jìng)態(tài)可能導(dǎo)致整個(gè)邏輯執(zhí)行完的結(jié)果大概率是錯(cuò)誤的匈辱。

如何規(guī)避這種競(jìng)態(tài)導(dǎo)致的不必要的錯(cuò)誤,首先我們想到的是加鎖杀迹,但是如果遞歸加鎖的話亡脸,線程會(huì)重復(fù)加鎖,導(dǎo)致死鎖树酪。所以這時(shí)候必須使用遞歸鎖來(lái)解決這個(gè)問(wèn)題

- (void)test_unrecursiveLock {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self recursiveTest];
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        [_recursiveLock lock];// 遞歸鎖
        self.recursiveNum = 7;
        NSLog(@">>> changed %ld <<<",self.recursiveNum);
        [_recursiveLock unlock];// 解鎖
    });
}
- (void)recursiveTest {
    [_recursiveLock lock];// 遞歸鎖
    if (self.recursiveNum > 0) {
        self.recursiveNum -= 1;
        NSLog(@">>> %ld <<<",self.recursiveNum);
        [self recursiveTest];
    }
    [_recursiveLock unlock];// 解鎖
}

控制臺(tái)輸出:

2019-11-11 21:34:44.422337+0800 DiscoverLock_iOS[95341:11655990] >>> 4 <<<
2019-11-11 21:34:44.422442+0800 DiscoverLock_iOS[95341:11655990] >>> 3 <<<
2019-11-11 21:34:44.422511+0800 DiscoverLock_iOS[95341:11655990] >>> 2 <<<
2019-11-11 21:34:44.422583+0800 DiscoverLock_iOS[95341:11655990] >>> 1 <<<
2019-11-11 21:34:44.422645+0800 DiscoverLock_iOS[95341:11655990] >>> 0 <<<
2019-11-11 21:34:44.422747+0800 DiscoverLock_iOS[95341:11655992] >>> changed 7 <<<

------

2019-11-11 21:37:11.238448+0800 DiscoverLock_iOS[95396:11662426] >>> changed 7 <<<
2019-11-11 21:37:11.238635+0800 DiscoverLock_iOS[95396:11662423] >>> 6 <<<
2019-11-11 21:37:11.238793+0800 DiscoverLock_iOS[95396:11662423] >>> 5 <<<
2019-11-11 21:37:11.238930+0800 DiscoverLock_iOS[95396:11662423] >>> 4 <<<
2019-11-11 21:37:11.239093+0800 DiscoverLock_iOS[95396:11662423] >>> 3 <<<
2019-11-11 21:37:11.239293+0800 DiscoverLock_iOS[95396:11662423] >>> 2 <<<
2019-11-11 21:37:11.239844+0800 DiscoverLock_iOS[95396:11662423] >>> 1 <<<
2019-11-11 21:37:11.239976+0800 DiscoverLock_iOS[95396:11662423] >>> 0 <<<

雖然存在兩種輸出結(jié)果浅碾,但是我們的遞歸操作的邏輯,可以完全不受干擾续语,如果需要控制順序及穗,(敲黑板)要怎么做呢?

NSCondition

  • - (void)wait;:當(dāng)前線程立即進(jìn)入休眠狀態(tài)
  • - (BOOL)waitUntilDate:(NSDate *)limit;:當(dāng)前線程立即進(jìn)入休眠狀態(tài)绵载,limit時(shí)間后喚醒
  • - (void)signal;:喚醒wait后進(jìn)入休眠的單條線程
  • - (void)broadcast;:喚醒wait后進(jìn)入休眠的所有線程埂陆,調(diào)度

有些情況需要協(xié)調(diào)線程之間的執(zhí)行。例如娃豹,一個(gè)線程可能需要等待其他線程返回結(jié)果焚虱,這個(gè)時(shí)候NSCondition可能是個(gè)好選擇

為了能體現(xiàn)NSCondition的作用,我們舉一個(gè)可能并不是很恰當(dāng)?shù)?strong>生產(chǎn)者-消費(fèi)者的例子:
我們現(xiàn)在有一條柔性生產(chǎn)線懂版,限定每個(gè)批次只能生產(chǎn)3件商品鹃栽,耗時(shí)6s,同時(shí)開(kāi)放網(wǎng)絡(luò)購(gòu)買(mǎi)平臺(tái)讓大家搶購(gòu)拼團(tuán)躯畴,訂單式銷售民鼓,三人成團(tuán),現(xiàn)在有三位天選之子 Tom/Mike/Lily 從全球千萬(wàn)人中脫穎而出蓬抄,成功成團(tuán)丰嘉。為了增強(qiáng)可玩性,活動(dòng)是從開(kāi)啟的一刻起嚷缭,同時(shí)開(kāi)始生產(chǎn)和搶購(gòu)饮亏,3件庫(kù)存銷售完成后,再次進(jìn)行同時(shí)進(jìn)行生產(chǎn)和搶購(gòu)活動(dòng)

代碼示例如下:

@interface Producer : NSObject
@property (nonatomic, assign) BOOL shouldProduce;
@property (nonatomic, strong) NSString *itemName;
@property (nonatomic, strong) NSCondition *condition;
@property (nonatomic, strong) NSMutableArray *collector;

- (instancetype)initWithConditon:(NSCondition *)condition collector:(NSMutableArray *)collector;
- (void)produce;
@end

@implementation Producer

- (instancetype)initWithConditon:(NSCondition *)condition collector:(NSMutableArray *)collector{
    
    self = [super init];
    if (self) {
        self.condition = condition;
        self.collector = collector;
        self.shouldProduce = NO;
        self.itemName = nil;
    }
    return self;
}

-(void)produce{
    self.shouldProduce = YES;
    while (self.shouldProduce) {
        NSLog(@"準(zhǔn)備生產(chǎn)");
        [self.condition lock];
        NSLog(@"- p lock -");
        if (self.collector.count > 0 ) {
            NSLog(@"- p - wait");
            [self.condition wait];
        }
        NSLog(@"開(kāi)始生產(chǎn)");
        [self.collector addObject:@"商品1"];
        [self.collector addObject:@"商品2"];
        [self.collector addObject:@"商品3"];
        NSLog(@"生產(chǎn):商品1/商品2/商品3");
        sleep(6);
        NSLog(@"生產(chǎn)結(jié)束");
        [self.condition broadcast];
        NSLog(@"- p signal -");
        [self.condition unlock];
        NSLog(@"- p unlock -");
    }
    NSLog(@"-結(jié)束生產(chǎn)-");
}

@end

@interface Consumer : NSObject
@property (nonatomic, assign) BOOL shouldConsumer;
@property (nonatomic, strong) NSCondition *condition;
@property (nonatomic, strong) NSMutableArray *collector;
@property (nonatomic,   copy) NSString *itemName;
- (instancetype)initWithConditon:(NSCondition *)condition collector:(NSMutableArray *)collector name:(NSString *)name;
- (void)consumer;
@end

@implementation Consumer
- (instancetype)initWithConditon:(NSCondition *)condition collector:(NSMutableArray *)collector name:(NSString *)name{
    self = [super init];
    if (self) {
        self.condition = condition;
        self.collector = collector;
        self.shouldConsumer = NO;
        self.itemName = name;
    }
    return self;
}

-(void)consumer{
    self.shouldConsumer = YES;
    while (self.shouldConsumer) {
        NSLog(@"%@-準(zhǔn)備購(gòu)買(mǎi)",self.itemName);
        [self.condition lock];
        NSLog(@"- c:%@ lock -",self.itemName);
        if (self.collector.count == 0 ) {
            NSLog(@"- c:%@ wait -",self.itemName);
            [self.condition wait];
        }
        NSString *item = [self.collector objectAtIndex:0];
        NSLog(@"%@-買(mǎi)入:%@",self.itemName,item);
        [self.collector removeObjectAtIndex:0];
        sleep(2);
        [self.condition signal];
        NSLog(@"- c:%@ signal -",self.itemName);
        [self.condition unlock];
        NSLog(@"- c:%@ unlock -",self.itemName);
    }
    NSLog(@"-%@結(jié)束購(gòu)買(mǎi)-",self.itemName);
}
@end

// 調(diào)用
{
    NSMutableArray *pipeline = [NSMutableArray array];
    NSCondition *condition = [NSCondition new];
    
    Producer *p = [[Producer alloc] initWithConditon:condition collector:pipeline];
    Consumer *c = [[Consumer alloc] initWithConditon:condition collector:pipeline name:@"Tom"];
    Consumer *c1 = [[Consumer alloc] initWithConditon:condition collector:pipeline name:@"Mike"];
    Consumer *c2 = [[Consumer alloc] initWithConditon:condition collector:pipeline name:@"Lily"];
    [[[NSThread alloc] initWithTarget:c selector:@selector(consumer) object:c] start];
    [[[NSThread alloc] initWithTarget:c1 selector:@selector(consumer) object:c] start];
    [[[NSThread alloc] initWithTarget:c2 selector:@selector(consumer) object:c] start];
    [[[NSThread alloc] initWithTarget:p selector:@selector(produce) object:p] start];
    

    sleep(15);
    NSLog(@"<----------------->");
    p.shouldProduce = NO;
    c.shouldConsumer = NO;
    c1.shouldConsumer = NO;
    c2.shouldConsumer = NO;
}

部分控制臺(tái)輸出:

2019-11-12 17:04:03.662926+0800 DiscoverLock_iOS[7110:12246052] Mike-準(zhǔn)備購(gòu)買(mǎi)
2019-11-12 17:04:03.662916+0800 DiscoverLock_iOS[7110:12246051] Tom-準(zhǔn)備購(gòu)買(mǎi)
2019-11-12 17:04:03.662990+0800 DiscoverLock_iOS[7110:12246053] Lily-準(zhǔn)備購(gòu)買(mǎi)
2019-11-12 17:04:03.663005+0800 DiscoverLock_iOS[7110:12246054] 準(zhǔn)備生產(chǎn)
2019-11-12 17:04:03.663083+0800 DiscoverLock_iOS[7110:12246053] - c:Lily lock -
2019-11-12 17:04:03.663144+0800 DiscoverLock_iOS[7110:12246053] - c:Lily wait -
2019-11-12 17:04:03.663254+0800 DiscoverLock_iOS[7110:12246052] - c:Mike lock -
2019-11-12 17:04:03.663439+0800 DiscoverLock_iOS[7110:12246052] - c:Mike wait -
2019-11-12 17:04:03.663805+0800 DiscoverLock_iOS[7110:12246051] - c:Tom lock -
2019-11-12 17:04:03.663903+0800 DiscoverLock_iOS[7110:12246051] - c:Tom wait -
2019-11-12 17:04:03.664126+0800 DiscoverLock_iOS[7110:12246054] - p lock -
2019-11-12 17:04:03.664297+0800 DiscoverLock_iOS[7110:12246054] 開(kāi)始生產(chǎn)
2019-11-12 17:04:03.664433+0800 DiscoverLock_iOS[7110:12246054] 生產(chǎn):商品1/商品2/商品3
2019-11-12 17:04:09.669735+0800 DiscoverLock_iOS[7110:12246054] 生產(chǎn)結(jié)束

基于多線程并發(fā)的工作原理阅爽,通過(guò)上面的部分打印結(jié)果路幸,也很容易得到這個(gè)結(jié)論。由于不符合購(gòu)買(mǎi)條件付翁,Lily/Mike/Tom都只能選擇wait简肴,這個(gè)時(shí)候,生產(chǎn)者獲取到鎖并執(zhí)行生產(chǎn)代碼百侧,在生產(chǎn)完成后砰识,broadcast或者signal告訴其他線程杂伟,可以喚醒線程并繼續(xù)執(zhí)行消費(fèi)者相關(guān)代碼。
NSCondition相較于NSConditionLock的不同點(diǎn)在于他依賴的是外部值仍翰,能夠滿足更多復(fù)雜需求場(chǎng)景赫粥。
假如將上述代碼中生產(chǎn)者的broadcast替換成signal后發(fā)現(xiàn),在當(dāng)前這種特定場(chǎng)景下予借,這兩個(gè)方法的作用似乎并沒(méi)有什么區(qū)別越平。而且感興趣的同學(xué),可以使用上述代碼多運(yùn)行幾次灵迫,看看是否能夠得出同筆者一樣的猜測(cè):

  1. NSCondition會(huì)自身通過(guò)隊(duì)列管理協(xié)同任務(wù)的調(diào)度
  2. wait的任務(wù)依次入等待隊(duì)列
  3. 未wait的任務(wù)根據(jù)獲得鎖的順序依次入執(zhí)行隊(duì)列
  4. wait任務(wù)的等待隊(duì)列會(huì)在執(zhí)行隊(duì)列執(zhí)行完后依次執(zhí)行并入執(zhí)行隊(duì)列
  5. 第一次調(diào)度順序確定后秦叛,后續(xù)任務(wù)的執(zhí)行,按照?qǐng)?zhí)行隊(duì)列緩存依次出列執(zhí)行
    這里僅做猜想瀑粥,具體實(shí)現(xiàn)可能并非如此挣跋,待大佬指點(diǎn)迷津或有機(jī)會(huì)鶸筆者自行研究

OSSpinLock

看了NSCondition這么個(gè)復(fù)雜的東西,我們看點(diǎn)輕松的狞换,OSSpinLock是蘋(píng)果在iOS10之前提供的自旋鎖方案避咆,但是存在優(yōu)先級(jí)反轉(zhuǎn)的問(wèn)題,被蘋(píng)果廢棄修噪,以前源碼中使用OSSpinLock的地方查库,都被蘋(píng)果替換成了pthread_mutex

被廢棄的OSSpinLock.png

官方備注.png

os_unfair_lock

os_unfair_lockiOS10以后新增的低級(jí)別加鎖方案,本質(zhì)是互斥鎖黄琼,這里需要注意樊销,目前很多文章認(rèn)為他是作為替代OSSpinLock的方案就是自旋鎖是有問(wèn)題的

  • void os_unfair_lock_lock(os_unfair_lock_t lock);:加鎖
  • bool os_unfair_lock_trylock(os_unfair_lock_t lock);:嘗試加鎖,成功返回true脏款,失敗返回false
  • void os_unfair_lock_unlock(os_unfair_lock_t lock);:解鎖
  • void os_unfair_lock_assert_owner(os_unfair_lock_t lock);:如果當(dāng)前線程未持有指定的鎖围苫,則觸發(fā)斷言
  • void os_unfair_lock_assert_not_owner(os_unfair_lock_t lock);:如果當(dāng)前線程持有指定的鎖,則觸發(fā)斷言

各方法同常見(jiàn)的鎖沒(méi)太大差別撤师,可以看下方法注釋剂府,只是需要注意一下初始化方式

{
    os_unfair_lock_t unfairLock;
    unfairLock = &(OS_UNFAIR_LOCK_INIT);
}

@synchronize(object)

@synchronized(object)指令使用傳入的對(duì)象作為該鎖的唯一標(biāo)識(shí),只有當(dāng)標(biāo)識(shí)相同時(shí)丈氓,才滿足互斥
@synchronized(object)指令實(shí)現(xiàn)鎖的優(yōu)點(diǎn)就是我們不需要在代碼中顯式的創(chuàng)建鎖對(duì)象周循,便可以實(shí)現(xiàn)鎖的機(jī)制,而且不用擔(dān)心忘記解鎖
使用方法極其常見(jiàn)万俗,不做示例了

dispatch_semaphore

  • dispatch_semaphore_t dispatch_semaphore_create(long value);:創(chuàng)建信號(hào)量,傳入初始值
  • long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);:當(dāng)信號(hào)<=0時(shí)饮怯,根據(jù)傳入的時(shí)間阻塞線程闰歪;如果信號(hào)>0則不阻塞線程,并對(duì)信號(hào)-1處理
  • long dispatch_semaphore_signal(dispatch_semaphore_t dsema);:對(duì)信號(hào)+1處理

GCD為我們提供的信號(hào)量也是常用的加鎖方式蓖墅,常見(jiàn)用法是初始化信號(hào)值為1

{
    dispatch_semaphore_t lock = dispatch_semaphore_create(1);

    dispatch_semaphore_wait(lock,DISPATCH_TIME_FOREVER);
    // 操作
    dispatch_semaphare_signal(lock);
}

常規(guī)操作大家都知道库倘,有常規(guī)操作临扮,那么一定也有非常規(guī)操作,可以看一下AFNetwork給我們的示范

- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
    __block NSArray *tasks = nil;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
            tasks = dataTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
            tasks = uploadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
            tasks = downloadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
            tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
        }

        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return tasks;
}

AFURLSessionManager中教翩,初始化使用dispatch_semaphore_create(0)杆勇,在return tasks;前調(diào)用dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);阻塞線程,待block將目標(biāo)值賦值后饱亿,執(zhí)行dispatch_semaphore_signal(semaphore);,此時(shí)tasks已經(jīng)有值蚜退,線程被喚醒后正常返回。很秀

pthread_mutex

C語(yǔ)言下的互斥鎖方案彪笼,是<NSLocking>協(xié)議下四個(gè)類的底層

鎖常用函數(shù):

  • pthread_mutex_init:動(dòng)態(tài)初始化互斥量
  • PTHREAD_MUTEX_INITIALIZER:靜態(tài)創(chuàng)建互斥量
  • pthread_mutex_lock:給一個(gè)互斥量加鎖
  • pthread_mutex_trylock:加鎖钻注,如果失敗不阻塞
  • pthread_mutex_unlock:解鎖
  • pthread_mutex_destroy:銷毀鎖

參數(shù)配置函數(shù):

  • pthread_mutexattr_init:初始化參數(shù)
  • pthread_mutexattr_settype:設(shè)置類型
  • pthread_mutexattr_setpshared:設(shè)置作用域
  • pthread_mutexattr_destroy:銷毀參數(shù)

條件常見(jiàn)函數(shù):

  • pthread_cond_init:動(dòng)態(tài)初始化條件量
  • PTHREAD_COND_INITIALIZER:靜態(tài)創(chuàng)建條件量
  • pthread_cond_wait:傳入條件量及鎖
  • pthread_cond_signal:喚醒單條線程并加鎖
  • pthread_cond_broadcast:廣播喚醒所有線程
  • pthread_cond_destroy:銷毀條件

以上函數(shù)都是有返回值的,需要注意的是配猫,若成功則返回0幅恋,否則返回錯(cuò)誤編號(hào),不是我們習(xí)慣中的成功YES失敗NO

鎖類型:

  • PTHREAD_MUTEX_NORMAL:缺省值泵肄,這種類型的互斥鎖不會(huì)自動(dòng)檢測(cè)死鎖捆交。如果一個(gè)線程試圖對(duì)一個(gè)互斥鎖重復(fù)鎖定,將會(huì)引起這個(gè)線程的死鎖腐巢。如果試圖解鎖一個(gè)由別的線程鎖定的互斥鎖會(huì)引發(fā)不可預(yù)料的結(jié)果零渐。如果一個(gè)線程試圖解鎖已經(jīng)被解鎖的互斥鎖也會(huì)引發(fā)不可預(yù)料的結(jié)果
  • PTHREAD_MUTEX_ERRORCHECK:這種類型的互斥鎖會(huì)自動(dòng)檢測(cè)死鎖。如果一個(gè)線程試圖對(duì)一個(gè)互斥鎖重復(fù)鎖定系忙,將會(huì)返回一個(gè)錯(cuò)誤代碼诵盼。如果試圖解鎖一個(gè)由別的線程鎖定的互斥鎖將會(huì)返回一個(gè)錯(cuò)誤代碼。如果一個(gè)線程試圖解鎖已經(jīng)被解鎖的互斥鎖也將會(huì)返回一個(gè)錯(cuò)誤代碼
  • PTHREAD_MUTEX_RECURSIVE:如果一個(gè)線程對(duì)這種類型的互斥鎖重復(fù)上鎖银还,不會(huì)引起死鎖风宁,一個(gè)線程對(duì)這類互斥鎖的多次重復(fù)上鎖必須由這個(gè)線程來(lái)重復(fù)相同數(shù)量的解鎖,這樣才能解開(kāi)這個(gè)互斥鎖蛹疯,別的線程才能得到這個(gè)互斥鎖戒财。如果試圖解鎖一個(gè)由別的線程鎖定的互斥鎖將會(huì)返回一個(gè)錯(cuò)誤代碼。如果一個(gè)線程試圖解鎖已經(jīng)被解鎖的互斥鎖也將會(huì)返回一個(gè)錯(cuò)誤代碼捺弦。這種類型的互斥鎖只能是進(jìn)程私有的(作用域?qū)傩訮THREAD_PROCESS_PRIVATE)
  • PTHREAD_MUTEX_DEFAULT:就是NORMAL類型

鎖作用域:

  • PTHREAD_PROCESS_PRIVATE:缺省值饮寞,作用域?yàn)檫M(jìn)程內(nèi)
  • PTHREAD_PROCESS_SHARED:作用域?yàn)檫M(jìn)程間

使用示例:

static pthread_mutex_t c_lock;
- (void)testPthread_mutex {
    pthread_mutexattr_t c_lockAttr;
    pthread_mutexattr_init(&c_lockAttr);
    pthread_mutexattr_settype(&c_lockAttr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutexattr_setpshared(&c_lockAttr, PTHREAD_PROCESS_PRIVATE);
    
    pthread_mutex_init(&c_lock, &c_lockAttr);
    pthread_mutexattr_destroy(&c_lockAttr);

    pthread_t thread1;
    pthread_create(&thread1, NULL, _thread1, NULL);
    
    pthread_t thread2;
    pthread_create(&thread2, NULL, _thread2, NULL);
}

void *_thread1() {
    pthread_mutex_lock(&c_lock);
    printf("thread 1\n");
    pthread_mutex_unlock(&c_lock);
    return 0;
}

void *_thread2() {
    pthread_mutex_lock(&c_lock);
    printf("thread 2 busy\n");
    sleep(3);
    printf("thread 2\n");
    pthread_mutex_unlock(&c_lock);
    return 0;
}

使用鎖的注意點(diǎn)

  1. 互斥量需要時(shí)間來(lái)加鎖和解鎖。鎖住較少互斥量的程序通常運(yùn)行得更快列吼。所以幽崩,互斥量應(yīng)該盡量少,夠用即可寞钥,每個(gè)互斥量保護(hù)的區(qū)域應(yīng)則盡量大慌申。

  2. 互斥量的本質(zhì)是串行執(zhí)行。如果很多線程需要領(lǐng)繁地加鎖同一個(gè)互斥量理郑,
    則線程的大部分時(shí)間就會(huì)在等待蹄溉,這對(duì)性能是有害的咨油。如果互斥量保護(hù)的數(shù)據(jù)(或代碼)包含彼此無(wú)關(guān)的片段,則可以特大的互斥量分解為幾個(gè)小的互斥量來(lái)提高性能柒爵。這樣役电,任意時(shí)刻需要小互斥量的線程減少,線程等待時(shí)間就會(huì)減少棉胀。所以法瑟,互斥量應(yīng)該足夠多(到有意義的地步),每個(gè)互斥量保護(hù)的區(qū)域則應(yīng)盡量的少膏蚓。

參考文檔

Posix互斥量pthread_mutex_t
iOS 常見(jiàn)知識(shí)點(diǎn)(三):Lock
不再安全的 OSSpinLock
How does @synchronized lock/unlock in Objective-C?
[爆棧熱門(mén) iOS 問(wèn)題] atomic 和 nonatomic 有什么區(qū)別瓢谢?
《高性能iOS應(yīng)用開(kāi)發(fā)中文版》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市驮瞧,隨后出現(xiàn)的幾起案子氓扛,更是在濱河造成了極大的恐慌,老刑警劉巖论笔,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件采郎,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡狂魔,警方通過(guò)查閱死者的電腦和手機(jī)蒜埋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)最楷,“玉大人整份,你說(shuō)我怎么就攤上這事∽阉铮” “怎么了烈评?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)犯建。 經(jīng)常有香客問(wèn)我讲冠,道長(zhǎng),這世上最難降的妖魔是什么适瓦? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任竿开,我火速辦了婚禮,結(jié)果婚禮上玻熙,老公的妹妹穿的比我還像新娘否彩。我一直安慰自己,他們只是感情好揭芍,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布胳搞。 她就那樣靜靜地躺著,像睡著了一般称杨。 火紅的嫁衣襯著肌膚如雪肌毅。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天姑原,我揣著相機(jī)與錄音悬而,去河邊找鬼。 笑死锭汛,一個(gè)胖子當(dāng)著我的面吹牛笨奠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播唤殴,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼般婆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了朵逝?” 一聲冷哼從身側(cè)響起蔚袍,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎配名,沒(méi)想到半個(gè)月后啤咽,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡渠脉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年宇整,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芋膘。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鳞青,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出为朋,到底是詐尸還是另有隱情臂拓,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布潜腻,位于F島的核電站埃儿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏融涣。R本人自食惡果不足惜童番,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望威鹿。 院中可真熱鬧剃斧,春花似錦、人聲如沸忽你。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至根蟹,卻和暖如春脓杉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背简逮。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工球散, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人散庶。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓蕉堰,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親悲龟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子屋讶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354