iOS中的鎖

前言

生活中的鎖隨處可見,鎖的作用也不言而喻,本文小結(jié)一下iOS的鎖。

技能表

  • atomic (醬油君)
  • @synchronized
  • NSLock
  • NSConditionLock
  • NSRecursiveLock
  • NSCondition
  • dispatch_semaphore
  • OSSpinLock
  • os_unfair_lock
  • POSIX LOCK
  • NSDistributedLock (醬油君)
atomic

說到鎖不得不提線程安全,說到線程安全,不得不提nonatomic與atomic的愛恨情仇。
我們經(jīng)??吹竭@樣的描述:“nonatomic為非原子性非線程安全,atomic為原子性線程安全,但是atomic真的線程安全嗎?”
然后就沒有然后了。。

先來扒一下nonatomic和atomic會干什么

nonatomic/atomic = getter + setter + ivar

nonatomic生成的getter、setter沒加鎖,atomic生成的getter、setter有鎖。所以當(dāng)通過setter/getter而非ivar賦值/取值被atomic修飾的屬性時,該屬性是讀寫安全的。
然而讀寫安全并不代表線程安全,那么什么是線程安全?

線程安全就是多線程訪問時,采用了加鎖機制,當(dāng)一個線程訪問該類的某個數(shù)據(jù)時,進行保護,其他線程不能進行訪問直到該線程讀取完,其他線程才可使用。不會出現(xiàn)數(shù)據(jù)不一致或者數(shù)據(jù)污染。 線程不安全就是不提供數(shù)據(jù)訪問保護,有可能出現(xiàn)多個線程先后更改數(shù)據(jù)造成所得到的數(shù)據(jù)是臟數(shù)據(jù) 【引自百科】

  • atomic非線程安全驗證
@interface ViewController ()

@property (strong) NSString *info;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    //A
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            self.info = @"a";
            NSLog(@"A--info:%@", self.info);
        }
    });
    
    //B
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            self.info = @"b";
            NSLog(@"B--info:%@", self.info);
        }
    });    

}

@end

根據(jù)線程安全定義,如果atomic為線程安全A輸出應(yīng)該永遠(yuǎn)為A--info:a,B輸出應(yīng)該永遠(yuǎn)為B--info:b

來看控制臺輸出

atomic

OK,atomic非線程安全驗證完畢,下面來說鎖。

@synchronized

@synchronized是iOS中最常見的鎖,用法很簡單

    //A
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            @synchronized (self) {
                _info = @"a";
                NSLog(@"A--info:%@", _info);
            }
        }
    });
    
    //B
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            @synchronized (self) {
                _info = @"b";
                NSLog(@"B--info:%@", _info);
            }
        }
    });

這樣就可以確保A中輸出均為A--info:a,B中輸出均為B--info:b

但是@synchronized()括號中只要寫相同數(shù)據(jù)就可以嗎?如果這個數(shù)據(jù)的地址在不斷變化呢?比如這樣:

   //A
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            @synchronized (_info) {
                _info = @"a";
                NSLog(@"A--info:%@", _info);
            }
        }
    });
    
    //B
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            @synchronized (_info) {
                _info = @"b";
                NSLog(@"B--info:%@", _info);
            }
        }
    });

再來看控制臺輸出:

@synchronized

可見@synchronized()括號中只能寫地址不變的數(shù)據(jù)。
@synchronized會隱式添加異常處理,當(dāng)發(fā)生異常時自動釋放互斥鎖,性能相對較低。

NSLock

NSLock是iOS中另一種較為常見的鎖,進入NSLock.h中可以發(fā)現(xiàn)NSLock繼承自NSObject并且遵守NSLocking協(xié)議。除此之外,在NSLock.h中還能看到NSConditionLock、NSRecursiveLock和NSCondition這3個類,他們也都是繼承自NSObject并且遵守NSLocking協(xié)議。

NSLocking協(xié)議定義了兩個實例方法,lock和unlock對應(yīng)著加鎖與解鎖

@protocol NSLocking

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

