iOS-多线程

iOS 线程同步

2019-07-10  本文已影响0人  Dan1els

多线程相关的概念

Lock Ownership.png

线程同步方案

要保证线程安全,就必须要线程同步,而在iOS中线程同步的方案有:

原子操作

在 iOS 中,原子操作可以保证属性在单独的 setter 或者 getter 方法中是线程安全的,但是不能保证多个线程对同一个属性进行读写操作时,可以得到预期的值,也就是原子操作不保证线程安全,例如:

// 共享资源name
@property (copy, atomic) NSString *name;
// 初始化
self.name = @"A";

// 线程2进行写操作,是原子操作,不可以分割的
self.name = @"B";

// 线程3进行写操作,是原子操作,不可以分割的
self.name = @"C";

// 线程4进行读操作,是原子操作,不可以分割的,但这时候存在三种可能
self.name == @"A";
self.name == @"B";
self.name == @"C";

Objective-C 的原子操作

在 Objective-C 中,可以在设置属性的时候,使用 atomic 来设置原子属性,保证属性 settergetter 的原子性操作,底层是在 gettersetter 内部使用 os_unfair_lock 加锁

@property (copy, atomic) NSString *name;

Swift 的原子操作

在 Swift 中,原生没有提供原子操作,可以使用 DispatchQueue 的同步函数来达到同样的效果

class Person {
  // 创建一个队列
  let queue = DispatchQueue(label: "Person")

  // 私有化需要原子操作的属性
  private var _name: String = ""

  // 向外界暴露的属性,把它的 get 和 set 方法都设置为同步操作,实际上是对 _name 进行操作,这样就可以间接的对 name 进行原子操作
  var name: String {
      get {
          return queue.sync {
              _name
          }
      }
      set {
          return queue.sync {
              _name = newValue
          }
      }
  }
}

信号量(Semaphore)

// 初始化一个值为 5 的信号量,可以同时有 5 条线程访问临界区,其他线程则进入睡眠状态
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);


// wait
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

// 临界区...

// signal
dispatch_semaphore_signal(semaphore);

GCD串行队列

dispatch_queue_t queue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
    // 临界区...
});

OSSpinLock

#import <libkern/OSAtomic.h>

OSSpinLock lock = OS_SPINLOCK_INIT;

// 加锁
OSSpinLockLock(&lock);

// 临界区...

// 解锁
OSSpinLockUnlock(&lock);

os_unfair_lock

#import <os/lock.h>

os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;


// 加锁
os_unfair_lock_lock(&lock);

// 临界区...

// 解锁
os_unfair_lock_unlock(&lock);

互斥锁

pthread_mutex

pthread 表示 POSIX thread,是 POSIX 标准的 unix 多线程库,定义了一组跨平台的线程相关的API。pthread_mutex 是一种用 C 语言实现的互斥锁,有单一的拥有者

#import <pthread.h>

// 静态初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;


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


// 加锁
pthread_mutex_lock(&mutex);

// 临界区...

// 解锁
pthread_mutex_unlock(&mutex);

// 销毁锁
pthread_mutex_destroy(&_mutex);

NSLock

NSLock *lock = [[NSLock alloc] init];

// 加锁
[lock lock];

// 临界区...

// 解锁
[lock unlock];

递归锁

递归锁是一种特殊互斥锁。递归锁允许单个线程在释放之前多次获取锁,其他线程保持睡眠状态,直到锁的所有者释放锁的次数与获取它的次数相同。递归锁主要在递归迭代中使用,但也可能在多个方法需要单独获取锁的情况下使用。

pthread_mutex(Recursive)

pthread_mutex 支持递归锁,只要把 attr 的类型改成 PTHREAD_MUTEX_RECURSIVE 即可,它有单一的拥有者

#import <pthread.h>

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


// 加锁
pthread_mutex_lock(&_mutex);


// 临界区...
// 在同一个线程中可以多次获取锁

// 解锁
pthread_mutex_unlock(&_mutex);


// 销毁锁
pthread_mutex_destroy(&_mutex);

NSRecursiveLock

NSRecursiveLock 是以 Objective-C 对象的形式对 pthread_mutex(Recursive) 的封装,它有单一的拥有者

NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];

// 加锁
[lock lock];

// 临界区...
// 在同一个线程中可以多次获取锁

// 解锁
[lock unlock];

@synchronized

@synchronized(lock) {
    // 临界区...
}

条件锁

