来自 电脑系统 2019-09-19 12:06 的文章
当前位置: 金沙澳门官网网址 > 电脑系统 > 正文

各种线程锁,多线程之线程安全

级别: ★★☆☆☆标签:「iOS」「多线程」「线程安全」作者: dac_1033审校: QiShare团队

参考:
正确使用多线程同步锁@synchronized()
iOS中的锁
iOS多线程安全详解
iOS 常见知识点(三):Lock

一、线程安全问题

在单线程的情形下,任务依次串行执行是不存在线程安全问题的。在单线程的情形下,如果多线程都是访问共享资源而不去修改共享资源也可以保证线程安全,比如:设置只读属性的全局变量。线程不安全是由于多线程访问造成的,是由于多线程访问和修改共享资源而引起不可预测的结果。而线程锁可以有效的解决线程安全问题,大致过程如下图:

金沙澳门官网网址 1无线程锁金沙澳门官网网址 2加线程锁

iOS 多线程开发中为保证线程安全而常用的几种锁:NSLockdispatch_semaphoreNSConditionNSRecursiveLockNSConditionLock@synchronized,这几种锁各有优点,适用于不同的场景,下面我们就来依次介绍一下。

各种锁

1、 简单常见的:
(1)atomic
(2)@synchronized
2、NS对象形式的:
(1)NSLock
(2)NSConditionLock条件锁
(3)NSRecursiveLock递归锁
(4)NSCondition挂起唤醒
3、GCD的:
(1)dispatch_semaphore
(2)dispatch_barrier_async
4、高级的:
(1)OSSpinLock
(2)os_unfair_lock
(3)pthread_mutex_t

金沙澳门官网网址 3

性能对比

二、iOS中的锁

atomic

atomic:原子属性,只是为gettet、setter方法加锁
atomic与nonatomic的区别

   // nonatomic修饰,会崩溃
    for (int i=0; i< 10000; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self.obj_nonatomic = [NSObject new];
        });
    }

    // atomic修饰,不崩溃
    for (int i=0; i< 10000; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self.obj_atomic = [NSObject new];
        });
    }

atomic只是gettet、setter方法加锁,其他操作会有线程安全问题

@interface ViewController ()
@property (atomic , 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);
        }
    });    
    // 根据线程安全定义,如果atomic为线程安全A输出应该永远为A--info:a,B输出应该永远为B--info:b
    // NSlog会有:A--info:b
}
@end
1. NSLock

NSLock 是OC层封装底层线程操作来实现的一种锁,继承NSLocking协议,在此我们不讨论各种锁的实现细节,因为基本用不到。NSLock使用非常简单:

NSLock *lock = [NSLock alloc] init];// 加锁[lock lock];/** 被加锁的代码区间*/// 解锁[lock Unlock];

我们以车站购票为例子,多个窗口同时售票,每个窗口有人循环购票:

// 定义NSLock变量@property (nonatomic, strong) NSLock *lock;// 实例化_lock = [[NSLock alloc] init];/*******************************************************************************/// 调用测试方法dispatch_queue_t queue = dispatch_queue_create("QiMultiThreadSafeQueue", DISPATCH_QUEUE_CONCURRENT); for (NSInteger i=0; i<10; i++) { dispatch_async(queue, ^{ [self testNSLock]; }); }}/*******************************************************************************/// 测试方法- testNSLock { while  { [_lock lock]; if (_ticketCount > 0) { _ticketCount --; NSLog(@"--->> %@已购票1张,剩余%ld张", [NSThread currentThread], _ticketCount); } else { [_lock unlock]; return; } [_lock unlock]; sleep; }}
@synchronized(obj)
  • synchronized是使用的递归mutex来做同步。
  • @synchronized(nil)不起任何作用

synchronized中传入的object的内存地址,被用作key,通过hash map对应的一个系统维护的递归锁。
所以不管是传入什么类型的object,只要是有内存地址,就能启动同步代码块的效果。

注意:需要合理使用obj,不是全部都用self

// 简单用法
@synchronized(obj) {
  //code
}

// 可以嵌套、递归
@synchronized(obj) {
      @synchronized(obj) {
      //code
    }
}
2. dispatch_semaphore

dispatch_semaphore 是 GCD 提供的,使用信号量来控制并发线程的数量(可同时进入并执行加锁代码块的线程的数量),相关的三个函数:

// 创建信号量dispatch_semaphore_create(long value); //等待信号dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);发送信号dispatch_semaphore_signal(dispatch_semaphore_t dsema);

