一.多线程的安全隐患以及解决方案

1.隐患

  • 资源共享
    1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
    比如多个线程访问同一个对象、同一个变量、同一个文件

  • 当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题

2.隐患解决方案:

  • 解决方案:使用线程同步技术(同步,就是协同步调,按预定的先后次序进行)
  • 常见的线程同步技术是:加锁

不是多线程就需要锁;多个线程访问一个值的话不需要;如果多个线程里面,即修改值、又访问这个值,就会有不一致的情况,所以需要锁

二.锁

1.自旋锁 OSSpinLock

  • OSSpinLock叫做”自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源

  • 目前已经不再安全,可能会出现优先级反转问题
    比如有两个线程,一个线程优先级高、一个优先级低。线程开始的时候,优先级低的线程先进入了线程,锁住了。
    优先级高的线程就会处于等待状态、一直占用着CPU。因为线程优先级高,优先级低的线程无法进行,就无法解锁。造成死锁。
    (即:如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁)

  • 需要导入头文件#import

OSSpinlock
#import 
//锁是结构体,所以用assign修饰
@property (assign, nonatomic) OSSpinLock moneyLock;
@implementation OSSpinLockDemo

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

- (void)__drawMoney
{
    OSSpinLockLock(&_moneyLock);
    
    [super __drawMoney];
    
    OSSpinLockUnlock(&_moneyLock);
}
- (void)__saveMoney
{
    OSSpinLockLock(&_moneyLock);
    
    [super __saveMoney];
    
    OSSpinLockUnlock(&_moneyLock);
}
@end

2. 互斥锁

2.1 os_unfair_lock(替代OSSpinLock)

  • os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持
  • 从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等
  • 需要导入头文件#import


    os_unfair_lock
#import 

@property (assign, nonatomic) os_unfair_lock moneyLock;
@implementation OSUnfairLockDemo

- (instancetype)init
{
    if (self = [super init]) {
        self.moneyLock = OS_UNFAIR_LOCK_INIT;
    }
    return self;
}
- (void)__saveMoney
{
    os_unfair_lock_lock(&_moneyLock);
    
    [super __saveMoney];
    
    os_unfair_lock_unlock(&_moneyLock);
}

- (void)__drawMoney
{
    os_unfair_lock_lock(&_moneyLock);
    
    [super __drawMoney];
    
    os_unfair_lock_unlock(&_moneyLock);
}

@end

2.2 pthread_mutex

  • mutex叫做”互斥锁”,等待锁的线程会处于休眠状态
  • 需要导入头文件#import
    (pthread开头的是跨平台的,linux、oc都可)


    pthread_mutex
attrtype
#import 
@property (assign, nonatomic) pthread_mutex_t moneyMutex;
- (void)__initMutex:(pthread_mutex_t *)mutex
{

    // 初始化属性
   pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
    // 初始化锁
    pthread_mutex_init(mutex, attr);
    // 销毁属性
    pthread_mutexattr_destroy(&attr);
}

- (instancetype)init
{
    if (self = [super init]) {
        [self __initMutex:&_moneyMutex];
    }
    return self;
}
- (void)__saveMoney
{
    pthread_mutex_lock(&_moneyMutex);
    
    [super __saveMoney];
    
    pthread_mutex_unlock(&_moneyMutex);
}

- (void)__drawMoney
{
    pthread_mutex_lock(&_moneyMutex);
    
    [super __drawMoney];
    
    pthread_mutex_unlock(&_moneyMutex);
}

- (void)dealloc
{
    pthread_mutex_destroy(&_moneyMutex);
}

@end

3. 递归锁

3.1 pthread_mutex – 递归锁

  • 把pthread_mutex属性的type设置为递归(OTHREAD_MUTEX_RECURSIVE)即可。
  • 允许同一个线程对一把锁进行重复加锁
递归锁
#import 
@property (assign, nonatomic) pthread_mutex_t mutex;

@implementation MutexDemo2

- (void)__initMutex:(pthread_mutex_t *)mutex
{
    // 递归锁:允许同一个线程对一把锁进行重复加锁
    
    // 初始化属性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    // 初始化锁
    pthread_mutex_init(mutex, &attr);
    // 销毁属性
    pthread_mutexattr_destroy(&attr);
}

- (instancetype)init
{
    if (self = [super init]) {
        [self __initMutex:&_mutex];
    }
    return self;
}
/**
 线程1:otherTest(+-)
        otherTest(+-)
         otherTest(+-)
 
 线程2:otherTest(等待)
 */

- (void)otherTest
{
    pthread_mutex_lock(&_mutex);
    
    NSLog(@"%s", __func__);
    
    static int count = 0;
    if (count < 10) {
        count++;
        [self otherTest];
    }
    
    pthread_mutex_unlock(&_mutex);
}
- (void)dealloc
{
    pthread_mutex_destroy(&_mutex);
}

@end

4. 条件锁

4.1 pthread_mutex – 条件

image.png

// 等待(解锁--》添加重新激活的条件--》等待)
pthread_cond_wait(&_cond, &_mutex);

// 信号(发出信号,告诉其他线程这个条件以满足,可以继续执行了。)
pthread_cond_signal(&_cond);

// 广播(如果有多个线程在等待这个条件,用这个广播发出)
// pthread_cond_broadcast(&_cond);

// 解锁(其他线程受到满足条件的通知后,也需要这里解锁后才能进行加锁)
pthread_mutex_unlock(&_mutex);
@interface MutexDemo3()
@property (assign, nonatomic) pthread_mutex_t mutex;
@property (assign, nonatomic) pthread_cond_t cond;
@property (strong, nonatomic) NSMutableArray *data;
@end

@implementation MutexDemo3

