013-iOS鎖機制

鎖的類別

  • NSLocking
    • NSLock
    • NSConditionLock 條件鎖
    • NSRecursiveLock 遞歸鎖
    • NSCondition
    • NSDistributedLock 分布鎖
  • @sychronized
  • dispatch_semaphore
  • OSSpinLock 自旋鎖
  • pthread_mutex

首先我們要實現一種簡單的線程訪問導致的數據混亂的情況

- (void)threadTestLearn
{
    __block NSInteger flag = 0;
    
    void (^positive)() = ^{
        while (1) {
            [NSThread sleepForTimeInterval:1];
            flag = 1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"1 == %ld", flag);
        }
    };
    
    void (^negative)() = ^{
        while (1) {
            [NSThread sleepForTimeInterval:1];
            flag = -1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"-1 == %ld", flag);
        }
    };
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
}

這個方法的打印結果,預期應該是

1 == 1
-1 == -1
1 == 1

而事實上,由于沒有互斥訪問機制,打印結果出現了

1 == -1
-1 == 1

NSLocking

諸如 NSLock、NSConditionLock 等鎖都繼承了 NSLocking 協議,NSLocking 協議簡單粗暴地定義了兩個方法

@protocol NSLocking

- (void)lock;
- (void)unlock;

@end

一個是加鎖,一個是解鎖。

NSLock

同一個 NSLock 對象顯式調用 lock 函數后,除非自己顯示調用 unlock 函數,否則其他線程均不能繼續對此 NSLock 對象加鎖,從而達到互斥訪問的目的。 所以使用也很簡單

- (void)threadTestLearn
{
    __block NSInteger flag = 0;
    NSLock *lock = [NSLock new];
    
    void (^positive)() = ^{
        while (1) {
            [lock lock];
            
            [NSThread sleepForTimeInterval:1];
            flag = 1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"1 == %ld", flag);
            
            [lock unlock];
        }
    };
    
    void (^negative)() = ^{
        while (1) {
            [lock lock];
            
            [NSThread sleepForTimeInterval:1];
            flag = -1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"-1 == %ld", flag);
            
            [lock unlock];
        }
    };
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
}

如果希望獲取鎖時不阻塞線程,則考慮使用 tryLock 方法,它會在獲取鎖失敗后返回 NO,可以繼續執行后面的代碼。

lockBeforeDate 方法在指定的時間以前得到鎖。YES: 在指定時間之前獲得了鎖;NO: 在指定時間之前沒有獲得鎖。該線程將被阻塞,直到獲得了鎖,或者指定時間過期。

NSLockConditionLock

NSConditionLock 是一種條件鎖,除了基本的lock與unlock函數,還提供了 lockWithCondition 以及 unlockWithCondition 方法,適合多個線程的工作需要按照順序執行的情景。其中 unLockWithCondition 會把 condition 變量修改為指定參數后解鎖。

- (void)threadTestLearn
{
    __block NSInteger flag = 0;
    __block NSInteger mutext = 1;
    
    NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:mutext];
    
    void (^positive)() = ^{
        while (1) {
            [lock lockWhenCondition:1];
            
            [NSThread sleepForTimeInterval:1];
            flag = 1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"1 == %ld", flag);
            
            [lock unlockWithCondition:-1];
        }
    };
    
    void (^negative)() = ^{
        while (1) {
            [lock lockWhenCondition:-1];
            
            [NSThread sleepForTimeInterval:1];
            flag = -1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"-1 == %ld", flag);
            
            [lock unlockWithCondition:1];
        }
    };
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
}

這里通過 mutex 變量嚴格保證了 positive 和 negative 方法會依次調用,并且第一個調用的方法一定是 positive 方法,同時如果將 negative 中的語句改成

            [lock unlockWithCondition:0];

則兩個方法都只會被調用一次就停止了,因為都獲得不了正確的 condition 所以無法獲得鎖。

NSRecursiveLock

NSRecursiveLock 是遞歸鎖,用于解決遞歸調用中的死鎖問題。當一個方法加鎖以后遞歸調用自己,會再次進行加鎖,由于鎖沒有被釋放所以線程會被阻塞掉,線程阻塞后將永遠不能解鎖,因此發生死鎖。

NSRecursiveLock 實際上定義的是一個遞歸鎖,主要是用在循環或遞歸操作中。它可以允許同一線程多次加鎖,而不會造成死鎖。遞歸鎖會跟蹤它被 lock 的次數。每次成功的 lock 都必須平衡調用 unlock 操作。只有所有達到這種平衡,鎖最后才能被釋放,以供其它線程使用。

    NSRecursiveLock *recursiveLock = [NSRecursiveLock new];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        static void (^test)(int);
        test = ^(int value)
        {
            [recursiveLock lock];
            if (value > 0)
            {
                [NSThread sleepForTimeInterval:0.5];
                NSLog(@"%d", value);
                test(--value);
            }
            else
            {
                [recursiveLock unlock];
                return;
            }
            NSLog(@"%d", value);
            [recursiveLock unlock];
        };
        __block int value = 5;
        test(value);
    });

NSCondition

NSCondition 與 NSConditionLock 是兩回事,它們之間最大的區別在于 NSCondition 支持在獲得鎖之后阻塞線程并放棄鎖,然后等待被喚醒后可以重新獲得鎖。
這里被喚醒有兩種方式,signal 和 broadcast,前者只會喚醒一個 wait 的線程,而后者會喚醒所有 wait 的線程。

但是有一個問題:在多個線程 wait 的場景下,一個線程調用了 broadcast 后如何喚醒多個 wait 的線程,能否保證線程安全,以及這個線程在未 unlock 前執行操作如何保證線程安全。

- (void)threadTestLearn
{
    __block NSInteger flag = 0;
    __block NSInteger mutex = 1;
    
    NSCondition *condition = [NSCondition new];
    
    void (^positive)() = ^{
        while (1) {
            [condition lock];
            NSLog(@"thread1");
            while (mutex == 0)
            {
                [condition wait];
            }
            [NSThread sleepForTimeInterval:1];
            flag = 1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"1 == %ld", flag);
            
            mutex = 0;
            [condition signal];
            [condition unlock];
        }
    };
    
    void (^negative)() = ^{
        while (1) {
            [condition lock];
            NSLog(@"thread-1");
            if (mutex == 0)
            {
                [condition wait];
            }
            
            [NSThread sleepForTimeInterval:1];
            flag = -1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"-1 == %ld", flag);
            
            mutex = 0;
            [condition signal];
            [condition unlock];
        }
    };
    
    void (^zero)() = ^{
        while (1) {
            [condition lock];
            
            if (mutex != 0)
            {
                [condition wait];
            }
            NSLog(@"mutex 0");
            [NSThread sleepForTimeInterval:1];
            
            mutex = 1;
            [condition broadcast];
            [condition unlock];
        }
    };
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), zero);
}

@synchronized

@synchronized 可以保證唯一標識下的資源互斥訪問,不需要顯式創建鎖對象,使用方便,但是表現性能不佳。

    __block NSInteger flag = 0;
    
    void (^positive)() = ^{
        while (1) {
            @synchronized (@(flag)) {
                [NSThread sleepForTimeInterval:1];
                flag = 1;
                [NSThread sleepForTimeInterval:0.5];
                NSLog(@"1 == %ld", flag);
                
            }
        }
    };
    
    void (^negative)() = ^{
        while (1) {
            @synchronized (@(flag)) {
                [NSThread sleepForTimeInterval:1];
                flag = -1;
                [NSThread sleepForTimeInterval:0.5];
                NSLog(@"-1 == %ld", flag);
            }
        }
    };
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);

dispatch_semaphore

dispatch_semaphore 是 GCD 中提供的信號量機制,通過信號量控制互斥訪問,提供了 wait 和 signal 方法,類似于信號量的 PV 操作。出于性能考慮建議開發中使用這個作為鎖。

  • wait 對信號量減一,如小于 0 則等待
  • signal 如果沒有等待接受信號的線程,則對信號量加一
    __block NSInteger flag = 0;
    
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    
    
    void (^positive)() = ^{
        while (1) {
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            [NSThread sleepForTimeInterval:1];
            flag = 1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"1 == %ld", flag);
            dispatch_semaphore_signal(semaphore);
        }
    };
    
    void (^negative)() = ^{
        while (1) {
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            [NSThread sleepForTimeInterval:1];
            flag = -1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"-1 == %ld", flag);
            dispatch_semaphore_signal(semaphore);
        }
    };
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);

OSSpinLock

自旋鎖,當無法獲得鎖時會一直空循環,占用 CPU,性能最高,適合執行輕量級工作,但是有隱藏的線程安全問題。

使用時需要引入包 #import <libkern/OSAtomic.h>。

    __block NSInteger flag = 0;
    __block OSSpinLock lock = OS_SPINLOCK_INIT;
    
    void (^positive)() = ^{
        while (1) {
            OSSpinLockLock(&lock);
            
            [NSThread sleepForTimeInterval:1];
            flag = 1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"1 == %ld", flag);
            
            OSSpinLockUnlock(&lock);
            [NSThread sleepForTimeInterval:1];
        }
    };
    
    void (^negative)() = ^{
        while (1) {
            OSSpinLockLock(&lock);
            
            [NSThread sleepForTimeInterval:1];
            flag = -1;
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"-1 == %ld", flag);
            
            OSSpinLockUnlock(&lock);
            [NSThread sleepForTimeInterval:1];
        }
    };
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), positive);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), negative);

pthread_mutex

這是 C 語言中定義的互斥鎖,對于未獲得鎖的線程不使用忙等,而是直接睡眠。

首先引入頭文件 #import <pthread.h>

然后初始化 lock 變量

    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
    __block pthread_mutex_t lock;
    pthread_mutex_init(&lock, &attr);
    pthread_mutexattr_destroy(&attr);

這里 attr 有幾種 type

PTHREAD_MUTEX_NORMAL 缺省類型,也就是普通鎖。當一個線程加鎖以后,其余請求鎖的線程將形成一個等待隊列,并在解鎖后先進先出原則獲得鎖。

PTHREAD_MUTEX_ERRORCHECK 檢錯鎖,如果同一個線程請求同一個鎖,則返回 EDEADLK,否則與普通鎖類型動作相同。這樣就保證當不允許多次加鎖時不會出現嵌套情況下的死鎖。

PTHREAD_MUTEX_RECURSIVE 遞歸鎖,允許同一個線程對同一個鎖成功獲得多次,并通過多次 unlock 解鎖。

PTHREAD_MUTEX_DEFAULT 適應鎖,動作最簡單的鎖類型,僅等待解鎖后重新競爭,沒有等待隊列。

加鎖解鎖過程類似其他鎖

pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);

pthread_mutex_destroy 為釋放鎖資源。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,253評論 2 375

推薦閱讀更多精彩內容