美文网首页
iOS下的多线程

iOS下的多线程

作者: buding_ | 来源:发表于2024-06-23 11:31 被阅读0次

    iOS下的多线程方案有:

    pthread
    • 一套通用的多线程API
    • 适用于Uinx/Linux/Windows等系统
    • 跨平台/可移植
    • 程序员管理生命周期
    • 语言:C
    NSThread
    • 使用更加面向对象
    • 简单易用,可直接操作线程对象
    • 程序员管理生命周期
    • 语言:OC
    GCD
    • 旨在替代NSThread等线程技术
    • 充分利用设备的多核
    • 自动管理生命周期
    • 语言:C
    NSOperation
    • 基于GCD
    • 比GCD多了一些更简单实用的功能
    • 使用更加面向对象
    • 自动管理生命周期
    • 语言:OC

    同步、异步、串行、并发

    同步和异步主要影响:能不能开辟新的线程
    • 同步:在当前线程中执行任务,不具备开辟线程能力,如dispatch_sync
    • 异步: 在新的线程中执行任务,具备开辟新线程的能力,如dispatch_async
    并发和串行主要影响:任务的执行方式
    • 并发:多个任务并发(同时)执行,如 dispatch_get_global_queue()
    • 串行:一个任务执行完后,再执行下一个任务,如 dispatch_get_main_queue, dispatch_queue_create(“”, DISPATCH_QUEUE_SERIAL)
    //以下代码是在主线程中执行的,会不会产生死锁?
    NSLog(@“”任务1”);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@“”任务2”);
    });
    NSLog(@“”任务3”);
    
    — > 会产生死锁,
    因为当前队列(主队列,属于串行队列),正在执行该函数, 以及 sync的任务2; 根据队列的FIFO特性,要执行任务2 必须先执行完当前函数
    而当前函数是的任务2 通过dispatch_sync 指定了同步执行(dispatch_sync会立马在当前线程执行任务),要执行到任务3 必须先执行任务2;
    故导致了 任务3 和 任务2 在互相等待,形成死锁
    
    NSLog(@“”任务1”);
    dispatch_queue_t queue = dispatch_queue_create(“q”, DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@“”任务2”);
        dispatch_sync(queue, ^{
            NSLog(@“”任务3”);
        });
        NSLog(@“”任务4”);
    });
    NSLog(@“”任务5”);
    - > 会造成死锁
    Queue是串行队列,队列中先添加了async中的任务2-4,然后再添加sync任务3;
    那么要执行完任务4,必须先执行到任务3; 而任务3所在的队列位置又在任务2-4之后, 故是在互相等待 形成死锁
    

    总结:
    使用sync函数往当前串行队列中添加任务,则会造成死锁

    - (void)test {
        NSLog(@“”任务2”);
    }
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async_queue(queue, ^{
        NSLog(@“”任务1”);
        [self performSelector:@select(test) withObject:nil afterDelay:.0];
        NSLog(@“”任务3”);
    });
    打印结果是:
    NSLog(@“”任务1”);
    NSLog(@“”任务3”);
    
    - > 任务2并不会执行,因为performSelector:withObject:afterDelay的本质是往RunLoop中添加定时器,
    而子线程默认没有启动RunLoop,故导致任务2并没有执行
    
    改进方式:
    dispatch_async_queue(queue, ^{
        NSLog(@“”任务1”);
        [self performSelector:@select(test) withObject:nil afterDelay:.0];
        NSLog(@“”任务3”);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    });
    
    
    

    iOS下线程的同步方案

    OSSpinLock
    • OSSpinLock是自旋锁,等待锁的线程会处于忙等(busy-wati)状态,一直占用着CPU资源
    • 其效率是比较高的,适用于等待时间比较短的场景,因为线程的睡眠与唤醒也是需要消耗性能的
    #import <libkern/OSAtomic.h>
    
    //初始化
    OSSpinLock lock = OS_SPINLOCK_INIT;
    //加锁
    OSSpinLockLock(&lock);
    //解锁
    OSSpinLockUnlock(&lock);
    
    一般使用锁时,都会持有这个锁才能对线程进行管理,如设为静态
    static OSSpinLock lock = OS_SPINLOCK_INIT;
    
    Ps:static是静态初始化(在编译时候就确定),调用函数的初始化就不可以在static中写,一般通过dispatch_once
    static Person *p = nil;
    dispatch_once(&onceToken,
        p = [Person instance];
    }
    
    

    由于OSSpinLock存在不安全的问题,有可能导致优先级反转,故apple不推荐使用

    什么是优先级反转?
    例如thread1-高优先级 thread2-低优先级,系统的线程调度会分配更加的的时间给thread1;
    假如刚开始是先进入thread2,thread2进行了加锁操作;而thread1则会处于忙等状态;
    由于系统分配更少的时间给thread2, 有可能导致thread2无法执行完任务并解锁;

    os_unfair_lock
    • os_unfair_lock用于替代不安全的的OSSpinLock,iOS10后支持
    • 从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等
    #import <os/lock.h>
    os_unfair_lock lock2 = OS_UNFAIR_LOCK_INIT;
    os_unfair_lock_lock(&lock2);
    os_unfair_lock_unlock(&lock2);
    
    pthread_mutex
    • 这是跨平台的互斥锁,等待锁的线程会处于休眠状态
    • 可以实现普通锁、递归锁、条件锁
    --> 普通锁
    //初始化属性
    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_destroy(&mutex);
    
    -> 递归锁:允许同一个线程对一个锁进行重复加锁
    //初始化属性
    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_mutex_t mutex;
    pthread_mutex_init(&mutex, NULL);
    //初始化条件
    pthread_cond_t condition;
    pthread_cond_init(&condition, NULL);
    //等待条件(进入休眠,放开mute锁,被唤醒后会再次对mute加锁)
    pthread_cond_wait(&condition, &mutex);
    //激活一个等待该条件的线程
    pthread_cond_signal(&condition);
    //激活全部等待该条件的线程
    pthread_cond_broadcast(&condition);
    //销毁资源
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&condition);
    
    NSLock、 NSRecursiveLock
    • NSLock是对mutex普通锁的封装
    • NSRecursiveLock是对mutex递归锁的封装
    NSCondition
    • NSCondition是对mutex和cont的封装(即条件和锁都封装在一起)
    • 可以应用到生产者消费者模式,即事情做到一半时,放开锁并进入等待;当收到信号,则重新加锁继续做事情
    - (void)_remove {
      [self.condition lock];
      if (self.data.count == 0) {
        //等待
        [self.condition wati];
      }
      [self.data removeLastObject];
      [self.condition unlock];
    }
    - (void)_add {
      [self.condition lock];
      [self.data addObject:”test”];
      //发送信号
      [self.condition sign]; //发送信号
      [self.condition unlock];
    }
    
    
    NSConditionLock
    • NSConditionLock是对NSCondition进行封装
    • 应用场景:做到控制线程的执行顺序
    NSConditionLock *ll = [[NSConditionLock alloc] initWithCondition:1];
    //进行事件1:当condition为1时,进入事件1 并加锁
    [ll lockWhenCondition:1];
    //结束事件1:解锁,并修改condition为2
    [ll unlockWithCondition:2];
    
    //进行事件2:当检测到condition为2时,进入事件2 并加锁
    [ll lockWhenCondition:2];
    //结束事件2:解锁,并修改condition为3
    [ll unlockWithCondition:3];
    
    
    dispatch_queue(DISPATCH_QUEUE_SERIAL)
    • 直接使用CCD的串行队列,也是可以实现线程同步的
    • 注意:每当考虑线程安全和线程同步时,不是只有加锁,本质上看就是保证多线程执行的操作可以按顺序去做
    dispatch_semaphore
    • 信号量,信号量的初始值可以用来控制线程并发访问的最大数量
    • 如果信号量的值 > 0, 就让信号量的值减1,然后就继续往下执行代码
    • 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成 > 0, 就让信号量的值减1,然后继续往下执行代码
    dispatch_semphore_wait(self.semphore, DISPATCH_TIME_FOREVER);
    
    dispatch_semphore_signal(self.semphore);
    
    @synchronized
    • @synchronized是对mutex递归锁的封装
    • 但苹果不推荐使用,因为性能比较差,它里面封装了hash表、封装了一个c++的数据结构
    性能从高到低排序:(具体要看使用场景)
    os_unfair_lock
    OSSpinLock
    dispatch_semaphore
    pthread_mutex
    dispatch_queue(DISPATCH_QUEUE_SERIAL)
    NSLock
    NSCondition
    pthread_mutex(recursive)
    NSRecursiveLock
    NSConditionLock
    @synchronized
    

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

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

    atomic
    • 给属性加上atomic修饰,保证属性的getter和setter都是原子性操作,就是保证getter和setter内部是线程同步的;
    • 但是他们并不保证使用属性的过程中是线程安全的
    • 不推荐ios中使用,因为属性操作一般情况下都是非常频繁的,而频繁的加锁解锁直接导致更多的损耗性能
    iOS的读写安全

    要求:
    1-同一时间只能有一条线程进行写操作
    2-同一时间可以多条新城进行读操作
    3-同一时间不可以同时存在读和写

    上诉场景就是经典的多读单写,经常用于文件等数据的读写操作,iOS的实现方案有:
    pthread_rwlock 读写锁
    dispatch_barrier_async 异步栅栏调用

    pthread_rwlock
    • 等待锁的线程会进入休眠
    //初始化锁
    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_destory(&lock);
    
    - (void)read {
      pthread_rwlock_rdlock(&lock);
      //…..read
      pthread_rwlock_unlock(&lock);
    }
    
    - (void)write {
      pthread_rwlock_wrlock(&lock);
      //…..write
       pthread_rwlock_unlock(&lock);
    }
    
    dispatch_barrier_async
    • 当执行dispatch_barrier_async函数,传入队列的其他任务都会停止
    • 这个函数传入的并发队列必须是自己通过dispatch_queue_create创建的
    • 如果传入一个串行队列或全局并发队列,那这个函数便等同于dispatch_async函数的效果
    //初始化队列
    dispatch_queue_t queue = dispatch_queue_create(“rw_queue”, DISPATCH_QUEUE_CONCURRENT);
    //读
    dispatch_async(queue, ^{
      //….read
    });
    //写
    dispatch_barrier_async(queue, ^{
      //….write
    });
    
    

    相关文章

      网友评论

          本文标题:iOS下的多线程

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