- (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
// 删除数组中的元素
- (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
// 往数组中添加元素
- (void)__add
{
    pthread_mutex_lock(&_mutex);
    
    sleep(1);
    
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    
    // 信号(发出信号,告诉其他线程这个条件以满足,可以继续执行了。)
    pthread_cond_signal(&_cond);
    // 广播(如果有多个线程在等待这个条件,用这个广播发出)
//    pthread_cond_broadcast(&_cond);
    
    // 解锁(其他线程受到满足条件的通知后,也需要这里解锁后才能进行加锁)
    pthread_mutex_unlock(&_mutex);
}

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

@end

三.锁的封装

1. NSLock(NSLock是对mutex普通锁的封装)

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

- (void)__saveMoney
{
    [self.moneyLock lock];
    
    [super __saveMoney];
    
    [self.moneyLock unlock];
}

- (void)__drawMoney
{
    [self.moneyLock lock];
    
    [super __drawMoney];
    
    [self.moneyLock unlock];
}

@end

2. NSRecursiveLock(也是对mutex递归锁的封装,API跟NSLock基本一致)

3. NSCondition(NSCondition是对mutex和cond的封装)

NSCondition

@interface NSConditionDemo()
@property (strong, nonatomic) NSCondition *condition;
@property (strong, nonatomic) NSMutableArray *data;
@end

@implementation NSConditionDemo

- (instancetype)init
{
    if (self = [super init]) {
        self.condition = [[NSCondition alloc] init];
        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
// 删除数组中的元素
- (void)__remove
{
    [self.condition lock];
    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
        // 等待
        [self.condition wait];
    }
    
    [self.data removeLastObject];
    NSLog(@"删除了元素");
    
    [self.condition unlock];
}

// 线程2
// 往数组中添加元素
- (void)__add
{
    [self.condition lock];
    
    sleep(1);
    
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    
    // 信号
    [self.condition signal];
    
    sleep(2);
    
    [self.condition unlock];
}
@end

4. NSConditionLock(对NSCondition的封装、可添加依赖条件)

NSConditionLock
@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];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
}

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

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

- (void)__three
{
    [self.conditionLock lockWhenCondition:3];
    
    NSLog(@"__three");
    
    [self.conditionLock unlock];
}

@end

四. 其他同步方案

1. 信号量 dispatch_semaphore

  • semaphore叫做”信号量”
  • 信号量的初始值,可以用来控制线程并发访问的最大数量
  • 信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步


    信号量
@interface SemaphoreDemo()
@property (strong, nonatomic) dispatch_semaphore_t semaphore;
@end

@implementation SemaphoreDemo

- (instancetype)init
{
    if (self = [super init]) {
        self.semaphore = dispatch_semaphore_create(5);
    }
    return self;
}

- (void)otherTest
{
    for (int i = 0; i < 20; i++) {
        [[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
    }
}

// 线程10、7、6、9、8
- (void)test
{
    // 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
    // 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    
    sleep(2);
    NSLog(@"test - %@", [NSThread currentThread]);
    
    // 让信号量的值+1
    dispatch_semaphore_signal(self.semaphore);
}

@end

2. 直接使用GCD的串行队列,也是可以实现线程同步的

GCD串行

3. synchroized (对mutex递归锁封装)

  • @synchronized是对mutex递归锁的封装
  • 源码查看:objc4中的objc-sync.mm文件
  • @synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作
- (void)__saveMoney
{
    @synchronized([self class]) { // objc_sync_enter
        [super __saveMoney];
    } // objc_sync_exit
}

- (void)__saleTicket
{
    static NSObject *lock;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        lock = [[NSObject alloc] init];
    });
    
    @synchronized(lock) {
        [super __saleTicket];
    }
}

五. 锁方案的性能比较

(推荐用:信号量、phtread_mutex)

  • 性能从高到低排序
    os_unfair_lock(ios 10以后才可用)
    OSSpinLock(会造成反转)
    dispatch_semaphore(信号量)
    pthread_mutex(互斥、递归、条件都可以)
    dispatch_queue(DISPATCH_QUEUE_SERIAL)
    NSLock
    NSCondition
    pthread_mutex(recursive)
    NSRecursiveLock
    NSConditionLock
    @synchronized

自旋锁、互斥锁比较:

  • 什么情况使用自旋锁比较划算?
    预计线程等待锁的时间很短
    加锁的代码(临界区)经常被调用,但竞争情况很少发生
    CPU资源不紧张
    多核处理器

  • 什么情况使用互斥锁比较划算?
    预计线程等待锁的时间较长
    单核处理器
    临界区有IO操作(文件存储操作)
    临界区代码复杂或者循环量大
    临界区竞争非常激烈

六.automic:

  1. 给属性添加上atomic修饰,可以保证书新的setter和getter都是原子性操作,也就是保证getter和setter内部都是线程同步的

  2. 它并不能保证使用属性的过程是线程安全的

automic是在set方法、get方法加锁。对于数组,往里面添加数据其实是不安全的。


image.png

七.iOS中的读写安全方案:

多读单写:

同一时间,只能有1个线程进行写的操作
同一时间,允许有多个线程进行读的操作
同一时间,不允许既有写的操作,又有读的操作

多读单写实现方案

pthread_rwlock:读写锁;
dispatch_barrier_async:异步栅栏调用;

1. pthread_rwlock

等待锁的线程会进入休眠

image.png
#import 

@interface ViewController ()
@property (assign, nonatomic) pthread_rwlock_t lock;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 初始化锁
    pthread_rwlock_init(&_lock, NULL);
    
    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

2. dispatch_barrier_async

  • 这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的
  • 如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果
dispatch_barrier_async

你可能感兴趣的:(锁)