iOS线程安全与线程锁的使用

这里写图片描述

线程安全

多线程操作共享数据不会出现想不到的结果就是线程安全的,否则,是线程不安全的。

举个例子:

NSInteger total = 0;
- (void)threadNotSafe {
    for (NSInteger index = 0; index < 3; index++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            total += 1;
            NSLog(@"total: %ld", total);
            total -= 1;
            NSLog(@"total: %ld", total);
        });
    }
}
//第一次输出:
total: 1
total: 3
total: 2
total: 2
total: 1
total: 0
//第二次输出
total: 1
total: 2
total: 3
total: 2
total: 1
total: 0

NSInteger total = 0;
NSLock *lock = [NSLock new];
- (void)threadSafe {
    for (NSInteger index = 0; index < 3; index++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [lock lock];
            total += 1;
            NSLog(@"total: %ld", total);
            total -= 1;
            NSLog(@"total: %ld", total);
            [lock unlock];
        });
    }
}
//第一次输出
total: 1
total: 0
total: 1
total: 0
total: 1
total: 0
//第二次输出
total: 1
total: 0
total: 1
total: 0
total: 1
total: 0

可以明显看出来第二种是线程安全的对共享数据进行操作。

线程锁

上面的例子中使用了NSLock来对线程添加了“锁”,使多线程的执行顺序按照我们的意愿进行,常见的锁有以下:(排名不分先后)
其中我常用的是前三种锁(能满足我的大部分需求)

1. @synchronized:

代码简单 加解锁的效率最高 一个对象层面的锁,锁住了整个对象,底层使用了互斥递归锁来实现。

NSObject *object = [NSObject new];
@synchronized(object) {
  //需要执行的代码
}
//对象内部可以使用
@synchronized(self) {
}

2. dispatch_semaphore:

GCD提供的线程锁 先创建一个信号量不为0的信号量,signal之后会先将信号量减一,并判断是否大于等于0,如果是,则返回0,并继续执行后续代码,否则,使线程进入睡眠状态,让出cpu时间。直到信号量大于0或者超时,则线程会被重新唤醒执行后续操作。

dispatch_semaphore_t lock = dispatch_semaphore_create(1);    //传入的参数必须大于或者等于0,否则会返回Null
long wait = dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);    //wait = 0,则表示不需要等待,直接执行后续代码;wait != 0,则表示需要等待信号或者超时,才能继续执行后续代码。lock信号量减一,判断是否大于0,如果大于0则继续执行后续代码;lock信号量减一少于或者等于0,则等待信号量或者超时。
//需要执行的代码
long signal = dispatch_semaphore_signal(lock);    //signal = 0,则表示没有线程需要其处理的信号量,换句话说,没有需要唤醒的线程;signal != 0,则表示有一个或者多个线程需要唤醒,则唤醒一个线程。(如果线程有优先级,则唤醒优先级最高的线程,否则,随机唤醒一个线程。)

3. NSLock:

NSLock在内部封装了一个 pthread_mutex(下文),属性为 PTHREAD_MUTEX_ERRORCHECK。

NSLock *lock = [NSLock new];
[lock lock];
//需要执行的代码
[lock unlock];

4. pthread_mutex:

pthread_mutex表示互斥锁,和信号量的实现原理类似,也是阻塞线程并进入睡眠,需要进行上下文切换。

pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);

pthread_mutex_t lock;
pthread_mutex_init(&lock, &attr);    //设置属性

pthread_mutex_lock(&lock);    //上锁
//需要执行的代码
pthread_mutex_unlock(&lock);    //解锁

5. NSCondition:

NSCondition封装了一个互斥锁和条件变量。互斥锁保证线程安全,条件变量保证执行顺序。

NSCondition *lock = [NSCondition new];
[lock lock];
//需要执行的代码
[lock unlock];

6. pthread_mutex(recursive):

pthread_mutex锁的一种,属于递归锁。一般一个线程只能申请一把锁,但是,如果是递归锁,则可以申请很多把锁,只要上锁和解锁的操作数量就不会报错。

pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

pthread_mutex_t lock;
pthread_mutex_init(&lock, &attr);    //设置属性

pthread_mutex_lock(&lock);    //上锁
//需要执行的代码
pthread_mutex_unlock(&lock);    //解锁

7. NSRecursiveLock:

递归锁,pthread_mutex(recursive)的封装。

NSRecursiveLock *lock = [NSRecursiveLock new];
[lock lock];
//需要执行的代码
[lock unlock];

8. NSConditionLock:

NSConditionLock借助 NSCondition 来实现,本质是生产者-消费者模型。

NSConditionLock *lock = [NSConditionLock new];
[lock lock];
//需要执行的代码
[lock unlock];

9. OSSpinLock:

自旋锁的实现原理比较简单,就是死循环。当a线程获得锁以后,b线程想要获取锁就需要等待a线程释放锁。在没有获得锁的期间,b线程会一直处于忙等的状态。如果a线程在临界区的执行时间过长,则b线程会消耗大量的cpu时间,不太划算。所以,自旋锁用在临界区执行时间比较短的环境性能会很高。

#import OSSpinLock lock = OS_SPINLOCK_INIT;
OSSpinLockLock(&lock);
//需要执行的代码
OSSpinLockUnlock(&lock);
//OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock)
//苹果在OSSpinLock注释表示被废弃,改用不安全的锁替代

你可能感兴趣的:(iOS线程安全与线程锁的使用)