条件锁是一种特殊互斥锁,需要条件变量(condition variable) 来配合。条件变量有点像信号量,提供了线程阻塞与信号机制,因此可以用来阻塞某个线程,并等待某个数据就绪,随后唤醒线程。条件锁是为了解决 生产者-消费者模型

pthread_mutex – 条件锁

pthread_mutex 配合 pthread_cond_t,可以实现条件锁,其中 pthread_cond_t 没有拥有者

#import <pthread.h>

// 初始化锁
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &NULL);
// 销毁属性
pthread_mutexattr_destroy(&attr);

// 初始化条件变量
pthread_cond_t cond;
pthread_cond_init(&cond, NULL);

// 消费者
- (void)remove {
    // 加锁
    pthread_mutex_lock(&mutex);

    // 先判断某个条件
    if (self.data.count == 0) {
        // 如果不满足条件,则等待,具体是释放锁,用条件变量来阻塞当前线程
        // 当条件满足的时候,条件变量唤醒线程,再用原来的锁加锁
        pthread_cond_wait(&cond, &mutex);
    }

    [self.data removeLastObject];


    // 解锁
    pthread_mutex_unlock(&mutex);
}


// 生产者
- (void)add
{
    // 加锁
    pthread_mutex_lock(&mutex);
    

    [self.data addObject:@"Test"];
    
    // 信号
    // 条件变量唤醒阻塞的线程
    pthread_cond_signal(&cond);
    // 广播
    // pthread_cond_broadcast(&cond);
    
    // 解锁
    pthread_mutex_unlock(&mutex);
}


// 销毁
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);

NSCondition

NSCondition 是以 Objective-C 对象的形式对 pthread_mutexpthread_cond_t 进行了封装,NSCondition 没有拥有者

NSCondition *condition = [[NSCondition alloc] init];

// 消费者
- (void)remove
{
    [condition lock];

    
    if (self.data.count == 0) {
        // 如果不满足条件,则等待,具体是释放锁,用条件变量来阻塞当前线程
        // 当条件满足的时候,条件变量唤醒线程,再用原来的锁加锁
        [condition wait];
    }
    
    [self.data removeLastObject];
    
    [condition unlock];
}


// 生产者
- (void)add
{
    [condition lock];
    
    
    [self.data addObject:@"Test"];
    
    // 信号
    // 条件变量唤醒阻塞的线程
    [condition signal];
    
    
    [condition unlock];
}

NSConditionLock

NSConditionLock 是对 NSCondition 的进一步封装,可以设置条件变量的值。通过改变条件变量的值,可以使任务之间产生依赖关系,达到使任务按照一定的顺序执行,它有单一的拥有者(不确定)

// 初始化设置条件变量的为1,如果不设置则默认为0
NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:1];


// 消费者
- (void)remove
{
    // 当条件变量为2的时候加锁,否则等待
    [lock lockWhenCondition:2];
    
    [self.data removeLastObject];
    
    // 直接解锁
    [lock unlock];
}


// 生产者
- (void)add
{
    // 直接加锁
    [lock lock];
    
    
    [self.data addObject:@"Test"];
    
    
    // 解锁并让条件变量为2
    [lock unlockWithCondition:2];
}

读写锁

读写锁是一种特殊互斥锁,提供"多读单写"的功能,多个线程可以同时对共享资源进行读取,但是同一时间只能有一条线程对共享资源进行写入

pthread_rwlock

pthread_rwlock 有多个拥有者

#import <pthread.h>

// 初始化
pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER;


// 读操作
- (void)read {
    pthread_rwlock_rdlock(&lock);

    // 临界区...
  
    pthread_rwlock_unlock(&lock);
}

// 写操作
- (void)write
{
    pthread_rwlock_wrlock(&lock);
    
    // 临界区...
    
    pthread_rwlock_unlock(&lock);
}

// 销毁
- (void)dealloc
{
    pthread_rwlock_destroy(&lock);
}

GCD 的 Barrier函数

dispatch_queue_t queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);


dispatch_async(queue, ^{
    // 读
});

dispatch_async(queue, ^{
    // 读
});


dispatch_barrier_async(queue, ^{
    // 写
});

dispatch_async(queue, ^{
    // 读
});

性能

性能从高到底分别是:

总结:

参考文章

苹果官方文档

白夜追凶,揭开iOS锁的秘密

起底多线程同步锁(iOS)

深入理解 iOS 开发中的锁

上一篇 下一篇

猜你喜欢

热点阅读