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
});
网友评论