@end

NSLock、NSConditionLock、NSRecursiveLock、NSCondition對應(yīng)的實例都可以通過lock/unlock來進行加鎖/解鎖。

代碼這樣寫就可以確保線程安全

    //A
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            [_lock lock];
            _info = @"a";
            NSLog(@"A--info:%@", _info);
            [_lock unlock];
        }
    });
    
    //B
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            [_lock lock];
            _info = @"b";
            NSLog(@"B--info:%@", _info);
            [_lock unlock];
        }
    });

注意:lock與unlock操作必須在同一線程,否則結(jié)果不確定甚至?xí)鹚梨i

除此之外,NSLock還提供另外兩個方法,見名知意,不做過多解釋。

- (BOOL)tryLock; 
- (BOOL)lockBeforeDate:(NSDate *)limit;
NSConditionLock

NSConditionLock中有這么幾個方法

- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;

在初始化lock時給個condition,屬性condition為readonly,此時也是在給這個屬性賦值
偽代碼

- (instancetype)initWithCondition:(NSInteger)condition {
        if (self =[ [NSConditionLock alloc] init]) {
                [self setValue:@condition forKey:@"condition"];
        }
        return self;
}

利用condition加鎖、解鎖時偽代碼是這樣的

- (void)lockWhenCondition:(NSInteger)condition {
        if (_condition == condition) [self lock];
}

- (void)unlockWithCondition:(NSInteger)condition {
      [self setValue:@condition forKey:@"condition"];
      [self unlock];
}

condition實現(xiàn)條件鎖時(也可以不實現(xiàn),直接調(diào)用協(xié)議方法lock),只有符合條件才能上鎖,但是解鎖為非條件,任意condition都可以解鎖,此時設(shè)置的condition為下一次條件鎖的condition。
線程安全示例代碼

   //A
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            [_lock lockWhenCondition:0];
            _info = @"a";
            NSLog(@"A--info:%@--condition:%zd", _info, _lock.condition);
            [_lock unlockWithCondition:1];

        }
    });
    
    //B
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            [_lock lockWhenCondition:1];
            _info = @"b";
            NSLog(@"B--info:%@--condition:%zd", _info, _lock.condition);
            [_lock unlockWithCondition:0];
        }
    });

利用這個特性,我們可以設(shè)置依賴關(guān)系。通常- (void)lock- (void)unlockWithCondition:(NSInteger)condition 配合使用 ,- (void)lockWhenCondition:(NSInteger)condition- (void) unlock 配合使用,當(dāng)然也可以混用。

NSRecursiveLock

NSRecursiveLock翻譯成中文叫遞歸鎖,顧名思義可處理同一方法內(nèi)部多次上鎖的場景

static int i = 10;

- (void)recursiveLock {
    [_lock lock];
    NSLog(@"NSRecursiveLock--%zd", i--);
    if (i >= 0) {
        [self recursiveLock];
    }
    [_lock unlock];
}

如果把這里的lock換成NSLock顯然必死無疑(死鎖)。不同于其他lock,雖然NSRecursiveLock可以多次上鎖,但是只有當(dāng)上的所有鎖全被解鎖后,其他線程才能再次獲取到NSRecursiveLock

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self recursiveLock];
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            [_lock lock];
            NSLog(@"lock");
            [_lock unlock];
            NSLog(@"unlock");
        }
    });
NSRecursiveLock
NSCondition

NSCondition中有這些方法

- (void)wait; //掛起線程
- (BOOL)waitUntilDate:(NSDate *)limit; //什么時候掛起線程
- (void)signal; // 喚醒一條掛起線程
- (void)broadcast; //喚醒所有掛起線程

NSCondition可以手動控制線程的掛起與喚醒,很明顯可以利用這個特性設(shè)置依賴

