美文网首页iOS
iOS - 多线程

iOS - 多线程

作者: valentizx | 来源:发表于2019-05-10 02:15 被阅读53次
    image.png

    基本认识

    在计算机的发展长河中,为了解决充分能让 CPU 得到利用,出现了多线程的概念,其目的就是为了提高 CPU 的使用率,用多个线程去同时完成几件事情而互不干扰,这样可以大大提高程序的运行效率,用户层面的用户体验也会大大得到提升。

    iOS 中的常见多线程方案

    技术方案 简介 语言 线程生命周期
    pthread 一套通用的多线程 API 可适用于 Unix、Linux、Windows 系统 C 手动管理
    NSThread 面向对象、简单易用、可直接操作线程对象 Objective-C 手动管理
    GCD 可充分利用多核,并能取代 NSThread C 自动管理
    NSOperation 基于 GCD、面向对象 Objective-C 自动管理

    GCD

    GCD 中有两个来执行任务的函数:同步和异步。GCD 的源码在

    GCD 执行任务的方式

    用同步的方式执行任务:

    dispatch_sync(dispatch_queue_t  _Nonnull queue, ^(void)block)
    

    用异步的方式执行任务:

    dispatch_async(dispatch_queue_t  _Nonnull queue, ^(void)block)
    

    queue:队列,block:任务

    viewDidLoad: 方法中执行:

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_sync(queue, ^{
        NSLog(@"%@", [NSThread currentThread]);
    });    
    

    这表示以同步执行的方式执行 block 中的逻辑,得到结果:

    <NSThread: 0x600002d600c0>{number = 1, name = main}
    

    打印结果为主线程。

    若以异步的方式去执行任务:

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        NSLog(@"%@", [NSThread currentThread]);
    }); 
    

    得到结果:

    <NSThread: 0x600001f0f100>{number = 3, name = (null)}
    

    说明开启了子线程执行了 block 中的任务。
    执行:

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        for (int i = 0; i < 20; i ++) {
                NSLog(@"任务1 %d", i);
            }
    });
        
    dispatch_async(queue, ^{
        for (int i = 0; i < 20; i ++) {
                NSLog(@"任务2 %d", i);
            }
    });
    

    可看到结果:


    image

    两个任务交替进行。

    GCD 的队列

    GCD 的队列主要分两大类:并发队列(Concurrent Dispatch Queue)和串行队列(Serial Dispatch Queue)。

    并发队列可以让多个任务同时执行,会自动开启多个线程同时执行任务,并发功能只有在异步函数下才有效。
    创建并发队列的方式:

    dispatch_queue_t queue = dispatch_queue_create("队列名字", DISPATCH_QUEUE_CONCURRENT);
    

    串行队列是让任务一个接着一个执行,一个任务执行完后再执行下一个任务。
    创建串行队列的方式:

    dispatch_queue_t queue = dispatch_queue_create("队列名字", DISPATCH_QUEUE_SERIAL);
    

    同步异步主要决定的是能不能开启新线程,同步表示在当前线程中执行任务,不具备开启新线程的能力,异步表示在新的线程中执行任务,具备开新线程的能力,但是,不一定真的开启新线程。

    并发和串行主要影响任务的执行方式,并发指多个任务同时执行,串行指任务顺序执行。

    各种队列执行效果如下:

    并发队列 串行队列 主队列
    同步 a. 不会开启新线程 b. 串行执行任务 a. 不会开启新线程 b. 串行执行任务 a. 不会开启新线程 b. 串行执行任务
    异步 b. 会开启新线程 b. 并发执行任务 b. 会开启新线程 b. 串行执行任务 a. 不会开启新线程 b. 串行执行任务

    ⚠️ 使用 sync 函数往当前串行队列中添加任务,会卡在当前的串行队列中,产生死锁。

    现在思考这样一个问题,在 viewDidLoad 中添加如下代码运行会产生什么结果?:

    NSLog(@"Task 1");
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    dispatch_block_t block1 = ^{
        NSLog(@"Task 3");
    };
    dispatch_block_t block0 = ^{
        NSLog(@"Task 2");
        dispatch_sync(queue, block1);
        NSLog(@"Task 4");
    };
    dispatch_async(queue, block0);
    NSLog(@"Task 5");
    

    结果就是输出:

    Task 1
    Task 5
    Task 2
    

    然后产生死锁,程序挂掉。
    首先我们知道 queue 是串行队列,block0block1 都会在这个串行队列中执行任务,由于 block0 是在异步函数中执行的,那么 Task 5 会先打印,那么 queue 执行任务的原则就是,一个任务执行完执行下一个任务,很明显在打印 Task 2 后任务还没有执行完就要去执行 Task 3 的打印,所以在这里会造成死锁。若 queue 是并发队列则不会产生死锁。

    dispatch_queue_global_t dispatch_get_global_queue(long identifier, unsigned long flags) 全局队列是并发队列,全局只有一份。

    再来看个例子,在 viewDidLoad 中加入:

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"Task 1");
        [self performSelector:@selector(printTask2) withObject:nil afterDelay:0];
        NSLog(@"Task 3");   
    });
    

    printTask2 的逻辑为:

    - (void)printTask2 {
        NSLog(@"Task 2");
    }
    

    运行结果为:

    Task 1
    Task 3
    

    那么为什么没有打印 Task 2?
    这是因为 performSelector: withObject: afterDelay: 的本质是在 RunLoop 中添加定时器,而所在函数为异步函数 dispatch_async,也就意味着,添加定时器是在子线程中进行的,但是,子线程默认是没有 RunLoop 的,也就是说,添加的定时器是无效的,没有人处理它,所以 Task 2 永远不会执行。解决办法就是:

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"Task 1");
        [self performSelector:@selector(printTask2) withObject:nil afterDelay:0];
        NSLog(@"Task 3");
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];   
    });
    

    或者:

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"Task 1");
        [self performSelector:@selector(printTask2) withObject:nil];
        NSLog(@"Task 3");   
    });
    
    

    performSelector: withObject: afterDelay:performSelector: withObject: 虽然长相差不多,但是实现是大有不同的,前者是通过 RunLoop 处理计时器,后者的本质就是 runtime 机制的消息发送机制。

    我们在 GNUstep 开源的 Objective-C 源码中发现线索:

    - (void) performSelector: (SEL)aSelector
              withObject: (id)argument
              afterDelay: (NSTimeInterval)seconds
    {
      NSRunLoop     *loop = [NSRunLoop currentRunLoop];
      GSTimedPerformer  *item;
    
      item = [[GSTimedPerformer alloc] initWithSelector: aSelector
                             target: self
                           argument: argument
                              delay: seconds];
      [[loop _timedPerformers] addObject: item];
      RELEASE(item);
      [loop addTimer: item->timer forMode: NSDefaultRunLoopMode];
    }
    

    GNUStep 开源的源码是自己实现的,具有相当高的参考价值,其他部分的源码可以配合 Apple 开源的 Core Foundation 源码研究。源码地址在

    GCD 队列组的使用

    在开发中,我们通常有这样的需求:异步执行任务 1、2,这两个任务都执行完毕再去执行任务 3。那么遇到这种问题,我们就可以通过队列组来实现。如:

    dispatch_group_t group = dispatch_group_create(); // 创建队列组
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT); // 创建并发队列
    // 任务1
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 3; i ++) {
            NSLog(@"【任务1】%d", i);
        }
    });
    // 任务2
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 3; i ++) {
            NSLog(@"【任务1】%d", i);
        }
    });    
    // 任务3
    dispatch_group_notify(group, queue, ^{
        // 回到主线程就用主队列,异步做事情就用全局队列或者手动创建异步队列
        dispatch_group_async(group, dispatch_get_main_queue(), ^{
            for (int i = 0; i < 3; i ++) {
                NSLog(@"【任务3】%d", i);
            }
        });
    });
    

    运行结果为:


    image

    dispatch_group_notify 函数可以保证,前面的异步任务执行完毕后才会执行 dispatch_group_notify 中的 block。

    多线程的安全隐患

    最典型的就是资源共享问题,1 块资源可能被多个线程享用,出现资源抢夺的可能。这个资源可能是一个对象、一个变量、一个文件等。

    如果是计算机专业的朋友对 “生产者-消费者” 或者 “银行存款取款” 的经典例子一定不陌生,这两种情况都是经典的线程资源抢夺案例。也就是两个任务在不同时刻访问了同一个资源。

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

    image

    在线程 A 对 17 进行操作的时候,先对 A 加锁,然后读取数据进行加法运算,然后写入数据,解锁,当线程 B 也要进行运算的时候,同样先加锁,然后取数进行加法操作,最后写入数据,解锁。加锁的目的就是保证,当前的资源只有一个线程能访问。

    iOS 中的线程同步方案

    • OSSpinLock
    • os_unfair_lock
    • pthread_mutex
    • dispatch_semphore
    • dispatch_queue(DISPATCH_QUEUE_SERIAL)
    • NSLock
    • NSRecursiveLock
    • NSCondition
    • NSConditionLock
    • @synchronized

    在下抢票例子上进行上述同步方案的逐一测试:

    @interface ViewController ()
    
    @property(assign, nonatomic) NSInteger tickets;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.tickets = 30;
        [self test];
    }
    
    - (void)test {
        
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_async(queue, ^{
            for (int i = 0; i < 5; i++) {
                [self getTicket];
            }
        });
        
        dispatch_async(queue, ^{
            for (int i = 0; i < 5; i++) {
                [self getTicket];
            }
        });
        
        dispatch_async(queue, ^{
            for (int i = 0; i < 5; i++) {
                [self getTicket];
            }
        });
    }
    
    - (void)getTicket {
        
        NSInteger tmp = self.tickets;
        self.tickets = -- tmp;
        NSLog(@"还剩 %ld 张票,%@", (long)self.tickets, [NSThread currentThread]);
    }
    
    @end
    

    抢票结果:


    image

    发生资源抢夺的部分都在 getTicket 函数中,所以加锁的逻辑也是在这里。

    OSSpinLock

    OSSpinLock 叫做“自旋锁”,等待锁的线程会处于忙等的状态,一直占用 CPU 的资源。处理上述抢票问题:

    - (void)getTicket {
        // 加锁
        OSSpinLockLock(&_lock);
        NSInteger tmp = self.tickets;
        self.tickets = -- tmp;
        NSLog(@"还剩 %ld 张票,%@", (long)self.tickets, [NSThread currentThread]);
        // 解锁
        OSSpinLockUnlock(&_lock);
    }
    

    声明一个全局的一把锁,然后初始化。

    OSSpinLock 的使用需要导入 libkern/OSAtomic.h
    自旋锁的初始化为:lock = OS_SPINLOCK_INIT

    OSSpinLock 的原理是,新来的线程会判断 _lock 是否加锁,如果加锁,则处于等待状态,等待上一个任务执行完毕,上一个任务执行完毕,新来的这个线程会立即加锁,执行自己的逻辑。

    运行程序结果正常。[不贴图了]

    但是目前该 OSSpinLock 可能出现优先级反转的问题,所以不再安全:假如有三个线程 A、B、C,其中 A 的优先级最高,B 的最低,在 A 执行任务的时候,发现资源已经被 B 在某个时间点上锁,所以 A 会一直处于等待状态,由于 A 的优先级最高,可能会导致 CPU 一直分配时间给 A,导致 B、C 不能执行任何操作,也就无法解锁。

    os_unfair_lock

    OSSpinLock 已经被 os_unfair_lock 替代,从底层调用来看,等待 os_unfair_lock 的线程会处于休眠状态,而不是忙等。处理上述抢票问题:

    - (void)getTicket {
        
        // 加锁
        os_unfair_lock_lock(&_lock);
        NSInteger tmp = self.tickets;
        self.tickets = -- tmp;
        NSLog(@"还剩 %ld 张票,%@", (long)self.tickets, [NSThread currentThread]);
        // 解锁
        os_unfair_lock_unlock(&_lock);
    }
    

    声明一个全局的一把锁,然后初始化。

    os_unfair_lock 的使用需要导入 os/lock.h
    自旋锁的初始化为:lock = OS_UNFAIR_LOCK_INIT

    运行程序结果正常。[不贴图了]

    pthread_mutex

    mutex 一般称作互斥锁,等待锁的线程会处于休眠状态,处理上述抢票问题:

    - (void)getTicket {
        // 加锁
        pthread_mutex_lock(&_mutex);
        NSInteger tmp = self.tickets;
        self.tickets = -- tmp;
        NSLog(@"还剩 %ld 张票,%@", (long)self.tickets, [NSThread currentThread]);
        // 解锁
        pthread_mutex_unlock(&_mutex);
    }
    

    声明一个全局的一把锁,然后初始化。

    pthread_mutex 的使用需要导入 pthread.h
    互斥锁的初始化步骤相对麻烦,还要对锁进行属性的设置:

    // 初始化属性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    //设置属性
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
    // 初始化锁
    pthread_mutex_init(&_mutex, &attr);
    // 销毁属性
    pthread_mutexattr_destroy(&attr);
    

    锁的属性除了 PTHREAD_MUTEX_NORMAL 还有以下:

    #define PTHREAD_MUTEX_NORMAL 0
    #define PTHREAD_MUTEX_ERRORCHECK 1
    #define PTHREAD_MUTEX_RECURSIVE 2// 递归锁,表示同一个线程可以对一个资源重复加锁
    #define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL

    运行程序结果正常。[不贴图了]

    另外,需要在 dealloc 函数中调用 pthread_mutex_destroy(&_mutex) 销毁锁。

    这里自旋锁和互斥锁的区别就是:在自旋锁机制中,如果自旋锁被别的执行单元加锁,则新来的调用者会一直处于循环等待的状态,等待持有者解锁。而互斥锁则不同,新来的调用者发现互斥锁被加锁,则会进入阻塞的状态。并且将这个调用者放入等待的消息队列等待调度。

    假如,两个子线程存在依赖关系,那么可通过条件来解决:

    // 初始化条件
    int pthread_cond_init(
            pthread_cond_t * __restrict,
            const pthread_condattr_t * _Nullable __restrict)
            __DARWIN_ALIAS(pthread_cond_init);
    // 等待方:
    int pthread_cond_wait(pthread_cond_t * __restrict,
            pthread_mutex_t * __restrict) __DARWIN_ALIAS_C(pthread_cond_wait); // 等待,休眠状态放开锁,被唤醒后加锁
    // 被依赖方:
    int pthread_cond_signal(pthread_cond_t *); // 唤醒,一对一唤醒
    
    // 被依赖方:
    int pthread_cond_broadcast(pthread_cond_t *); // 唤醒,一次唤醒多个线程
    

    NSLock、NSRecursiveLock

    NSLock 是对 mutex 的普通锁封装,NSRecursiveLock 是对 mutex 的递归锁封装。处理上述抢票问题:

    - (void)getTicket {
        // 加锁
        [self.lock lock];
        NSInteger tmp = self.tickets;
        self.tickets = -- tmp;
        NSLog(@"还剩 %ld 张票,%@", (long)self.tickets, [NSThread currentThread]);
        // 解锁
        [self.lock unlock];
    }
    
    

    NSRecursiveLock 使用方法和 NSLock 基本一致。

    在互斥锁中,还有个条件的概念,同样的,在对应的 Objective-C 互斥锁中,也有条件的概念,那就是 NSCondition。对应 API 和 mutex 的一样。

    运行程序结果正常。[不贴图了]

    NSConditionLock

    NSConditionLock 又称条件锁,是对 NSConditionLock 的进一步封装,可以设置具体的值。

    使用方法几乎和 NSLock 一样,并且根据条件值来加锁,使用起来更方便。详见文档

    dispatch_queue(DISPATCH_QUEUE_SERIAL)

    直接使用 GCD 的串行队列,也可以实现线程同步。处理上述抢票问题:

    - (void)getTicket {  
        dispatch_sync(self.queue, ^{
            NSInteger tmp = self.tickets;
            self.tickets = -- tmp;
            NSLog(@"还剩 %ld 张票,%@", (long)self.tickets, [NSThread currentThread]);
        });
    }
    

    queue 的初始化为:self.queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL)

    dispatch_semphore

    semphore 为信号量,信号量的初始值,可以用来控制线程并发访问的最大数量,为 1 表示只允许一条线程访问资源,保证线程同步。处理上述抢票问题:

    - (void)getTicket {
        dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
        NSInteger tmp = self.tickets;
        self.tickets = -- tmp;
        NSLog(@"还剩 %ld 张票,%@", (long)self.tickets, [NSThread currentThread]);
        dispatch_semaphore_signal(self.semaphore);
    }
    

    信号量的初始化为:self.semaphore = dispatch_semaphore_create(int value),若解决抢票问题,则为 1。
    dispatch_semaphore_wait 若信号量的值 <= 0,当前线程就会进入休眠状态,若 > 0,就减 1,然后执行后面的代码。
    dispatch_semaphore_signal 让信号量的值加 1。

    运行程序结果正常。[不贴图了]

    @synchronized

    @synchronized 是对 mutex 的封装。也是最简单的方案,处理上述抢票问题:

    - (void)getTicket {  
        @synchronized (self) {
            NSInteger tmp = self.tickets;
            self.tickets = -- tmp;
            NSLog(@"还剩 %ld 张票,%@", (long)self.tickets, [NSThread currentThread]);
        }  
    }
    

    @synchronized (对象),该对象必须是唯一不会变的,形如前面的锁,都是全局唯一的,self 表示当前控制器对象,可以满足需求,但是其他情况下未必满足,所以这个对象的选择还需要视情况而定。

    我们可以在源码中看到:

    int objc_sync_enter(id obj)
    {
        int result = OBJC_SYNC_SUCCESS;
    
        // obj 是当前对象
        if (obj) {
            SyncData* data = id2data(obj, ACQUIRE); // 传入对象得到 SyncData 对象
            assert(data);
            data->mutex.lock(); // 从 SyncData 对象中取出锁然后加锁,从这里也可以看出,一个对象对应一把锁
        } else {
            if (DebugNilSync) {
                _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
            }
            objc_sync_nil();
        }
        return result;
    }
    

    SyncData 的形式:

    typedef struct alignas(CacheLineSize) SyncData {
        struct SyncData* nextData;
        DisguisedPtr<objc_object> object;
        int32_t threadCount;
        recursive_mutex_t mutex; // 递归锁
    } SyncData;
    

    苹果并不推荐用 @synchronized 来加锁,会损耗很多性能。

    运行程序结果正常。[不贴图了]

    以上线程同步方案性能对比

    性能由高到低:
    a. os_unfair_lock // iOS 10 才开始支持
    b. OSSpinLock
    c. dispatch_semaphore // 推荐
    d. pthread_mutex // 跨平台
    e. dispatch_queue(DISPATCH_QUEUE_SERIAL)
    f. NSLock
    g. NSCondition
    h. pthread_mutex(recursive)
    i. NSRecursiveLock
    j. NSConditionLock
    k. @synchronized

    Q. 自旋锁和互斥锁什么情况下选择对应的锁?
    A. 预计线程等待锁的时间很短、加锁的代码(临界区)经常被调用,但竞争情况很少发生、CPU 资源不紧张、多核处理器的情况下选择自旋锁,反之临界区有 IO 操作、单核处理器、预计线程等待锁的时间比较长、临界区逻辑复杂循环量大的情况下用互斥锁。

    atomic

    atomic 用于保证属性 setter/getter 的原子操作,相当于在 setter/getter 内部增加了线程同步锁。但它并不能保证使用属性的过程是一定线程安全的。
    objc-accessors.mm 源码中有体现,其 setter 方法为:

    static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
    {
        ...
        // 判断是否具有原子性,也就是是否是 atomic 修饰
        if (!atomic) {
            // 不是的话,直接赋值,也就是 noatomic
            oldValue = *slot;
            *slot = newValue;
        } else {
            // 是的话先获得锁
            spinlock_t& slotlock = PropertyLocks[slot];
            // 加锁
            slotlock.lock();
            oldValue = *slot;
            *slot = newValue;
            // 解锁        
            slotlock.unlock();
        }
    
        objc_release(oldValue);
    }
    

    getter 方法为:

    id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
        ...
        if (!atomic) return *slot;
            
        // 加锁
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        id value = objc_retain(*slot);
        // 解锁
        slotlock.unlock();
        
        return objc_autoreleaseReturnValue(value);
    }
    

    为什么说 atomic 修饰的不一定是线程安全的?因为我们看到,只有对属性的 setter/getter 操作才有加锁解锁的操作,那么对于其他的操作:如数组添加元素、删除元素以及其他操作就不具备加锁解锁的操作,会导致线程不安全。

    在进行 iOS 开发的过程中,通常是不使用 atomic 关键字修饰属性的,因为会消耗性能,在 macOS 开发的过程中使用的较多。

    iOS 读写安全方案

    现有以下场景:

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

    也就是这样的例子:

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        for (int i = 0; i < 10; i ++) {
            [[[NSThread alloc] initWithTarget:self selector:@selector(read) object:nil] start];
            [[[NSThread alloc] initWithTarget:self selector:@selector(write) object:nil] start];
        }
    }
    
    - (void)read {
        NSLog(@"%s", __func__);
    }
    
    - (void)write {
         NSLog(@"%s", __func__);
    }
    
    @end
    

    上面的场景就是典型的“多读单写”,经常用于文件数据的读写操作,iOS 中的实现方案有:

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

    pthread_rwlock

    等待锁的线程会进入休眠状态。
    其 API 有如下:

    pthread_rwlock_t lock;
    // 初始化锁
    pthread_rwlock_init(&lock, NULL);
    // 读-加锁
    pthread_rwlock_rdlock(&lock);
    // 读-尝试加锁
    pthread_rwlock_tryrdlock(&lock);
    // 写-加锁
    pthread_rwlock_wrlock(&lock);
    // 写-尝试加锁
    pthread_rwlock_trywrlock(&lock);
    // 解锁
    pthread_rwlock_unlock(&lock);
    // 销毁锁
    pthread_rwlock_destroy(&lock);
    

    解决上述问题的处理为:

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        // 初始化锁
        pthread_rwlock_init(&_lock, NULL);
    
        for (int i = 0; i < 10; i ++) {
            [[[NSThread alloc] initWithTarget:self selector:@selector(read) object:nil] start];
            [[[NSThread alloc] initWithTarget:self selector:@selector(write) object:nil] start];
        }
    }
    
    - (void)read {
        
        pthread_rwlock_rdlock(&_lock);
        NSLog(@"%s", __func__);
        pthread_rwlock_unlock(&_lock);
    }
    
    - (void)write {
        
        pthread_rwlock_wrlock(&_lock);
        NSLog(@"%s", __func__);
        pthread_rwlock_unlock(&_lock);
    }
    
    @end
    

    dispatch_barrier_async

    这个函数传入的并发队列必须是自己通过 dispatch_queue_create 创建的。
    解决上述问题的处理为:

    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        _queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_SERIAL);
    
        for (int i = 0; i < 10; i ++) {
            [[[NSThread alloc] initWithTarget:self selector:@selector(read) object:nil] start];
            [[[NSThread alloc] initWithTarget:self selector:@selector(write) object:nil] start];
        }
    }
    
    - (void)read {
        // 读
        dispatch_async(_queue, ^{
            NSLog(@"%s", __func__);
        });
        
    }
    
    - (void)write {
        // 写
        dispatch_barrier_async(_queue, ^{
            NSLog(@"%s", __func__);
        });
    }
    @end
    

    若传入的队列是串行队列或者全局并发队列,则 dispatch_barrier_async 的效果等同于 dispatch_async。

    相关文章

      网友评论

        本文标题:iOS - 多线程

        本文链接:https://www.haomeiwen.com/subject/lcgnoqtx.html