iOS 鎖&線程安全

為什么要用鎖?

為了保證多線程訪問一塊公共資源時(shí)虚倒,對資源的保護(hù)逝撬。或者說是多線程安全 or 線程同步
但是線程同步的實(shí)現(xiàn)并不是只有加鎖才能解決度宦,串行隊(duì)列也是一種解決方式踢匣。

鎖通用使用步驟
//帶?的是一定要有的步驟。 
?初始化鎖 | 賦予一定參數(shù)
?加鎖 | 通過一定條件加鎖
等待 | 線程進(jìn)入 wait 等待條件  
?處理公共資源代碼 { } 
?解鎖 | 給鎖賦予條件
銷毀鎖 & 鎖的屬性

??????????????????????正片????????????????????

1.OSSpinLock (Deprecated)

介紹: 是一種'自旋鎖'
使用: 
#import <libkern/OSAtomic.h>
    OSSpinLock lock = OS_SPINLOCK_INIT;
    //加鎖
    OSSpinLockLock(&lock);
    //嘗試加鎖
    BOOL lockStatus = OSSpinLockTry(&lock);
    //你需要保護(hù)的操作
    {}
    //解鎖
    OSSpinLockUnlock(&lock);
#define OS_SPINLOCK_INIT    0   (就是把lock賦值為 0)
不過這個(gè)鎖已經(jīng)被廢棄掉了戈抄±牖#可查看.h文件中的介紹。
'OSSpinLock' is deprecated: first deprecated in iOS 10.0 - Use os_unfair_lock() from <os/lock.h> instead

廢棄原因:
新版 iOS 中划鸽,系統(tǒng)維護(hù)了 5 個(gè)不同的線程優(yōu)先級/QoS: background输莺,utility,default裸诽,user-initiated嫂用,user-interactive。高優(yōu)先級線程始終會在低優(yōu)先級線程前執(zhí)行丈冬,一個(gè)線程不會受到比它更低優(yōu)先級線程的干擾嘱函。這種線程調(diào)度算法會產(chǎn)生潛在的優(yōu)先級反轉(zhuǎn)問題,從而破壞了 spin lock埂蕊。
具體來說往弓,如果一個(gè)低優(yōu)先級的線程獲得鎖并訪問共享資源,這時(shí)一個(gè)高優(yōu)先級的線程也嘗試獲得這個(gè)鎖粒梦,它會處于 spin lock 的忙等狀態(tài)從而占用大量 CPU亮航。此時(shí)低優(yōu)先級線程無法與高優(yōu)先級線程爭奪 CPU 時(shí)間,從而導(dǎo)致任務(wù)遲遲完不成匀们、無法釋放 lock。這并不只是理論上的問題准给,libobjc 已經(jīng)遇到了很多次這個(gè)問題了泄朴,于是蘋果的工程師停用了 OSSpinLock。

but, 除非開發(fā)者能保證訪問鎖的線程全部都處于同一優(yōu)先級露氮,否則 iOS 系統(tǒng)中所有類型的自旋鎖都不能再使用了祖灰。

2.os_unfair_lock

介紹: 是一種低級鎖('Low-level'),'互斥鎖' 畔规,看了好多博客說是自旋鎖局扶,其實(shí)都是錯(cuò)的。
os_unfair_lock雖然是  'OSSpinLock'  的替代品叁扫,但是它確實(shí)是互斥鎖三妈。
??有對os_unfair_lock是互斥鎖的考證。
.h中的官方解釋 
Does not spin on contention but waits in the kernel to be woken up
by an unlock

使用方法

    #import <os/lock.h>
    //靜態(tài)初始化
    os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
    //加鎖
    os_unfair_lock_lock(&lock);
    bool isCanLock = os_unfair_lock_trylock(&lock);
    //解鎖
    os_unfair_lock_unlock(&lock);

3.pthread_mutex_t

介紹: 是一種跨平臺的鎖(Linux,Unix,OS,iOS)莫绣,本質(zhì)上是一種 互斥鎖畴蒲,可以動態(tài)初始化。
根據(jù)傳入的參數(shù)生成對應(yīng)的鎖.(e.g. 遞歸鎖)

使用介紹