基本用法:

    //A
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [_lock lock];
        NSLog(@"A線程加鎖");
        [_lock wait];
        NSLog(@"A線程喚醒");
        [_lock unlock];
        NSLog(@"A線程解鎖");
    });
    
    //B
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [_lock lock];
        NSLog(@"B線程加鎖");
        [_lock wait];
        NSLog(@"B線程喚醒");
        [_lock unlock];
        NSLog(@"B線程解鎖");
    });
    
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(2);
        [_lock signal];
    });
NSCondition-1

如果把[_lock signal]換成[_lock broadcast]

NSCondition-2
dispatch_semaphore

dispatch_semaphore利用信號量進行鎖定

線程安全示例代碼:

- (void)semaphore {
    dispatch_semaphore_t dsema = dispatch_semaphore_create(1);
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            dispatch_semaphore_wait(dsema, DISPATCH_TIME_FOREVER);
            _info = @"a";
            NSLog(@"A--info:%@", _info);
            dispatch_semaphore_signal(dsema);
        }
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            dispatch_semaphore_wait(dsema, DISPATCH_TIME_FOREVER);
            _info = @"b";
            NSLog(@"B--info:%@", _info);
            dispatch_semaphore_signal(dsema);
        }
    });
}

/*! 
 * @param value
 *信號量的起始值,當(dāng)傳入的值小于零時返回NULL
 * @result
 * 成功返回一個新的信號量,失敗返回NULL
 */
dispatch_semaphore_t dispatch_semaphore_create(long value)

/*!
 * @discussion
 * 信號量減1,如果結(jié)果小于0,那么等待隊列中信號增量到來直到timeout
 * @param dsema
 * 信號量
 * @param timeout
 * 等待時間
 * 類型為dispatch_time_t,這里有兩個宏DISPATCH_TIME_NOW、DISPATCH_TIME_FOREVER
 * @result
 * 若等待成功返回0,timeout返回非0
 */
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

/*!
 * @discussion
 * 信號量加1,如果之前的信號量小于0,將喚醒一條等待線程
 * @param dsema 
 * 信號量
 * @result
 * 喚醒一條線程返回非0,否則返回0
 */
long dispatch_semaphore_signal(dispatch_semaphore_t dsema)

OK,了解完3個函數(shù)都是干嘛用的,來試試水

超時,線程喚醒

- (void)semaphore {
    dispatch_semaphore_t dsema = dispatch_semaphore_create(0);
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
            long a = dispatch_semaphore_wait(dsema, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
            NSLog(@"a--%ld", a);
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        sleep(2);
        long b = dispatch_semaphore_signal(dsema);
        NSLog(@"b--%ld", b);
    });
}
semaphore-1

線程未喚醒,未超時

- (void)semaphore {
    dispatch_semaphore_t dsema = dispatch_semaphore_create(0);
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
            long a = dispatch_semaphore_wait(dsema, dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC));
            NSLog(@"a--%ld", a);
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        sleep(1);
        long b = dispatch_semaphore_signal(dsema);
        NSLog(@"b--%ld", b);
    });
}

semaphore-2

結(jié)果和想的一樣,沒啥可繼續(xù)嘮的。多一嘴,線程喚醒與否和是否超時沒必然關(guān)系,要看代碼怎么寫。

OSSpinLock

OSSpinLock自旋鎖,使用時需導(dǎo)入頭文件#import <libkern/OSAtomic.h>

    // 初始化 unlock為0,lock為非0
    OSSpinLock spinLock = OS_SPINLOCK_INIT;
    // 加鎖
    OSSpinLockLock(&spinLock);
    // 解鎖
    OSSpinLockUnlock(&spinLock);
    // 嘗試加鎖
    BOOL b = OSSpinLockTry(&spinLock);
- (void)OSSpinLock {
    OSSpinLock spinLock = OS_SPINLOCK_INIT;
    NSLog(@"加鎖前:%zd", spinLock);
    OSSpinLockLock(&spinLock);
    NSLog(@"加鎖后:%zd", spinLock);
    OSSpinLockUnlock(&spinLock);
    NSLog(@"解鎖后:%zd", spinLock);
}
OSSpinLock-1

再來看一張截圖

OSSpinLock-2

OSSpinLock is deprecated in iOS 10.0 - Use os_unfair_lock() from <os/lock.h> instead

由于自旋鎖存在優(yōu)先級反轉(zhuǎn)問題(可查看YYKit作者的這篇文章 不再安全的 OSSpinLock),在iOS 10.0中被<os/lock.h>中的os_unfair_lock()取代

os_unfair_lock

os_unfair_lock iOS 10.0新推出的鎖,用于解決OSSpinLock優(yōu)先級反轉(zhuǎn)問題


    // 初始化
    os_unfair_lock_t unfairLock = &(OS_UNFAIR_LOCK_INIT);
    // 加鎖
    os_unfair_lock_lock(unfairLock);
    // 解鎖
    os_unfair_lock_unlock(unfairLock);
    // 嘗試加鎖
    BOOL b = os_unfair_lock_trylock(unfairLock);
POSIX LOCK

POSIX LOCK為C語言級別的鎖,需引入頭像文件#import<pthread.h>
線程安全示例代碼:

static pthread_mutex_t lock;
- (void)pLock {
    
    pthread_mutex_init(&lock, NULL);
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            pthread_mutex_lock(&lock);
            _info = @"a";
            NSLog(@"A--info:%@", _info);
            pthread_mutex_unlock(&lock);
        }
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            pthread_mutex_lock(&lock);
            _info = @"b";
            NSLog(@"B--info:%@", _info);
            pthread_mutex_unlock(&lock);
        }
    });
}

POSIX LOCK不單有pthread_mutex_t還有pthread_cond_t等,因為不常用這里不做過多介紹。

NSDistributedLock

NSDistributedLock分布式鎖,用于MAC OS開發(fā),醬油路過

死鎖

所謂死鎖: 是指兩個或兩個以上的進程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外力作用,它們都將無法推進下去。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等待的進程稱為死鎖進程。

雖然進程在運行過程中,可能發(fā)生死鎖,但死鎖的發(fā)生也必須具備一定的條件,死鎖的發(fā)生必須具備以下四個必要條件。
1)互斥條件:指進程對所分配到的資源進行排它性使用,即在一段時間內(nèi)某資源只由一個進程占用。如果此時還有其它進程請求資源,則請求者只能等待,直至占有資源的進程用畢釋放。
2)請求和保持條件:指進程已經(jīng)保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程占有,此時請求進程阻塞,但又對自己已獲得的其它資源保持不放。
3)不剝奪條件:指進程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。
4)環(huán)路等待條件:指在發(fā)生死鎖時,必然存在一個進程——資源的環(huán)形鏈,即進程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1占用的資源;P1正在等待P2占用的資源,……,Pn正在等待已被P0占用的資源。 【引自百科】

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 寫在前面 多線程在日常開發(fā)中能起到性能優(yōu)化的作用,但是一旦沒用好就會造成線程不安全,本文就來講講如何保證線程安全 ...
    M_慕宸閱讀 576評論 0 5
  • iOS中的鎖 前言 寫在前面: 臨界區(qū):指的是一塊對公共資源進行訪問的代碼,并非一種機制或是算法。 自旋鎖:是用于...
    ROBIN2015閱讀 958評論 0 7
  • 本文不介紹各種鎖的高級用法,只是整理鎖相關(guān)的知識點,幫助理解。 鎖的作用 防止在多線程(多任務(wù))的情況下對共享資源...
    HelloiWorld閱讀 3,031評論 0 8
  • 鎖的種類 互斥鎖 自旋鎖 互斥鎖:保證在任何時候,都只有一個線程訪問對象。當(dāng)獲取鎖操作失敗時,線程會進入睡眠,等待...
    ricefun閱讀 414評論 0 1
  • 在平時的開發(fā)中經(jīng)常使用到多線程,在使用多線程的過程中,難免會遇到資源競爭的問題,那我們怎么來避免出現(xiàn)這種問題那? ...
    IAMCJ閱讀 3,314評論 2 25

友情鏈接更多精彩內(nèi)容