iOS多線程安全-鎖

聲明:本文主要參考文章iOS多線程安全-13種線程鎖,整理了代碼格式,看起來更舒服一些

  • 1、為什么要線程安全
  • 2、自旋鎖和互斥鎖
  • 3、鎖的類型
    • 1、OSSpinLock
    • 2、os_unfair_lock
    • 3、pthread_mutex
    • 4、dispatch_semaphore
    • 5、dispatch_queue(DISPATCH_QUEUE_SERIAL)
    • 6、NSLock
    • 7、NSRecursiveLock
    • 8、NSCondition
    • 9、NSConditionLock
    • 10、@synchronized
    • 11、pthread_rwlock
    • 12、dispatch_barrier_async
    • 13、atomic
  • 4、鎖的性能比較

為什么要線程安全

多個(gè)線程訪問同一塊資源的時(shí)候,很容易引發(fā)數(shù)據(jù)混亂問題。 一個(gè)大家都喜歡拿來舉例子的就是買票demo,今天我使用這個(gè)案例 假設(shè)有100張票,同時(shí)開5個(gè)窗口買票,5個(gè)窗口買票,我們來看看結(jié)果

//賣票演示
- (void)ticketTest
{
  self.ticketsCount = 50;
  dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

  for (NSInteger i = 0; i < 5; i++) 
  {
    dispatch_async(queue, ^{
      for (int i = 0; i < 10; i++) {
        [self sellingTickets];
      }
    });
  }
}
//賣票
- (void)sellingTickets
{
  int oldMoney = self.ticketsCount;
  sleep(.2);
  oldMoney -= 1;
  self.ticketsCount = oldMoney;
  NSLog(@"當(dāng)前剩余票數(shù)-> %d", oldMoney);
}
image.png

正常情況下我有50張票,然后賣了50次,剩余票數(shù)應(yīng)該是0,但是打印結(jié)果竟然是3,所以這里就存在了線程安全問題。

出現(xiàn)線程安全的原因

image.png

出現(xiàn)線程安全的原因就是在同一個(gè)時(shí)間,多個(gè)線程同時(shí)讀取一個(gè)值,像線程A和B同時(shí)讀取了當(dāng)前票數(shù)為10,等于是賣了兩張票,但是總票數(shù)其實(shí)就減少了一張。

解決方法

使用線程同步技術(shù),按照預(yù)定的先后次序依次進(jìn)行,常見的線程同步技術(shù)就是加鎖

image.png

自旋鎖和互斥鎖

自旋鎖(Spin lock)

自旋鎖與互斥鎖有點(diǎn)類似,只是自旋鎖不會(huì)引起調(diào)用者睡眠,如果自旋鎖已經(jīng)被別的執(zhí)行單元保持,調(diào)用者就一直循環(huán)在那里看是 否該自旋鎖的保持者已經(jīng)釋放了鎖,"自旋"一詞就是因此而得名。其作用是為了解決某項(xiàng)資源的互斥使用。因?yàn)樽孕i不會(huì)引起調(diào)用者睡眠,所以自旋鎖的效率遠(yuǎn) 高于互斥鎖。雖然它的效率比互斥鎖高,但是它也有些不足之處: 1、自旋鎖一直占用CPU,他在未獲得鎖的情況下,一直運(yùn)行--自旋,所以占用著CPU,如果不能在很短的時(shí) 間內(nèi)獲得鎖,這無疑會(huì)使CPU效率降低。 2、在用自旋鎖時(shí)有可能造成死鎖,當(dāng)遞歸調(diào)用時(shí)有可能造成死鎖,調(diào)用有些其他函數(shù)也可能造成死鎖,如 copy_to_user()、copy_from_user()、kmalloc()等。 因此我們要慎重使用自旋鎖,自旋鎖只有在內(nèi)核可搶占式或SMP的情況下才真正需要,在單CPU且不可搶占式的內(nèi)核下,自旋鎖的操作為空操作。自旋鎖適用于鎖使用者保持鎖時(shí)間比較短的情況下。

互斥鎖

互斥鎖屬于sleep-waiting類型的鎖。例如在一個(gè)雙核的機(jī)器上有兩個(gè)線程(線程A和線程B),它們分別運(yùn)行在Core0和 Core1上。假設(shè)線程A想要通過pthread_mutex_lock操作去得到一個(gè)臨界區(qū)的鎖,而此時(shí)這個(gè)鎖正被線程B所持有,那么線程A就會(huì)被阻塞 (blocking),Core0 會(huì)在此時(shí)進(jìn)行上下文切換(Context Switch)將線程A置于等待隊(duì)列中,此時(shí)Core0就可以運(yùn)行其他的任務(wù)(例如另一個(gè)線程C)而不必進(jìn)行忙等待。而自旋鎖則不然,它屬于busy-waiting類型的鎖,如果線程A是使用pthread_spin_lock操作去請(qǐng)求鎖,那么線程A就會(huì)一直在 Core0上進(jìn)行忙等待并不停的進(jìn)行鎖請(qǐng)求,直到得到這個(gè)鎖為止。

兩種鎖的加鎖原理

互斥鎖:線程會(huì)從sleep(加鎖)——>running(解鎖),過程中有上下文的切換,cpu的搶占,信號(hào)的發(fā)送等開銷。

自旋鎖:線程一直是running(加鎖——>解鎖),死循環(huán)檢測(cè)鎖的標(biāo)志位,機(jī)制不復(fù)雜。

對(duì)比 互斥鎖的起始原始開銷要高于自旋鎖,但是基本是一勞永逸,臨界區(qū)持鎖時(shí)間的大小并不會(huì)對(duì)互斥鎖的開銷造成影響,而自旋鎖是死循環(huán)檢測(cè),加鎖全程消耗cpu,起始開銷雖然低于互斥鎖,但是隨著持鎖時(shí)間,加鎖的開銷是線性增長(zhǎng)。

兩種鎖的應(yīng)用

互斥鎖用于臨界區(qū)持鎖時(shí)間比較長(zhǎng)的操作,比如下面這些情況都可以考慮

  • 1 臨界區(qū)有IO操作
  • 2 臨界區(qū)代碼復(fù)雜或者循環(huán)量大
  • 3 臨界區(qū)競(jìng)爭(zhēng)非常激烈
  • 4 單核處理器

至于自旋鎖就主要用在臨界區(qū)持鎖時(shí)間非常短且CPU資源不緊張的情況下,自旋鎖一般用于多核的服務(wù)器。

13種鎖

1、OSSpinLock

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

//初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
//加鎖
OSSpinLockLock(&lock);
//解鎖
OSSpinLockUnlock(&lock);
復(fù)制代碼

demo

#import "OSSpinLockDemo.h"
#import <libkern/OSAtomic.h>
@interface OSSpinLockDemo()
@property (assign, nonatomic) OSSpinLock ticketLock;
@end

@implementation OSSpinLockDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.ticketLock = OS_SPINLOCK_INIT;
    }
    return self;
}

//賣票
- (void)sellingTickets
{
    OSSpinLockLock(&_ticketLock);
    [super sellingTickets];
    OSSpinLockUnlock(&_ticketLock);
}
@end

image.png

OSSpinLock在iOS10.0以后就被棄用了,可以使用os_unfair_lock_lock替代。而且還有一些安全性問題,具體參考不再安全的 OSSpinLock

2、os_unfair_lock

os_unfair_lock用于取代不安全的OSSpinLock,從iOS10開始才支持 從底層調(diào)用看,等待os_unfair_lock鎖的線程會(huì)處于休眠狀態(tài),并非忙等 需要導(dǎo)入頭文件#import <os/lock.h>

//初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
//加鎖
os_unfair_lock_lock(&lock);
//解鎖
os_unfair_lock_unlock(&lock);

demo

#import "os_unfair_lockDemo.h"
#import <os/lock.h>
@interface os_unfair_lockDemo()
@property (assign, nonatomic) os_unfair_lock ticketLock;
@end

@implementation os_unfair_lockDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.ticketLock = OS_UNFAIR_LOCK_INIT;
    }
    return self;
}

//賣票
- (void)sellingTickets
{
    os_unfair_lock_lock(&_ticketLock);
    [super sellingTickets];
    os_unfair_lock_unlock(&_ticketLock);
}
@end


3、pthread_mutex

mutex叫做”互斥鎖”,等待鎖的線程會(huì)處于休眠狀態(tài)。需要導(dǎo)入頭文件#import <pthread.h> 使用步驟

  • 1、初始化鎖的屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

/*
* 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

  • 2、初始化鎖
// 初始化鎖
pthread_mutex_init(mutex, &attr);
  • 3、初始化鎖結(jié)束以后,銷毀屬性
// 銷毀屬性
pthread_mutexattr_destroy(&attr);
  • 4、加鎖解鎖
pthread_mutex_lock(&_mutex);
pthread_mutex_unlock(&_mutex);
  • 5、銷毀鎖
pthread_mutex_destroy(&_mutex);

備注:我們可以不初始化屬性,在傳屬性的時(shí)候直接傳NULL,表示使用默認(rèn)屬性PTHREAD_MUTEX_NORMAL。pthread_mutex_init(mutex, NULL);

具體代碼

#import "pthread_mutexDemo.h"
#import <pthread.h>
@interface pthread_mutexDemo()
@property (assign, nonatomic) pthread_mutex_t ticketMutex;
@end

@implementation pthread_mutexDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        / / 初始化屬性
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
        // 初始化鎖
        pthread_mutex_init(&(_ticketMutex), &attr);
        // 銷毀屬性
        pthread_mutexattr_destroy(&attr);
    }
    return self;
}

//賣票
- (void)sellingTickets
{
    pthread_mutex_lock(&_ticketMutex);
    [super sellingTickets];
    pthread_mutex_unlock(&_ticketMutex);
}
@end

死鎖 我們稍微的修改一下代碼

//賣票
- (void)sellingTickets {
    pthread_mutex_lock(&_ticketMutex);
    [super sellingTickets];
    [self sellingTickets2];
    pthread_mutex_unlock(&_ticketMutex);
}

- (void)sellingTickets2
{
    pthread_mutex_lock(&_ticketMutex);
    NSLog(@"%s",__func__);
    pthread_mutex_unlock(&_ticketMutex);
}
image.png

上面的代碼就會(huì)造成線程死鎖,因?yàn)榉椒?code>sellingTickets的結(jié)束需要sellingTickets2解鎖,方法sellingTickets2的結(jié)束需要sellingTickets解鎖,相互引用造成死鎖

但是pthread_mutex_t里面有一個(gè)屬性可以解決這個(gè)問題PTHREAD_MUTEX_RECURSIVE

PTHREAD_MUTEX_RECURSIVE 遞歸鎖:允許同一個(gè)線程對(duì)同一把鎖進(jìn)行重復(fù)加鎖。要考重點(diǎn)同一個(gè)線程同一把鎖

- (instancetype)init
{
    self = [super init];
    if (self) {
        // 初始化屬性
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        // 初始化鎖
        pthread_mutex_init(&(_ticketMutex), &attr);
        // 銷毀屬性
        pthread_mutexattr_destroy(&attr);
    }
    return self;
}

對(duì)于上面的問題還有一個(gè)解決方案就是在方法sellingTickets2中重新在創(chuàng)建一把新的鎖,兩個(gè)方法的鎖對(duì)象不同,就不會(huì)造成線程死鎖了。

image.png

條件

// 初始化屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化鎖
pthread_mutex_init(&_mutex, &attr);
// 銷毀屬性
pthread_mutexattr_destroy(&attr);

// 初始化條件
pthread_cond_t condition
pthread_cond_init(&_cond, NULL);

// 等待條件
pthread_cond_wait(&_cond, &_mutex);

//激活一個(gè)等待該條件的線程
pthread_cond_signal(&_cond);
//激活所有等待該條件的線程
pthread_cond_broadcast(&_cond);

//銷毀資源
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);

使用案例:假設(shè)我們有一個(gè)數(shù)組,里面有兩個(gè)線程,一個(gè)是添加數(shù)組,一個(gè)是刪除數(shù)組,我們先調(diào)用刪除數(shù)組,在調(diào)用添加數(shù)組,但是在數(shù)組為空的時(shí)候不調(diào)用刪除數(shù)組。

#import "pthread_mutexDemo1.h"
#import <pthread.h>

@interface pthread_mutexDemo1()
@property (assign, nonatomic) pthread_mutex_t mutex;
@property (assign, nonatomic) pthread_cond_t cond;
@property (strong, nonatomic) NSMutableArray *data;
@end

@implementation pthread_mutexDemo1

- (instancetype)init
{
    if (self = [super init]) {
        // 初始化屬性
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        // 初始化鎖
        pthread_mutex_init(&_mutex, &attr);
        // 銷毀屬性
        pthread_mutexattr_destroy(&attr);
        
        // 初始化條件
        pthread_cond_init(&_cond, NULL);
        
        self.data = [NSMutableArray array];
    }
    return self;
}
- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

// 線程1
// 刪除數(shù)組中的元素
- (void)__remove
{
    pthread_mutex_lock(&_mutex);
    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
        // 等待
        pthread_cond_wait(&_cond, &_mutex);
    }
    
    [self.data removeLastObject];
    NSLog(@"刪除了元素");
    
    pthread_mutex_unlock(&_mutex);
}

// 線程2
// 往數(shù)組中添加元素
- (void)__add
{
    pthread_mutex_lock(&_mutex);
    
    sleep(1);
    
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    
    // 激活一個(gè)等待該條件的線程
    pthread_cond_signal(&_cond);
    
    pthread_mutex_unlock(&_mutex);
}

- (void)dealloc
{
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_cond);
}

為了準(zhǔn)確測(cè)試我們可以在__addsleep(1)

image.png

4、NSLock

NSLock是對(duì)mutex普通鎖的封裝。pthread_mutex_init(mutex, NULL);

NSLock 遵循 NSLocking 協(xié)議。Lock 方法是加鎖,unlock 是解鎖,tryLock 是嘗試加鎖,如果失敗的話返回 NO,lockBeforeDate: 是在指定Date之前嘗試加鎖,如果在指定時(shí)間之前都不能加鎖,則返回NO

@protocol NSLocking
- (void)lock;
- (void)unlock;
@end

@interface NSLock : NSObject <NSLocking> {
@private
void *_priv;
}

- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name
@end

使用起來也是十分的簡(jiǎn)單

#import "LockDemo.h"
@interface LockDemo()
@property (strong, nonatomic) NSLock *ticketLock;
@end
@implementation LockDemo
//賣票
- (void)sellingTickets{
    [self.ticketLock lock];
    [super sellingTickets];
    [self.ticketLock unlock];
}

@end

5、NSRecursiveLock

NSRecursiveLock是對(duì)mutex遞歸鎖的封裝,API跟NSLock基本一致

#import "RecursiveLockDemo.h"
@interface RecursiveLockDemo()
@property (nonatomic,strong) NSRecursiveLock *ticketLock;
@end
@implementation RecursiveLockDemo
//賣票
- (void)sellingTickets{
    [self.ticketLock lock];
    [super sellingTickets];
    [self.ticketLock unlock];
}
@end

6、NSCondition

NSCondition是對(duì)mutexcond的封裝,更加面向?qū)ο?,我們使用起來也更加的方便?jiǎn)潔

@interface NSCondition : NSObject <NSLocking> {
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@property (nullable, copy) NSString *name 
@end
復(fù)制代碼

對(duì)于上面那個(gè)數(shù)組操作的案例我們就可以變成這個(gè)樣子了


// 線程1
// 刪除數(shù)組中的元素
- (void)__remove
{
    [self.condition lock];
    if (self.data.count == 0) {
        // 等待
        [self.condition wait];
    }
    [self.data removeLastObject];
    NSLog(@"刪除了元素");
    [self.condition unlock];
}

// 線程2
// 往數(shù)組中添加元素
- (void)__add
{
    [self.condition lock];
    sleep(1);
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    // 信號(hào)
    [self.condition signal];
    [self.condition unlock];
}

7、NSConditionLock

NSConditionLock是對(duì)NSCondition的進(jìn)一步封裝,可以設(shè)置具體的條件值

@interface NSConditionLock : NSObject <NSLocking> {

- (instancetype)initWithCondition:(NSInteger)condition;

@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;
@property (nullable, copy) NSString *name;
@end
復(fù)制代碼

里面有三個(gè)常用的方法

  • 1、initWithCondition:初始化Condition,并且設(shè)置狀態(tài)值
  • 2、lockWhenCondition:(NSInteger)condition:當(dāng)狀態(tài)值為condition的時(shí)候加鎖
  • 3、unlockWithCondition:(NSInteger)condition當(dāng)狀態(tài)值為condition的時(shí)候解鎖
@interface NSConditionLockDemo()
@property (strong, nonatomic) NSConditionLock *conditionLock;
@end
@implementation NSConditionLockDemo

- (instancetype)init
{
    if (self = [super init]) {
        self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
    }
    return self;
}

- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
}

- (void)__one
{
    [self.conditionLock lock];
    NSLog(@"__one");
    sleep(1);
    [self.conditionLock unlockWithCondition:2];
}

- (void)__two
{
    [self.conditionLock lockWhenCondition:2];
    NSLog(@"__two");
    [self.conditionLock unlockWithCondition:3];
}
@end

image.png

8、dispatch_semaphore

  • semaphore叫做”信號(hào)量”
  • 信號(hào)量的初始值,可以用來控制線程并發(fā)訪問的最大數(shù)量
  • 信號(hào)量的初始值為1,代表同時(shí)只允許1條線程訪問資源,保證線程同步
//表示最多開啟5個(gè)線程
dispatch_semaphore_create(5);
// 如果信號(hào)量的值 > 0,就讓信號(hào)量的值減1,然后繼續(xù)往下執(zhí)行代碼
// 如果信號(hào)量的值 <= 0,就會(huì)休眠等待,直到信號(hào)量的值變成>0,就讓信號(hào)量的值減1,然后繼續(xù)往下執(zhí)行代碼
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
// 讓信號(hào)量的值+1
dispatch_semaphore_signal(self.semaphore);

@interface dispatch_semaphoreDemo()
@property (strong, nonatomic) dispatch_semaphore_t semaphore;
@end
@implementation dispatch_semaphoreDemo
- (instancetype)init
{
    if (self = [super init]) {
        self.semaphore = dispatch_semaphore_create(1);
    }
    return self;
}
- (void)otherTest
{
    for (int i = 0; i < 20; i++) {
        [[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
    }
}
- (void)test
{
    // 如果信號(hào)量的值 > 0,就讓信號(hào)量的值減1,然后繼續(xù)往下執(zhí)行代碼
    // 如果信號(hào)量的值 <= 0,就會(huì)休眠等待,直到信號(hào)量的值變成>0,就讓信號(hào)量的值減1,然后繼續(xù)往下執(zhí)行代碼
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    
    sleep(2);
    NSLog(@"test - %@", [NSThread currentThread]);
    
    // 讓信號(hào)量的值+1
    dispatch_semaphore_signal(self.semaphore);
}
@end

我們?cè)谶\(yùn)行代碼打印的時(shí)候發(fā)現(xiàn),每隔一秒出現(xiàn)一次打印。雖然我們同時(shí)開啟20個(gè)線程,但是一次只能訪問一條線程的資源

9、dispatch_queue

使用GCD的串行隊(duì)列也可以實(shí)現(xiàn)線程同步的

dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
    // 追加任務(wù)1
    for (int i = 0; i < 2; ++i) {
        NSLog(@"1---%@",[NSThread currentThread]);
    }
});

dispatch_sync(queue, ^{
    // 追加任務(wù)2
    for (int i = 0; i < 2; ++i) {
        NSLog(@"2---%@",[NSThread currentThread]);
    }
});

10、@synchronized

@synchronized是對(duì)mutex遞歸鎖的封裝, @synchronized(obj)內(nèi)部會(huì)生成obj對(duì)應(yīng)的遞歸鎖,然后進(jìn)行加鎖、解鎖操作

//賣票
- (void)sellingTickets{
    @synchronized ([self class]) {
        [super sellingTickets];
    }
}

對(duì)是實(shí)現(xiàn)底層我們可以在objc4的objc-sync.mm文件中找到 synchronized就是在開始和結(jié)束的時(shí)候調(diào)用了objc_sync_enter&objc_sync_exit方法。

objc_sync_enter實(shí)現(xiàn)

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;
}

就是根據(jù)id2data方法找到一個(gè)data對(duì)象,然后在對(duì)data對(duì)象進(jìn)行mutex.lock()加鎖操作。我們點(diǎn)擊進(jìn)入id2data方法繼續(xù)查找

#define LIST_FOR_OBJ(obj) sDataLists[obj].data
static StripedMap<SyncList> sDataLists;

發(fā)現(xiàn)獲取data對(duì)象的方法其實(shí)就是根據(jù)sDataLists[obj].data這個(gè)方法來實(shí)現(xiàn)的,也就是一個(gè)哈希表。

關(guān)于 @synchronized,這兒比你想知道的還要多

11、atomic

  • atomic用于保證屬性setter、getter的原子性操作,相當(dāng)于在getter和setter內(nèi)部加了線程同步的鎖
  • 可以參考源碼objc4的objc-accessors.mm
  • 它并不能保證使用屬性的過程是線程安全的

12、pthread_rwlock:讀寫鎖

pthread_rwlock經(jīng)常用于文件等數(shù)據(jù)的讀寫操作,需要導(dǎo)入頭文件#import <pthread.h>

iOS中的讀寫安全方案需要注意一下場(chǎng)景

  • 1、同一時(shí)間,只能有1個(gè)線程進(jìn)行寫的操作
  • 2、同一時(shí)間,允許有多個(gè)線程進(jìn)行讀的操作
  • 3、同一時(shí)間,不允許既有寫的操作,又有讀的操作
//初始化鎖
pthread_rwlock_t lock;
pthread_rwlock_init(&_lock, NULL);

//讀加鎖
pthread_rwlock_rdlock(&_lock);
//讀嘗試加鎖
pthread_rwlock_trywrlock(&_lock)

//寫加鎖
pthread_rwlock_wrlock(&_lock);
//寫嘗試加鎖
pthread_rwlock_trywrlock(&_lock)

//解鎖
pthread_rwlock_unlock(&_lock);
//銷毀
pthread_rwlock_destroy(&_lock);

#import <pthread.h>
@interface pthread_rwlockDemo ()
@property (assign, nonatomic) pthread_rwlock_t lock;
@end

@implementation pthread_rwlockDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        // 初始化鎖
        pthread_rwlock_init(&_lock, NULL);
    }
    return self;
}

- (void)otherTest{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            [self read];
        });
        dispatch_async(queue, ^{
            [self write];
        });
    }
}
- (void)read {
    pthread_rwlock_rdlock(&_lock);
    sleep(1);
    NSLog(@"%s", __func__);
    pthread_rwlock_unlock(&_lock);
}
- (void)write
{
    pthread_rwlock_wrlock(&_lock);
    sleep(1);
    NSLog(@"%s", __func__);
    pthread_rwlock_unlock(&_lock);
}
- (void)dealloc
{
    pthread_rwlock_destroy(&_lock);
}
@end

image.png

我們可以發(fā)現(xiàn)讀操作1s有可能出現(xiàn)多次,但是寫操作不會(huì)

13、dispatch_barrier_async

這個(gè)函數(shù)傳入的并發(fā)隊(duì)列必須是自己通過dispatch_queue_cretate創(chuàng)建的 如果傳入的是一個(gè)串行或是一個(gè)全局的并發(fā)隊(duì)列,那這個(gè)函數(shù)便等同于dispatch_async函數(shù)的效果

//初始化
self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
//讀操作
dispatch_async(self.queue, ^{
});
//寫操作
dispatch_barrier_async(self.queue, ^{

});

鎖的性能比較

性能從高到低排序

  • 1、os_unfair_lock
  • 2、OSSpinLock
  • 3、dispatch_semaphore
  • 4、pthread_mutex
  • 5、dispatch_queue(DISPATCH_QUEUE_SERIAL)
  • 6、NSLock
  • 7、NSCondition
  • 8、pthread_mutex(recursive)
  • 9、NSRecursiveLock
  • 10、NSConditionLock
  • 11、@synchronized
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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