#import <pthread.h>
    //靜態(tài)初始化鎖
    pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
    //動態(tài)初始化
    pthread_mutex_t mutex;
    //初始化屬性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
     //傳入  PTHREAD_MUTEX_RECURSIVE  (遞歸鎖屬性对室。)
     //PTHREAD_MUTEX_ERRORCHECK(錯(cuò)誤檢查)
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
    pthread_mutex_init(&mutex, NULL); (初始化屬性可為null) 
    //注: #  define NULL ((void*)0)
    //動態(tài)初始化鎖
    pthread_mutex_init(&mutex, &attr);
     //銷毀模燥,一定銷毀對應(yīng)的屬性咖祭。
    pthread_mutexattr_destroy(&attr);
     pthread_mutex_destroy(&mutex);
    //加鎖解鎖
    pthread_mutex_lock(&mutex);
    pthread_mutex_unlock(&mutex);
// 關(guān)于另一種屬性的解釋 PTHREAD_MUTEX_ERRORCHECK
This type of mutex provides error checking. A thread attempting to relock 
this mutex without first unlocking it shall return with an error. A thread 
attempting to unlock a mutex which another thread has locked shall return
 with an error. A thread attempting to unlock an unlocked mutex shall 
return with an error.

4.pthread_cond_t

介紹:條件鎖,是pthread_mutex_t引申出來的鎖蔫骂。
配合pthread_mutex_t來一起使用么翰,可以用于線程的同步。亦或者是解決線程間的依賴關(guān)系辽旋。 
當(dāng)當(dāng)前線程進(jìn)入 wait 之后硬鞍, 當(dāng)前線程 mutex 會放開,保證其他線程可以拿到鎖 mutex 執(zhí)行戴已,
直到收到 signal 信號或者broadcast之后才會喚醒 當(dāng)前線程固该,并且 喚醒后再次對 mutex 進(jìn)行加鎖。
    //條件鎖
    pthread_cond_t cond;
    //靜態(tài)初始化
    pthread_cond_t cond2 = PTHREAD_COND_INITIALIZER;
    pthread_condattr_t condAttr;
    //初始化attr參數(shù)
    pthread_condattr_init(&condAttr);
    //動態(tài)初始化糖儡,也可不傳attr參數(shù)
    pthread_cond_init(&cond, &condAttr);
    pthread_cond_init(&cond, NULL);
    //1.放開當(dāng)前鎖 2.使當(dāng)前線程進(jìn)入休眠(wait) 3.喚醒后會再次mutex程加鎖
    pthread_cond_wait(&cond, &mutex);
    //在time之前等待伐坏,之后放開鎖。
    pthread_cond_timedwait(&cond, &mutex, const struct timespec *restrict _Nullable);
    //喚醒一個(gè)被wait的線程
    pthread_cond_signal(&cond);
    //喚醒所有被wait的線程
    pthread_cond_broadcast(&cond);
    //銷毀attr 和cond
    pthread_condattr_destroy(&condAttr);
    pthread_cond_destroy(&cond);
5.pthread_rwlock_t
介紹: 讀寫鎖,(互斥鎖的進(jìn)化)分為讀鎖(rlock)和寫鎖(wlock),可以有多個(gè)線程共同持有讀鎖握联,但是寫鎖只能有一個(gè)線程持有桦沉,如果讀鎖被持有是,寫鎖是不能持有的金闽。
需要等待讀鎖unlock 才能持有寫鎖,同樣需要寫鎖unlock才能持有讀鎖纯露。

具體使用

//靜態(tài)初始化
        pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER;
        _rwlock = lock;
//動態(tài)初始化
        pthread_rwlockattr_init(&_rwlock_attr);
        pthread_rwlock_init(&_rwlock, &_rwlock_attr);
- (void)__add {
//寫鎖上鎖
    pthread_rwlock_wrlock(&_rwlock);
    [super __add];
    pthread_rwlock_unlock(&_rwlock);
}
- (void)__readArr {
//讀鎖上鎖
    pthread_rwlock_rdlock(&_rwlock);
    NSLog(@"self.lockArr=%@",self.lockArray);
    pthread_rwlock_unlock(&_rwlock);
}
- (void)dealloc {
//銷毀 鎖 & 鎖的屬性
    pthread_rwlockattr_destroy(&_rwlock_attr);
    pthread_rwlock_destroy(&_rwlock);
}
/*
 * Mutex type attributes
 */