//! 定义信号量semaphore@property (nonatomic, strong) dispatch_semaphore_t semaphore;//! 实例化_semaphore = dispatch_semaphore_create;/*******************************************************************************/// 调用测试方法- multiThread { dispatch_queue_t queue = dispatch_queue_create("QiMultiThreadSafeQueue", DISPATCH_QUEUE_CONCURRENT); for (NSInteger i=0; i<2; i++) { dispatch_async(queue, ^{ [self testDispatchSemaphore:i]; }); }}/*******************************************************************************/// 测试方法- testDispatchSemaphore:(NSInteger)num { while  { // 参数1为信号量;参数2为超时时间;ret为返回值 //dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); long ret = dispatch_semaphore_wait(_semaphore, dispatch_time(DISPATCH_TIME_NOW, (0.21*NSEC_PER_SEC))); if  { if (_ticketCount > 0) { NSLog(@"%d 窗口 卖了第%d张票", num, _ticketCount); _ticketCount --; } else { dispatch_semaphore_signal(_semaphore); NSLog(@"%d 卖光了", ; break; } [NSThread sleepForTimeInterval:0.2]; dispatch_semaphore_signal(_semaphore); } else { NSLog(@"%d %@", num, @"超时了"); } [NSThread sleepForTimeInterval:0.2]; }}

当第一各参数semaphore取值为1时,dispatch_semaphore_wait(semaphore, timeout)与dispatch_semaphore_signal成对出现,所达到的效果就跟NSLock中的lock和unlock是一样的。区别在于当semaphore取值为n时,则可以有n个线程同时访问被保护的临界区,即可以控制多个线程并发。第二个参数为dispatch_time_t类型,如果直接输入一个非dispatch_time_t的值会导致dispatch_semaphore_wait方法偶尔返回非0值。

NSLocking

NSLocking协议定义了两个实例方法,lock和unlock对应着加锁与解锁

@protocol NSLocking

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

@end

NSLock、NSConditionLock、NSRecursiveLock、NSCondition对应的实例都可以通过lock/unlock来进行加锁/解锁。

3. NSCondition

NSCondition 常用于生产者-消费者模式,它继承于NSLocking协议,同样有lock和unlock方法。条件变量有点像信号量,提供了线程阻塞与信号机制,因此可以用来阻塞某个线程,并等待数据就绪,再唤醒线程。

NSCondition *lock = [[NSCondition alloc] init];//线程A[lock lock];[lock wait]; // 线程被挂起[lock unlock];//线程2sleep;//以保证让线程2的代码后执行[lock lock];[lock signal]; // 唤醒线程1[lock unlock];

我们执行了两次for循环,起了两批新线程,一批来add数据,另一批来remove数据。其中add数据方法加锁,remove数据方法也加了锁:

// 定义变量@property (nonatomic, strong) NSCondition *condition;// 实例化_condition = [[NSCondition alloc] init];/*******************************************************************************/// 调用测试方法- multiThread { dispatch_queue_t queue = dispatch_queue_create("QiMultiThreadSafeQueue", DISPATCH_QUEUE_CONCURRENT); for (NSInteger i=0; i<10; i++) { dispatch_async(queue, ^{ [self testNSConditionAdd]; }); } for (NSInteger i=0; i<10; i++) { dispatch_async(queue, ^{ [self testNSConditionRemove]; }); }}/*******************************************************************************/// 测试方法- testNSConditionAdd { [_condition lock]; // 生产数据 NSObject *object = [NSObject new]; [_ticketsArr addObject:object]; NSLog(@"--->>%@ add", [NSThread currentThread]); [_condition signal]; [_condition unlock];}- testNSConditionRemove { [_condition lock]; // 消费数据 if (!_ticketsArr.count) { NSLog(@"--->> wait"); [_condition wait]; } [_ticketsArr removeObjectAtIndex:0]; NSLog(@"--->>%@ remove", [NSThread currentThread]); [_condition unlock];}
NSLock
@interface NSLock : NSObject <NSLocking> {
@private
    void *_priv;
}

// 尝试加锁,如果失败了,并不会阻塞线程,只是立即返回NO
- (BOOL)tryLock; 
// 是在指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO,阻塞线程。
- (BOOL)lockBeforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name;

@end

简单测试例子:

    //主线程中
    NSLock *lock = [[NSLock alloc] init];
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lock];
        NSLog(@"线程1");
        [lock unlock];
        NSLog(@"线程1解锁成功");
    });

    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lock];
        NSLog(@"线程2");
        [lock unlock];
        NSLog(@"线程2解锁成功");
    });
4. NSConditionLock

NSConditionLock 为条件锁,lockWhenCondition:方法是当condition参数与初始化时候的 condition 相等时才可加锁。而unlockWithCondition:方法并不是当 Condition 符合条件时才解锁,而是解锁之后,修改 Condition 的值。NSConditionLock 借助 NSCondition 来实现,它的本质就是一个生产者-消费者模型。“条件被满足”可以理解为生产者提供了新的内容NSConditionLock 的内部持有一个 NSCondition 对象,以及 _condition_金沙澳门官网网址 ,value 属性,在初始化时就会对这个属性进行赋值:

// 设置条件#define CONDITION_NO_DATA 100#define CONDITION_HAS_DATA 101/*******************************************************************************/// 初始化条件锁对象@property (nonatomic, strong) NSConditionLock *conditionLock;// 实例化_conditionLock = [[NSConditionLock alloc] initWithCondition:CONDITION_NO_DATA];/*******************************************************************************/// 调用测试方法- multiThread { dispatch_queue_t queue = dispatch_queue_create("QiMultiThreadSafeQueue", DISPATCH_QUEUE_CONCURRENT); for (NSInteger i=0; i<10; i++) { dispatch_async(queue, ^{ [self testNSConditionLockAdd]; }); } for (NSInteger i=0; i<10; i++) { dispatch_async(queue, ^{ [self testNSConditionLockRemove]; }); }}/*******************************************************************************/// 测试方法- testNSConditionLockAdd { // 满足CONDITION_NO_DATA时,加锁 [_conditionLock lockWhenCondition:CONDITION_NO_DATA]; // 生产数据 NSObject *object = [NSObject new]; [_ticketsArr addObject:object]; NSLog(@"---->>%@ add", [NSThread currentThread]); [_condition signal]; // 有数据,解锁并设置条件 [_conditionLock unlockWithCondition:CONDITION_HAS_DATA];}- testNSConditionLockRemove { // 有数据时,加锁 [_conditionLock lockWhenCondition:CONDITION_HAS_DATA]; // 消费数据 if (!_ticketsArr.count) { NSLog(@"---->> wait"); [_condition wait]; } [_ticketsArr removeObjectAtIndex:0]; NSLog(@"---->>%@ remove", [NSThread currentThread]); //3. 没有数据,解锁并设置条件 [_conditionLock unlockWithCondition:CONDITION_NO_DATA];}
NSConditionLock条件锁
@interface NSConditionLock : NSObject <NSLocking> {
@private
    void *_priv;
}

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

@property (readonly) NSInteger condition;

- (void)lockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;

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

- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name;

@end

condition实现条件锁时(也可以不实现,直接调用协议方法lock),只有符合条件才能上锁,但是解锁为非条件,任意condition都可以解锁,此时设置的condition为下一次条件锁的condition。

伪代码

- (instancetype)initWithCondition:(NSInteger)condition {
        if (self =[ [NSConditionLock alloc] init]) {
               _condition = condition
        }
        return self;
}
// 利用condition加锁、解锁时伪代码是这样的
- (void)lockWhenCondition:(NSInteger)condition {
        if (_condition == condition) [self lock];
}
- (void)unlockWithCondition:(NSInteger)condition {
      [self setValue:@condition forKey:@"condition"];
      [self unlock];
}

简单例子:

    //主线程中
    NSConditionLock *theLock = [[NSConditionLock alloc] init];

    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (int i=0;i<=2;i++)
        {
            [theLock lock];
            NSLog(@"thread1:%d",i);
            sleep(2);
            [theLock unlockWithCondition:i];
        }
    });

    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [theLock lockWhenCondition:2];
        NSLog(@"thread2");
        [theLock unlock];
    });

    /*运行结果
     2017-03-04 22:21:29.031 LockDemo[87455:3031878] thread1:0
     2017-03-04 22:21:31.105 LockDemo[87455:3031878] thread1:1
     2017-03-04 22:21:33.175 LockDemo[87455:3031878] thread1:2
     2017-03-04 22:21:35.249 LockDemo[87455:3031879] thread2
     */
5. NSRecursiveLock

顾名思义,NSRecursiveLock定义的是一个递归锁,这个锁可以被同一线程多次请求,而不会引起死锁。这主要是用在循环或递归操作中。NSRecursiveLock在识别到递归时,只加1次锁,在递归返回时也只解锁1次。

// 初始化锁对象@property (nonatomic, strong) NSRecursiveLock *recursiveLock;_recursiveLock = [[NSRecursiveLock alloc] init];/*******************************************************************************/// 加锁的递归方法- testNSRecursiveLock:(NSInteger)tag { [_recursiveLock lock]; if (tag > 0) { [self testNSRecursiveLock:tag - 1]; NSLog(@"--->> %ld", ; } [_recursiveLock unlock];}
NSRecursiveLock递归锁

NSRecursiveLock 是递归锁,他和 NSLock 的区别在于,NSRecursiveLock 可以在一个线程中重复加锁(反正单线程内任务是按顺序执行的,不会出现资源竞争问题),NSRecursiveLock 会记录上锁和解锁的次数,当二者平衡的时候,才会释放锁,其它线程才可以上锁成功。

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

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

@property (nullable, copy) NSString *name ;

@end

简单例子

static int i = 10;

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

本文由金沙澳门官网网址发布于电脑系统,转载请注明出处:各种线程锁,多线程之线程安全

关键词: