iOS:多线程与锁

按照功能来区分锁:

互斥锁(mutexlock)sleep-waiting:

保证共享数据操作的完整性, 锁被占用的时候会休眠, 等待锁释放的时候会唤醒。
在访问共享资源之前进行加锁,访问完成后解锁。
线程A获取到锁,在释放锁之前,其他线程都获取不到锁。
解锁时,如果有1个以上的线程阻塞,那么所有该锁上的线程变为就绪状态,第一个就绪的加锁,其他的又进入休眠。
从而实现在任意时刻,最多只有1个线程能够访问被互斥锁保护的资源。

自旋锁(spinlock)busy-waiting:

跟互斥锁类似, 只是资源被占用的时候, 会一直循环检测锁是否被释放(CPU不能做其他的事情)
线程A获取到锁,在释放锁之前,线程B 来获取锁,此时获取不到,线程B就会不断的进入循环,一直检查锁是否已被释放,如果释放,则能获取到锁。
由于调用方会一直循环看该自旋锁的的保持者是否已经释放了资源,节省了唤醒睡眠线程的内核消耗。
但是如果不能短时间获得锁,就会一直占用着CPU,造成效率低下。
(在加锁时间短暂的情况下会大大提高效率)

其他锁都是这两种锁的延伸和扩展。

常见的锁

1、OSSpinLock

自旋锁,会造成优先级反转的问题。当一个低优先级线程获得锁的时候,如果此时一个高优先级的系统到来,那么会进入忙等状态,不会进入睡眠,此时会一直占用着系统CPU时间,导致低优先级的无法拿到CPU时间片,从而无法完成任务也无法释放锁。除非能保证访问锁的线程全部处于同一优先级,否则系统所有的自旋锁都会出现优先级反转的问题。

现在苹果的OSSpinLockiOS10+已废弃,已经被替换成os_unfair_lock。,有兴趣可以看看:不再安全的OSSpinLock

2、os_unfair_lock

互斥锁,iOS10+才支持,为了替代OSSpinLock。

os_unfair_lock 是一种适用于需要简单和轻量级互斥的情况下的锁。

此锁必须从与锁定它的相同线程中解锁,尝试从不同线程解锁将导致断言中止进程。
必须使用 OS_UNFAIR_LOCK_INIT 进行初始化。

使用时需要引入#import <os/lock.h>

// 必须在线程里初始化 且必须使用OS_UNFAIR_LOCK_INIT初始化
os_unfair_lock_t unfairLock = &(OS_UNFAIR_LOCK_INIT); 
os_unfair_lock_lock(unfairLock); // 加锁
......其他操作......
os_unfair_lock_unlock(unfairLock); // 解锁

3、pthread_mutex

互斥锁,等待锁的线程会处于休眠状态。

使用时需要引入头文件#import <pthread.h>

  // 定义互斥锁和互斥锁属性对象
  // 若不特殊设置互斥锁属性对象可以不设置
  // 初始化互斥锁时的第二个参数可以传NULL
    pthread_mutex_t mutex;
    pthread_mutexattr_t mutex_attr;

    // 初始化互斥锁属性对象
    pthread_mutexattr_init(&mutex_attr);
    
    // 设置互斥锁属性为默认类型
    pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_DEFAULT);
    
    // 初始化互斥锁
    pthread_mutex_init(&mutex, &mutex_attr);

    // 加锁
    pthread_mutex_lock(&mutex);
    
    // 访问共享资源
    // 这里省略了共享资源的操作
    
    // 解锁
    pthread_mutex_unlock(&mutex);
    
    // 销毁互斥锁
    pthread_mutex_destroy(&mutex);
    
    // 销毁互斥锁属性对象
    pthread_mutexattr_destroy(&mutex_attr);
#define PTHREAD_MUTEX_NORMAL        0
#define PTHREAD_MUTEX_ERRORCHECK    1
#define PTHREAD_MUTEX_RECURSIVE        2
#define PTHREAD_MUTEX_DEFAULT        PTHREAD_MUTEX_NORMAL

互斥锁类型具体为:
PTHREAD_MUTEX_NORMAL:普通锁。默认类型。不提供死锁检测或递归保护。如果同一线程尝试再次获取锁,可能会导致死锁。
PTHREAD_MUTEX_ERRORCHECK:错误检查锁。提供死锁检测。如果同一线程尝试再次获取锁,函数将返回错误 EBUSY。
PTHREAD_MUTEX_RECURSIVE:递归锁。允许同一线程多次获取锁而不会死锁。必须在解锁时与加锁次数匹配。
PTHREAD_MUTEX_DEFAULT:等同于 PTHREAD_MUTEX_NORMAL。

4、NSLock

互斥锁,遵循NSLocking协议。内部是封装了pthread_mutext,类型是PTHREAD_MUTEXT_ERRORCHECK。

NSLock、NSCondition、NSConditionLock、NSRecursiveLock都实现了NSLocking协议。了解更多查看官方文档

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

-lock和-unlock必须在相同的线程调用。

5、NSCondition

互斥锁,也遵循NSLocking协议。内部是封装了pthread_mutext,和它和NSLock的区别是:NSLock在获取不到锁的时候自动使线程进入休眠,锁被释放后线程又自动被唤醒,NSCondition可以使我们更加灵活的控制线程状态,在任何需要的时候使线程进入休眠或唤醒它。

