iOS 線程鎖

概念

  1. 自旋鎖
    1.1 OSSpinLock
    1.2 os_unfair_lock
    1.3 atomic
  2. 互斥鎖
    2.1 pthread_mutex_t
    2.2 NSLock
    2.3 NSRecursiveLock
    2.4 @synchronized
    2.5 dispatch_semaphore_t
    2.6 NSCondition
    2.7 NSConditionLock
  3. 讀寫鎖

性能對比
參考


概念

什么是鎖
  • 鎖是一種同步的機制探越。
  • 鎖是一個對象。
  • 鎖是為了保證某一個資源在同一時間搀擂,不被多個“潛在調(diào)用者”持有威恼,保證資源在同一時間不會因搶奪而出現(xiàn)錯誤。
  • 鎖是對資源的訪問限制。


死鎖

當兩個及兩個以上的運算單元在等待對方停止運行哎媚,從而獲取對方持有的系統(tǒng)資源,但又沒有一方提前退出爭奪的時候,就會產(chǎn)生死鎖。


死鎖

如果線程1和線程2,誰都不先釋放自己對已擁有的鎖對象的持有權(quán),那么就會陷入互相等待對方先松手的狀態(tài),這就是死鎖。

死鎖產(chǎn)生的必要條件
  1. 互斥條件 :
    資源只能在同一時間分配給某一個運算單元涮较,如果其他的運算單元也要請求同一資源,則只能等待持有資源的運算單元使用完畢。
  2. 持有和等待 :
    某運算單元已經(jīng)持有一個或多個資源国瓮,又請求了其他被占用的資源跟衅,這個運算單元并不釋放自己已有的資源叭莫,持有資源進行新資源的等待廓潜。
  3. 不可剝奪條件 :
    指運算單元已經(jīng)獲得了資源,在沒有使用完成該資源的情況下,該資源不可以被剝奪,只能等待運算單元使用完畢后自己釋放。
  4. 循環(huán)等待 :
    指一組集合中有很多運算單元佛致,它們互相持有其他運算單元的資源罐脊。

1. 自旋鎖

線程反復(fù)檢查鎖變量是否可用层玲,在此過程中線程一直處于執(zhí)行狀態(tài)润绵。適用于預(yù)期持有鎖時間很短的操作卿捎,此時因為阻塞線程和喚醒涉及上下文切換和線程數(shù)據(jù)結(jié)構(gòu)的更新,在cpu資源寬裕且鎖預(yù)期等待時間很短的情況下氛魁,輪詢通常比阻塞線程更有效宙项。

1.1 OSSpinLock(iOS 10 以后廢棄)

OSSpinLock spinLock = OS_SPINKLOCK_INIT;
OSSpinLockLock(&spinLock);
//code
OSSpinLockUnlock(&spinLock);

OSSSpinLock 存在優(yōu)先級反轉(zhuǎn)問題盆繁。如果一個低優(yōu)先級的線程 A 獲得鎖并訪問共享資源拦惋,這時如果另一個高優(yōu)先級的線程 B 也嘗試獲得這個鎖,線程 B 會處于忙等狀態(tài),由于線程 B 是一個高優(yōu)先級線程,因此 CPU 會盡量將執(zhí)行的資源分配給線程B,從而導(dǎo)致線程 B 占用大量 CPU 而線程 A 由于得到的執(zhí)行資源少而遲遲無法解鎖辅辩。

1.2 os_unfair_lock

//頭文件
@import Darwin.os.lock;

os_unfair_lock_t lock = &(OS_UNFAIR_LOCK_INIT);
os_unfair_lock_lock(lock);
// code
os_unfair_lock_unlock(lock);

多個線程同時等待鎖時,先請求獲取鎖的線程不一定會先獲取鎖,鎖的獲取與請求鎖的先后順序無關(guān)赔桌,例如最后請求獲取鎖的線程可能先獲得鎖。
鎖的獲取和釋放基于原子操作。
只包含一個指針大小的內(nèi)存空間幽勒,性能開銷小击吱。

1.3 atomic

@protocol(atomic, assign) NSInteger count;

編譯器自動生成 getter / setter 內(nèi)部會調(diào)用 objc_getProperty / reallySetProperty 方法鞋仍,方法內(nèi)部根據(jù)是否為原子屬性執(zhí)行不同的代碼

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();
    
    // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
    return objc_autoreleaseReturnValue(value);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}

原子性修飾的屬性會進行 spinlock 加鎖處理,spinlock由于優(yōu)先級反轉(zhuǎn)問題已經(jīng)被 os_unfair_lock 代替