#define PTHREAD_MUTEX_NORMAL        0
#define PTHREAD_MUTEX_ERRORCHECK    1
#define PTHREAD_MUTEX_RECURSIVE     2
#define PTHREAD_MUTEX_DEFAULT       PTHREAD_MUTEX_NORMAL
6.NSLock 、NSCondition 代芜、NSConditionLock和NSRecursiveLock
簡介: 都屬于互斥鎖埠褪。
NSLock 底層是對 pthread_mutex_t 的封裝.對應(yīng)的參數(shù)是 PTHREAD_MUTEX_NORMAL
NSCondition 底層則是對 pthread_cond_t 的封裝. 
NSConditionLock 的底層則是使 NSCondition 實(shí)現(xiàn)的.
NSRecursiveLock 則是對 pthread_mutex_t 的 PTHREAD_MUTEX_RECURSIVE 參數(shù)的封裝。
實(shí)現(xiàn)原理可以通過 GNUstep 查看 
以上都是蘋果對pthread_mutex的封裝挤庇,讓鎖的使用更面向?qū)ο罅恕?

具體使用

    NSLock *lock = [[NSLock alloc] init];
    //嘗試加鎖
    BOOL isLocked = [lock tryLock];
    [lock lock];
    [lock unlock];

    //由于 NSCondition 是對 pthread_cond_t 的封裝钞速,所以使用方法與 pthread_cond_t 基本一致。
    //不同的是不需要我們?nèi)ナ謩愉N毀鎖嫡秕。
    NSCondition *conLock = [[NSCondition alloc] init];
    [conLock lock];
    [conLock wait];
    [conLock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
    [conLock unlock];
    [conLock signal];
    [conLock broadcast];

//NSConditionLock  設(shè)置condition 保證多線程中的同步渴语,按自己想要的順序執(zhí)行。
//先add 然后 remove昆咽。
self.conditionLock = [[NSConditionLock alloc] init]; //默認(rèn)condition 是0驾凶。
self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
- (void)demoTest {
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    sleep(3);
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}
- (void)__remove {
    [self.conditionLock lockWhenCondition:2];
    [super __remove];
    [self.conditionLock unlock];
}
- (void)__add {
    [self.conditionLock lockWhenCondition:1];
    [super __add];
    [self.conditionLock unlockWithCondition:2];
}

// NSRecursiveLock 用法類似于 NSLock 但是可以遞歸加鎖。
    NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
    [recursiveLock lock];
    [recursiveLock unlock];
7.dispatch_semaphore
簡單來說并不是鎖掷酗,而是通過信號的方式调违,可以實(shí)現(xiàn)鎖的一種機(jī)制。

簡單使用

//create 的value 代表最多有幾個(gè)信號量
     dispatch_semaphore_t sema = dispatch_semaphore_create(0);
    dispatch_after(dispatch_time( DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC),              dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"走到了塊里");
        dispatch_semaphore_signal(sema);//發(fā)送1個(gè)信號量
    });
    NSLog(@"等待-----");
//如果信號量的值 >0,就讓信號量的值減1汇在,然后繼續(xù)往下執(zhí)行代碼
//如果信號量的值 <= 0,就讓線程 `sleep` (休眠).直到信號量 >0.
    dispatch_wait(sema, DISPATCH_TIME_FOREVER);