// 让当前线程处于等待状态
- (void)wait;
// 向等待在条件上的一个线程发出信号,可以继续执行
- (void)signal;
//   向等待在条件上的所有线程发出信号,从而唤醒所有线程
- (void)broadcast;
NSCondition *cLock = [NSCondition new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [cLock lock];
    NSLog(@"线程1加锁成功");
    [cLock wait];
    NSLog(@"线程1");
    [cLock unlock];

});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [cLock lock];
    NSLog(@"线程2加锁成功");
    [cLock wait];
    sleep(1);
    NSLog(@"线程2");
    [cLock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(2);
    NSLog(@"唤醒一个等待的线程");
    [cLock signal];
});

输出:
线程1加锁成功
线程2加锁成功
唤醒一个等待的线程
线程1

6、NSConditionLock

互斥锁,有条件的互斥锁。

// 只读属性condition,保存锁当前的条件
@property (readonly) NSInteger condition;

// 解锁并设置新的条件值。

-(void)unlockWithCondition:(NSInteger)condition;

// 条件值满足时加锁, 执行之后代码

-(BOOL)lockWhenCondition:(NSInteger)condition;

// 当条件值等于指定值时尝试加锁。

-(BOOL)tryLockWhenCondition:(NSInteger)condition;

NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0]; // 设置初始标志位
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  if ([cLock tryLockWhenCondition:0]) {
      NSLog(@"Task - 1, cLock.condition = %ld",cLock.condition);
      [cLock unlockWithCondition:1];
      NSLog(@"Task - 1, end - cLock.condition = %ld",cLock.condition);

  } else {
      NSLog(@"Task -  加锁失败");
  }
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [cLock lockWhenCondition:3];
    NSLog(@"Task - 2, cLock.condition = %ld",cLock.condition);
    [cLock unlockWithCondition:2];
    NSLog(@"Task - 2, end- cLock.condition = %ld",cLock.condition);
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [cLock lockWhenCondition:1];
    NSLog(@"Task - 3, cLock.condition = %ld",cLock.condition);
    [cLock unlockWithCondition:3];
    NSLog(@"Task - 3, end- cLock.condition = %ld",cLock.condition);

});

输出:
Task - 1, cLock.condition = 0
Task - 1, end - cLock.condition = 1
Task - 3, cLock.condition = 1
Task - 3, end- cLock.condition = 3
Task - 2, cLock.condition = 3
Task - 2, end- cLock.condition = 2

7、NSRecursiveLock

互斥锁中的递归锁。内部使用的pthread_mutex_t的类型是PTHREAD_MUTEXT_RECURSIVE。可以被同一线程多次请求,而不会引起死锁。这主要是用在循环或递归操作中。

NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
NSLock *lo = [NSLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    static void (^RecursiveMethod)(int);
    RecursiveMethod = ^(int value) {
        NSLog(@"加锁: %d", value);
        [lock lock]; // 加锁
        if (value < 3) {
            sleep(2);
            RecursiveMethod(++value); // 解锁之前又锁上
        }
        NSLog(@"解锁: %d", value);
        [lock unlock]; // 解锁
    };
    RecursiveMethod(1);
    NSLog(@"finish");
});

输出:
加锁: 1
加锁: 2
加锁: 3
解锁: 3
解锁: 3
解锁: 2
finish

8、@synchronized

@synchronized(id)的使用应该是较多的,它底层实现是个递归锁,不会产生死锁,且不需要手动去加锁解锁,使用起来比较方便。

@synchronized(self) {  
    //数据操作  
}

9、atomic

修饰属性关键字,对被修饰的进行原子操作(不负责使用,只是setter/getter方法安全,其他不保证)点击属性关键字-atomic了解更多。

10、dispatch_semaphore_t

是GCD用来同步的一种方式。

GCD 信号量dispatch_semaphore可以用来控制最大并发数量,可以用来实现 iOS 的线程同步方案。
信号量的初始值,可以用来控制线程并发访问的最大数量;
信号量的初始值为1,代表同时只允许 1 条线程访问资源,保证线程同步。
若设置信号量初始值为3.则控制最大并发数为3。

// 创建一个 Semaphore 并初始化信号的总量
dispatch_semaphore_create

// 发送一个信号,让信号总量加 1
dispatch_semaphore_signal

// 可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
dispatch_semaphore_wait

点击iOS:多线程,了解更多信号量相关信息。

锁的关系及性能

锁之间的关系

性能排序(从高到低):

1、os_unfair_lock

2、OSSpinLock(已经不推荐使用)

3、dispatch_semaphore

4、pthread_mutext

5、NSLock

6、NSCondition

7、NSRecursiveLock

8、NSConditonLock

9、@synchronized

死锁

指多个进程因竞争共享资源而造成的一种僵局,若无外力作用,这些进程都将永远不能再向前推进。

死锁的条件

1、互斥:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。

2、占有且等待:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。

3、不可抢占:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来,只能在使用完时由自己释放。

4、循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 前言 iOS开发中由于各种第三方库的高度封装,对锁的使用很少,刚好之前面试中被问到的关于并发编程锁的问题,都是一知...
    iOS__开发者皮皮峰阅读 3,649评论 1 5
  • 前言 iOS开发中由于各种第三方库的高度封装,对锁的使用很少,刚好之前面试中被问到的关于并发编程锁的问题,都是一知...
    KingWorld阅读 4,819评论 0 0
  • 多线程需要一种互斥的机制来访问共享资源。 一、 互斥锁 互斥锁的意思是某一时刻只允许一个线程访问某一资源。为了保证...
    doudo阅读 4,084评论 0 5
  • 前言 iOS开发中由于各种第三方库的高度封装,对锁的使用很少,刚好之前面试中被问到的关于并发编程锁的问题,都是一知...
    喵渣渣阅读 9,112评论 0 33
  • 1.概述 1.1 类型 自旋锁 OSSpinLock 信号量 dispatch_semaphore 互斥锁 ...
    George_Luofz阅读 2,880评论 0 0