using spinlock_t = mutex_tt<LOCKDEBUG>;
class mutex_tt : nocopy_t {
    os_unfair_lock mLock;
    ...
}

對于原子性修飾的屬性猫妙,只能保證 getter / setter 的線程安全,無法保證屬性在使用過程中的線程安全。例如可變數(shù)組在多個線程中 removeObjectAtIndex:


2. 互斥鎖

是一種用于多線程編程中,防止多條線程同時對同一公共資源(比如全局變量)進行讀寫的機制杖剪。它通過將代碼切片成一個一個的臨界區(qū)域達成。臨界區(qū)域指的是一塊對公共資源進行訪問的代碼,并非一種機制或是算法怠蹂。一個程序、進程、線程可以擁有多個臨界區(qū)域,但是并不一定會應(yīng)用互斥鎖妒峦。
互斥鎖不會出現(xiàn)忙碌等待,僅僅是線程阻塞坦冠。

2.1 pthread_mutex_t

// 導(dǎo)入頭文件
#import <pthread/pthread.h>

pthread_mutex_t lock;
pthread_mutexattr_t attr;
// 初始化屬性
pthread_mutexattr_init(&attr);
/*
 * 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
 */
// 設(shè)置類型為遞歸
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化鎖灭抑,如果不需要設(shè)置屬性則第二個參數(shù)傳入NULL救湖,使用默認屬性
pthread_mutex_init(&lock, &attr);
// 銷毀屬性
pthread_mutexattr_destroy(&attr);
// 加鎖
pthread_mutex_lock(&lock);
// code
// 解鎖
pthread_mutex_unlock(&lock);

// 使用完成后需要在合適的時機對鎖進行銷毀
pthread_mutex_destroy(&lock);

pthread_mutex 是 iOS 中抵乓,多種類型的鎖的底層實現(xiàn)铡原,例如 NSLock、NSRecursiveLock晚岭、@ synchronized 字管。
pthread_mutex底層有實現(xiàn)一個阻塞隊列丁逝,如果當前有其他任務(wù)正在執(zhí)行回挽,則加入到隊列中,放棄當前cpu時間片辜膝。一旦其他任務(wù)執(zhí)行完崔梗,則從隊列中取出等待執(zhí)行的線程對象,恢復(fù)上下文重新執(zhí)行质和。

2.2 NSLock
NSLock * lock = [[NSLock alloc] init];
[lock lock];
// code
[lock unlock];

NSLock 是對 pthread_mutex 的封裝魂仍,屬性為 PTHREAD_MUTEX_ERRORCHECK赊舶,它會自動檢測死鎖,損失一定性能換來錯誤提示鸠珠。
注意:在同一個線程中多次對同一個對象加鎖會導(dǎo)致死鎖可缚。

2.3 NSRecursiveLock
NSRecursiveLock * lock = [[NSRecursiveLock alloc] init];
[lock lock];
// code 
[lock unlock];

NSRecursiveLock 是對 pthread_mutex 的封裝,屬性為 PTHREAD_MUTEX_RECURSIVE
遞歸鎖允許在同一線程內(nèi)對同一個鎖對象多次加鎖允跑,但是需要注意的是在線程執(zhí)行完畢后必須在當前線程內(nèi)進行同樣次數(shù)的解鎖操作王凑,否則會導(dǎo)致其他線程無法獲得鎖(死鎖)。

- (void)NSRecursiveLockTest:(NSRecursiveLock *)lock some:(NSInteger)i {
    [lock lock];
    NSLog(@"%zd", i);
    if (i != 0) {
        [self NSRecursiveLockTest:lock some:--i];
    } else {
        // i == 0 時直接結(jié)束遞歸聋丝,不進行解鎖
        return;
    }
    [lock unlock];
}

NSRecursiveLock * lock = [[NSRecursiveLock alloc] init];
// 由于首先執(zhí)行的線程沒有解鎖索烹,導(dǎo)致后面執(zhí)行的線程一直在等待解鎖
dispatch_async(self.queue, ^{
     [self NSRecursiveLockTest:lock some:3];
});
dispatch_async(self.queue, ^{
     [self NSRecursiveLockTest:lock some:4];
});
2.4 @synchronized
@synchronized (obj) {
// code
}

@synchronized是對pthread_mutex遞歸鎖的封裝, @synchronized(obj)內(nèi)部會生成obj對應(yīng)的遞歸鎖弱睦,然后進行加鎖百姓、解鎖操作。如果 obj 為 nil 則不會進行加鎖况木,不能保證線程安全垒拢。

2.5 dispatch_semaphore_t
// 初始化 
dispatch_semaphore_t semaphore_t = dispatch_semaphore_create(0);
// 如果信號計數(shù) <= 0 阻塞當前線程旬迹,否則信號計數(shù) - 1 不阻塞當前線程
dispatch_semaphore_wait(semaphore_t,DISPATCH_TIME_FOREVER);
// 信號計數(shù) + 1
dispatch_semaphore_signal(semaphore_t);

GCD信號量是通過對信號量計數(shù)和0的對比來進行鎖的實現(xiàn)。

  • 當信號量的信號計數(shù) > 0求类,使信號計數(shù) -1奔垦,并不造成線程阻塞。
  • 當信號量的信號計數(shù) <= 0尸疆,其所在的線程會被阻塞執(zhí)行椿猎,直到信號計數(shù) > 0 為止。

信號量除了可以作為鎖使用還可以用于將異步操作轉(zhuǎn)為同步操作寿弱。

- (void)fetchDataComplete:(void(^)(id data, BOOL isSuccess))complete {
    // 模擬網(wǎng)絡(luò)請求
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), self.queue, ^{
        NSArray * data = @[@"data"];
        complete(data, YES);
    });
}

- (id)syncFetchData {
    // 1.創(chuàng)建信號量鸵贬,計數(shù)為0
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    __block id data = nil;
    [self fetchDataComplete:^(id d, BOOL isSuccess) {
        if (isSuccess) {
            data = d;
        }
        // 3.數(shù)據(jù)請求完成,發(fā)送 signal 信號計數(shù) + 1 繼續(xù)執(zhí)行 2
        dispatch_semaphore_signal(semaphore);
    }];
    // 2.計數(shù) <= 0 阻塞當前線程脖捻,等待異步請求回調(diào)
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    return data;
}
2.6 NSCondition
NSCondition * condition = [[NSCondition alloc] init];
// 加鎖
[condition lock];
// 解鎖
[condition unlock];

NSLock 基于 mutex 與 POSIX condition 實現(xiàn)阔逼,除了基礎(chǔ)的 lock / unlock 還支持類似于 信號量 功能:

  • wait 釋放互斥量,線程進入休眠狀態(tài)地沮。
  • waitUntilDate: 釋放互斥量嗜浮,當前線程立即進入休眠,其他線程繼續(xù)執(zhí)行任務(wù)摩疑,直到limit時間點危融,當前線程再被喚醒。
  • signal 喚醒一個等待的線程
  • broadcast 喚醒所有等待的線程

以上方法必須在 NSCondition 對象 lock 之后調(diào)用雷袋,例如下面的例子:

// 初始有兩張票
static NSInteger ticket = 2;
static NSCondition * condition;
- (void)NSConditionTest {
    condition = [[NSCondition alloc] init];
    for (int i = 0; i < 5; i++) {
        dispatch_async(self.queue, ^{
            [self waitTicket];
        });
    }
    for (int i = 0; i < 3; i++) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(i * 3 * NSEC_PER_SEC)), self.queue, ^{
            [self signalTicket];
        });
    }
}

- (void)waitTicket {
    // 加鎖
    [condition lock];
    while (!ticket) {
        NSLog(@"沒有票吉殃,線程休眠");
        // 釋放互斥量,線程休眠
        [condition wait];
        // 線程喚醒后繼續(xù)while循環(huán)檢查票數(shù)楷怒,如果沒有票說明票已經(jīng)被之前喚醒的線程售出蛋勺,當前線程再次休眠
        NSLog(@"線程喚醒,檢查票數(shù)");
    }
    ticket--;
    NSLog(@"賣出了一張票 剩余:%zd", ticket);
    // 解鎖
    [condition unlock];
}

- (void)signalTicket {
    // 加鎖
    [condition lock];
    ticket++;
    NSLog(@"發(fā)行了一張票 剩余:%zd", ticket);
    // 發(fā)送信號,通知喚醒一條休眠的線程
    [condition signal];
    // 解鎖
    [condition unlock];
}

賣出了一張票 剩余:1
賣出了一張票 剩余:0
沒有票鸠删,線程休眠
沒有票抱完,線程休眠
沒有票,線程休眠
發(fā)行了一張票 剩余:1
線程喚醒刃泡,檢查票數(shù)
賣出了一張票 剩余:0
發(fā)行了一張票 剩余:1
線程喚醒巧娱,檢查票數(shù)
賣出了一張票 剩余:0
發(fā)行了一張票 剩余:1
線程喚醒,檢查票數(shù)
賣出了一張票 剩余:0

如果將以上例子用 mutex 與 POSIX condition 實現(xiàn)如下:

static pthread_mutex_t mutex;
static pthread_cond_t cond;
- (void)mutexAndCondTest {
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);
    for (int i = 0; i < 5; i++) {
        dispatch_async(self.queue, ^{
            [self cond_waitTicket];
        });
    }
    for (int i = 0; i < 3; i++) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(i * 3 * NSEC_PER_SEC)), self.queue, ^{
            [self cond_signalTicket];
        });
    }
}

- (void)cond_waitTicket {
    pthread_mutex_lock(&mutex);
    while (!ticket) {
        NSLog(@"沒有票烘贴,線程休眠");
        pthread_cond_wait(&cond, &mutex);
        NSLog(@"線程喚醒禁添,檢查票數(shù)");
    }
    ticket--;
    NSLog(@"賣出了一張票 剩余:%zd", ticket);
    pthread_mutex_unlock(&mutex);
}

- (void)cond_signalTicket {
    pthread_mutex_lock(&mutex);
    ticket++;
    NSLog(@"發(fā)行了一張票 剩余:%zd", ticket);
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);
}
2.7 NSConditionLock
// 初始化并設(shè)置條件變量
NSConditionLock * conditionLock = [[NSConditionLock alloc] initWithCondition:cond];
// 當條件變量相等且可以獲取到鎖時加鎖,否則阻塞
[conditionLock lockWhenCondition:cond];
// 只要能夠獲取到鎖就加鎖桨踪,否則阻塞
// [conditionLock lock];
// code
// 解鎖并重新設(shè)置條件變量
[conditionLock unlockWithCondition:cond];
// 解鎖 不會改變當前條件變量
// [conditionLock unlock];

NSConditionLock 是對 NSCondition 的封裝老翘,在加鎖前會比較等待變量和條件變量是否相等,如果不相等則阻塞線程

    // 初始化條件變量
    NSConditionLock * lock = [[NSConditionLock alloc] initWithCondition:2];
    dispatch_async(self.queue, ^{
        // 條件變量為 1 且能夠獲取到鎖時加鎖,否則阻塞
        [lock lockWhenCondition:1];
        NSLog(@"1");
        // 解鎖并將條件變量設(shè)置為 2
        [lock unlockWithCondition:2];
    });
    dispatch_async(self.queue, ^{
        // 條件變量為 3 且能夠獲取到鎖時時加鎖酪捡,否則阻塞
        [lock lockWhenCondition:3];
        NSLog(@"2");
        // 解鎖并將條件變量設(shè)置為 1
        [lock unlockWithCondition:1];
    });
    dispatch_async(self.queue, ^{
        // 無論條件變量為多少只要獲取到鎖就進行加鎖,如果獲取不到鎖則阻塞線程
        [lock lock];
        NSLog(@"4");
        // 解鎖且不改變條件變量
        [lock unlock];
    });
    dispatch_async(self.queue, ^{
        // 條件變量為 2 且能夠獲取到鎖時時加鎖纳账,否則阻塞
        [lock lockWhenCondition:2];
        NSLog(@"3");
        // 解鎖并將條件變量設(shè)置為 3
        [lock unlockWithCondition:3];
    });

執(zhí)行順序: 4 -> 3 -> 2 -> 1

3. 讀寫鎖

讀寫鎖實際是一種特殊的自旋鎖逛薇,它把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問疏虫,寫者則需要對共享資源進行寫操作永罚。這種鎖相對于自旋鎖而言,能提高并發(fā)性卧秘,因為在多處理器系統(tǒng)中呢袱,它允許同時有多個讀者來訪問共享資源,最大可能的讀者數(shù)為實際的CPU數(shù)
寫者是排他性的翅敌,?個讀寫鎖同時只能有?個寫者或多個讀者(與CPU數(shù)相關(guān))羞福,但不能同時既有讀者?有寫者。在讀寫鎖保持期間也是搶占失效的
如果讀寫鎖當前沒有讀者蚯涮,也沒有寫者治专,那么寫者可以?刻獲得讀寫鎖,否則它必須?旋在那?遭顶,直到?jīng)]有任何寫者或讀者张峰。如果讀寫鎖沒有寫者,那么讀者可以?即獲得該讀寫鎖棒旗,否則讀者必須?旋在那?喘批,直到寫者釋放該讀寫鎖。

pthread_rwlock_t

// 導(dǎo)入頭文件
#import <pthread/pthread.h>

pthread_rwlock_t lock;
// 初始化讀寫鎖
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);

讀寫鎖的三種狀態(tài) :

  • 以讀的方式占據(jù)鎖的狀態(tài) :
    如果有其他的線程以讀的方式請求占據(jù)鎖铣揉,并讀取鎖內(nèi)的共享資源饶深,不會造成線程阻塞,允許其他線程進行讀取逛拱,就像遞歸鎖的可重入一樣粥喜。
    如果有其他的線程以寫的方式請求占據(jù)鎖,企圖更改鎖內(nèi)的共享資源橘券,則會阻塞請求的線程额湘,直到讀的操作進行完畢。
    如果有其他多條線程旁舰,分別以讀和寫的不同方式請求占據(jù)鎖锋华,那么這些多條線程也會被阻塞,并且在當前線程讀操作結(jié)束后箭窜,先讓寫方式的線程占據(jù)鎖毯焕,避免讀模式的鎖長期占用資源,而寫模式的鎖卻長期堵塞。
  • 以寫的方式占據(jù)鎖的狀態(tài) : 所有其他請求占據(jù)鎖的線程都會阻塞纳猫。
  • 沒有線程占據(jù)鎖的狀態(tài) : 按照操作系統(tǒng)的調(diào)度順序婆咸,依次調(diào)用,調(diào)度后要符合上述兩種情況芜辕。

性能對比

性能

參考

Threading Programming Guide
【iOS】—— iOS中的相關(guān)鎖
iOS線程安全——鎖
iOS 多線程下的不同鎖
iOS常用的幾種鎖詳解以及用法
第三十二節(jié)—iOS的鎖(一)
iOS GCD (四) dispatch_semaphore 信號量

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末尚骄,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子侵续,更是在濱河造成了極大的恐慌倔丈,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件状蜗,死亡現(xiàn)場離奇詭異需五,居然都是意外死亡,警方通過查閱死者的電腦和手機轧坎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門宏邮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人缸血,你說我怎么就攤上這事蜀铲。” “怎么了属百?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵记劝,是天一觀的道長。 經(jīng)常有香客問我族扰,道長厌丑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任渔呵,我火速辦了婚禮怒竿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘扩氢。我一直安慰自己耕驰,他們只是感情好,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布录豺。 她就那樣靜靜地躺著朦肘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪双饥。 梳的紋絲不亂的頭發(fā)上媒抠,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音咏花,去河邊找鬼趴生。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的苍匆。 我是一名探鬼主播刘急,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼浸踩!你這毒婦竟也來了叔汁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤民轴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后球订,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體后裸,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年冒滩,在試婚紗的時候發(fā)現(xiàn)自己被綠了微驶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡开睡,死狀恐怖因苹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情篇恒,我是刑警寧澤扶檐,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站胁艰,受9級特大地震影響款筑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜腾么,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一奈梳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧解虱,春花似錦攘须、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至悍汛,卻和暖如春限煞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背员凝。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工署驻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓旺上,卻偏偏與公主長得像瓶蚂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子宣吱,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

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

  • 1. 為什么多線程需要鎖窃这? 首先在多線程處理的時候我們經(jīng)常會需要保證同步,這是為啥呢征候,看一下下面這個例子: 這種時...
    木小易Ying閱讀 1,046評論 0 8
  • 前言 最開始我想把線程和線程鎖放在一起整理出一篇文章杭攻,結(jié)果整理了線程發(fā)現(xiàn)有點長,于是便把線程鎖單獨拿出來了疤坝。感興趣...
    陌路賣醬油閱讀 994評論 0 21
  • 一兆解、線程分享梗概 二、線程的概念和實現(xiàn) 線程:是程序執(zhí)行流的最小單元跑揉。一個標準的線程由線程ID锅睛,當前指令集合,寄存...
    魁拔2015閱讀 5,658評論 0 22
  • 一历谍、線程鎖相關(guān)概念 線程鎖:我們在使用多線程的時候多個線程可能會訪問同一塊資源现拒,這樣就很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全...
    2525252472閱讀 405評論 0 2
  • 多線程系列篇章計劃內(nèi)容:iOS多線程編程(一) 多線程基礎(chǔ)[https://juejin.im/post/6890...
    賣饃工程師閱讀 545評論 0 3