//  dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);  //兩個(gè)方法都可
    NSLog(@"完成”);
2018-07-13 16:34:17.524572 AddressBook[6830:473890] 等待-----
2018-07-13 16:34:20.823293 AddressBook[6830:473911] 走到了塊里
2018-07-13 16:34:20.823515 AddressBook[6830:473890] 完成
8.@synchronized(id obj) { }
簡介: 互斥鎖
關(guān)于 更深的synchronized的實(shí)現(xiàn)。實(shí)際上也是對  pthread_mutex 的遞歸鎖的一個(gè)封裝糕殉。

簡單使用和底層實(shí)現(xiàn):

 @synchronized(id obj) {
      //公共資源操作
        NSLog(@"加鎖");
    }

實(shí)現(xiàn)原理:  調(diào)用堆棧
0x107d25111 <+2193>: callq  0x107d27b68 ; symbol stub for: objc_sync_enter
0x107d25139 <+2233>: callq  0x107d27ab4 ; symbol stub for: NSLog
0x107d2514a <+2250>: callq  0x107d27b6e ; symbol stub for: objc_sync_exit
0x107d2515c <+2268>: callq  0x107d27b44 ; symbol stub for: objc_release
通過查看 objc4-723 中 objc-sync.mm 源碼亩鬼,可以知道:
int objc_sync_enter(id obj)
{
 int result = OBJC_SYNC_SUCCESS;
   if (obj) {
        SyncData* data = id2data(obj, ACQUIRE);
        assert(data);
        data->mutex.lock();
    } else {
        // @synchronized(nil) does nothing
        if (DebugNilSync) {
            _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
        }
        objc_sync_nil();
    }
  return result;
}
SyncData結(jié)構(gòu)體如下
typedef struct SyncData {
    struct SyncData* nextData;
    DisguisedPtr<objc_object> object;
    int32_t threadCount;  // number of THREADS using this block
    recursive_mutex_t mutex;
} SyncData;

objc_sync_enter 中 通過 synchronized 傳入的對象obj 生成 data 結(jié)構(gòu)體指針
然后data 在 LIST_FOR_OBJ(obj)  (static StripedMap<SyncList> sDataLists)中
取出對應(yīng)的 mutex.lock. 
這個(gè)obj 就作為這個(gè)鎖的key殖告,從對應(yīng)的hash表中找到對應(yīng)的鎖。
只要傳入的obj相同雳锋,對應(yīng)的鎖就相同黄绩。
如果傳入nil 則  // @synchronized(nil) does nothing (什么也做)

mutex 對應(yīng)的就是 recursive_mutex_t。 
通過源碼再往里面查看就知道 synchronized 實(shí)質(zhì)就是 一把  RECURSIVE 的pthread_mutex_t (遞歸鎖)玷过。

lockdebug_recursive_mutex_lock(recursive_mutex_t *lock)
{
    auto& locks = ownedLocks();
    setLock(locks, lock, RECURSIVE);
}

補(bǔ)充 atomic (原子性) 很好的參考博客

改變setter爽丹,getter方法的實(shí)現(xiàn),對方法進(jìn)行加鎖和解鎖的操作(原子性操作)辛蚊。
保證 setter和getter方法內(nèi)部線程同步粤蝎。底層實(shí)現(xiàn)是 os_unfair_lock 。
但是: 并不能保證 使用atomic修飾的屬性 的線程安全袋马。
而且性能消耗太大, 因?yàn)?setter和getter 方法調(diào)用頻率太高3跖臁! 

源碼實(shí)現(xiàn):

objc4-723 中全局搜索atomic 發(fā)現(xiàn)在 objc-abi.h 文件中的 
objc_setProperty(id _Nullable self, SEL _Nonnull _cmd, ptrdiff_t offset,
                 id _Nullable newValue, BOOL atomic, signed char shouldCopy)
方法中虑凛。通過調(diào)用棧查看具體實(shí)現(xiàn):
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{
    bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
    bool mutableCopy = (shouldCopy == MUTABLE_COPY);
    reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) {
 if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }
} //省略部分代碼
由 reallySetProperty 方法可知碑宴,如果是atomic 則會在 set 前生成 PropertyLocks 鎖。
set 值之后 解鎖
對應(yīng)的 getter 方法中 的實(shí)現(xiàn) 

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
    if (offset == 0) {
        return object_getClass(self);
    }

    // Retain release world
    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();
    
//再通過源碼查看
StripedMap<spinlock_t> PropertyLocks;
slotlock是PropertyLocks通過 slot 從StripedMap 獲取桑谍。
在查看 slotlock定義
using spinlock_t = mutex_tt<LOCKDEBUG>;
class mutex_tt : nocopy_t {
    os_unfair_lock mLock;
}
可見 底層是通過  os_unfair_lock 實(shí)現(xiàn)延柠。
    // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
    return objc_autoreleaseReturnValue(value);
}

