美文网首页
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