幾種主要鎖的類別

互斥鎖 sleep
是一種 low-level 的鎖,相對于自旋鎖來說比較低級锣披。如果發(fā)現(xiàn)沒有持有鎖贞间,則使線程進(jìn)入sleep 狀態(tài)。
自旋鎖 busy-wait
相當(dāng)于是一個(gè)外部死循環(huán)盈罐。當(dāng)其他線程訪問被鎖的資源后榜跌,會一直進(jìn)行循環(huán),進(jìn)入 busy-wait的狀態(tài)盅粪,自旋鎖不會引起調(diào)用者休眠,節(jié)省了線程休眠的狀態(tài)切換悄蕾,所以有更高的效率票顾。
直到其他線程鎖放開,因?yàn)榫€程一直在進(jìn)行執(zhí)行帆调,所以會一直占用cpu資源奠骄。
遞歸鎖
可以讓當(dāng)前線程遞歸的去給當(dāng)前線程加鎖,然后解鎖番刊。
比如: 
    //動態(tài)初始化
    pthread_mutex_t mutex;
    //初始化屬性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&mutex, &attr);
- (void)pthread_mutex_recursive {
    //加鎖
    pthread_mutex_lock(&mutex);
    for (int i=0; i<5; i++) {
//遞歸調(diào)用
        [self pthread_mutex_recursive];
    }
    pthread_mutex_unlock(&mutex);
}
注意點(diǎn)
使用任何鎖都需要消耗系統(tǒng)資源(內(nèi)存資源和CPU時(shí)間)含鳞,這種資源消耗可以分為兩類:
 1.建立鎖所需要的資源 
 2.當(dāng)線程被阻塞時(shí)所需要的資源 

同步方案的性能排序(待考證) 從高到低

  • os_unfair_lock
  • OSSpinLock (Deprecated)
  • dispatch_semaphore
  • pthread_mutex_t
  • dispatch_queue_create("串行隊(duì)列", DISPATCH_QUEUE_SERIAL); (本文未做詳細(xì)介紹)
  • NSLock
  • NSCondition
  • pthread_mutex( recursive)
  • NSRecursiveLock
  • NSConditionLock
  • @synchronized

為什么 @synchronized 執(zhí)行效率比 NSLock效率低?

鎖的粒度: @synchronized 的鎖粒度更粗芹务,它鎖定的是一個(gè)對象蝉绷,而不是一個(gè)具體的代碼塊鸭廷。這意味著如果多個(gè)臨界區(qū)使用了相同的對象作為鎖,那么它們將會互斥熔吗,即使它們沒有直接的競爭條件辆床。相比之下,NSLock 允許更細(xì)粒度的控制桅狠,可以只鎖定需要保護(hù)的臨界區(qū)讼载,從而減少了互斥的范圍,提高了并發(fā)性能中跌。

互斥鎖的開銷: @synchronized 在內(nèi)部使用了互斥鎖來實(shí)現(xiàn)線程同步咨堤,而 NSLock 也使用了類似的機(jī)制。然而漩符,由于 @synchronized 是一個(gè)語言級別的特性一喘,其實(shí)現(xiàn)可能比 NSLock 更復(fù)雜,并且可能會引入一些額外的開銷陨仅,如鎖的創(chuàng)建和釋放等津滞。

底層實(shí)現(xiàn)差異: @synchronized 的底層實(shí)現(xiàn)可能會依賴于運(yùn)行時(shí)系統(tǒng)的特性,而 NSLock 則是直接調(diào)用系統(tǒng)提供的互斥鎖機(jī)制灼伤。因此触徐,NSLock 的實(shí)現(xiàn)可能更接近操作系統(tǒng)的底層機(jī)制,性能上可能會更高效一些狐赡。


關(guān)于 os_unfair_lock 是互斥鎖的考證

os_unfair_lock的使用.png

thread_9中對資源加鎖撞鹉,在thread_10中對os_unfair_lock_lock()的實(shí)現(xiàn)進(jìn)行disassembly 查看。


image.png

下面是調(diào)用棧颖侄,省略了其他步驟

->  0x10d7628c4 <+20>:  movq   0x43bd(%rip), %rdi ; OSUnfairLock._unfair_lock
->  0x10d762fb6 <+0>: jmpq   *0x217c(%rip); os_unfair_lock_lock
->  0x1128d334b <+19>: jmp    0x1128d3350; _os_unfair_lock_lock_slow
->  0x1128d33cd <+125>: callq  0x1128d3ae6 ; _os_ulock_wait
->  0x1128d3afa <+20>:  callq  0x1128d5318 ; symbol stub for: __ulock_wait
->  0x1128d5318 <+0>: jmpq   *0x1d5a(%rip);  __ulock_wait
->  0x1128ae31c <+8>:  syscall 

當(dāng)調(diào)用玩 syscall的時(shí)候線程進(jìn)入休眠而不是進(jìn)行自旋鸟雏。所以 os_unfair_lock是互斥鎖
#0  0x00000001128ae31e in __ulock_wait ()

用到的資源

寫在最后: 關(guān)于技術(shù)的運(yùn)用,總結(jié)一句話:知識決定你的下限览祖,但是想象力決定你的上限孝鹊。熟練的運(yùn)用在項(xiàng)目中才是我們最需要的。

可以關(guān)注 我的掘金 也可以 關(guān)注 我的簡書

如果本文幫助了你展蒂,也可以贊助我一哈又活,O(∩_∩)O哈哈~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市锰悼,隨后出現(xiàn)的幾起案子柳骄,更是在濱河造成了極大的恐慌,老刑警劉巖箕般,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件耐薯,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)曲初,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門体谒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人复斥,你說我怎么就攤上這事营密。” “怎么了目锭?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵评汰,是天一觀的道長。 經(jīng)常有香客問我痢虹,道長被去,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任奖唯,我火速辦了婚禮惨缆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘丰捷。我一直安慰自己坯墨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布病往。 她就那樣靜靜地躺著裆馒,像睡著了一般猜丹。 火紅的嫁衣襯著肌膚如雪嫉柴。 梳的紋絲不亂的頭發(fā)上恕酸,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機(jī)與錄音畔勤,去河邊找鬼蕾各。 笑死,一個(gè)胖子當(dāng)著我的面吹牛庆揪,可吹牛的內(nèi)容都是我干的式曲。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼缸榛,長吁一口氣:“原來是場噩夢啊……” “哼检访!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起仔掸,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎医清,沒想到半個(gè)月后起暮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年负懦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了筒捺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡纸厉,死狀恐怖系吭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情颗品,我是刑警寧澤肯尺,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站躯枢,受9級特大地震影響则吟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜锄蹂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一氓仲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧得糜,春花似錦敬扛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至槽棍,卻和暖如春捉蚤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背炼七。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工缆巧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人豌拙。 一個(gè)月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓陕悬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親按傅。 傳聞我的和親對象是個(gè)殘疾皇子捉超,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

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

  • 鎖是一種同步機(jī)制,用于多線程環(huán)境中對資源訪問的限制iOS中常見鎖的性能對比圖(摘自:ibireme): iOS鎖的...
    LiLS閱讀 1,510評論 0 6
  • 前言 iOS開發(fā)中由于各種第三方庫的高度封裝唯绍,對鎖的使用很少拼岳,剛好之前面試中被問到的關(guān)于并發(fā)編程鎖的問題,都是一知...
    喵渣渣閱讀 3,690評論 0 33
  • 引用自多線程編程指南應(yīng)用程序里面多個(gè)線程的存在引發(fā)了多個(gè)執(zhí)行線程安全訪問資源的潛在問題况芒。兩個(gè)線程同時(shí)修改同一資源有...
    Mitchell閱讀 1,984評論 1 7
  • demo下載 建議一邊看文章惜纸,一邊看代碼。 聲明:關(guān)于性能的分析是基于我的測試代碼來的,我也看到和網(wǎng)上很多測試結(jié)果...
    炸街程序猿閱讀 789評論 0 2
  • 我見過牽牛花爬上窗臺 白天粪牲,黑夜 喜悅古瓤,歡快 靜靜地等 慢慢地開 我知道,最終它會 蜂蝶自來 我見過臘梅孤立郊外 ...
    馬海燕閱讀 568評論 1 2