一. 多线程
1.常见多线程方案
pthread
: 纯粹 C 语言的API,跨平台, 线程生命周期程序员管理,几乎不用
NSThread
: OC 面向对象 API ,简单易用,可以直接操作线程对象 线程生命周期程序员管理,偶尔使用
GCD
:旨在替代NSThread
,充分利用 设备多核,C 语言的API,线程生命周期自动管理 经常使用
NSOPeration
:基于 GCD
(底层就是GCD
)比GCD
多了些实用功能,OC 方法,自动管理线程,经常实用。
2. GCD
的基础知识
(1)常用函数:
dispatch_sync()
:同步执行函数,不会开辟新线程。
dispatch_async()
:异步执行函数,具备开辟新线程的能力,但是不一定会开辟新线程执行函数。
异步执行任务 方式:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"执行任务-----%@",[NSThread currentThread]);
});
(2)GCD
队列
GCD
队列分为:串行队列
、并行队列
、主队列
- 串行队列创建方式:
dispatch_queue_t queue = dispatch_queue_create("myQueue",DISPATCH_QUEUE_SERIAL);
// 串行执行,第一个参数是名称 随便写,第二个是标识:DISPATCH_QUEUE_SERIAL
注意:这里虽然是C
语言的API,并且有create
关键字,但是这里并不需要释放。
- 并发队列创建方式有两种:
第一种:全局的并发队列:
dispatch_quque_t queue = dispatch_get_global_queue(0,0);
第二种: 手动创建并发队列
dispatch_queue_t queue = dispatch_queue_create("myQueue",DISPATCH_QUEUE_CONCURRENT);
// 串行执行,第一个参数是名称 随便写,第二个是标识:DISPATCH_QUEUE_CONCURRENT,并发队列标识
- 主队列 是一种特殊的串行队列
dispatch_queue_t queue = dispatch_get_main_queue();
(3)同步、异步、串行、并发
- 同步跟异步:主要是方法区别
dispatch_sync()
同步 跟dispatch_async()
异步方法。两者的区别是 是否能开辟新的线程执行任务。
image.png
- 队列的类型决定了任务的执行方式:串行跟并发
注意dispatch_async()
具备开辟 新线程的能力,但是不表示 使用它就一定会开辟新的线程。 例如 传入的 queue 是主队列,就是在主线程中执行任务,没有开辟新线程。
image.png
(4)线程死锁
发生线程死锁的条件:
使用
dispatch_sync()
函数往当前
串行
队列里面添加任务就会产生死锁。
几个典型的 死锁案例:
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"执行任务2");
});// 往主线程里面 同步添加任务 会发生死锁现象
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // 0
NSLog(@"执行任务2");
dispatch_sync(queue, ^{ // 1
NSLog(@"执行任务3");
});//此时会发生线程死锁
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");
注意:如果把 “执行任务3”代码改为:
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"执行任务3");
}); //此时不会发生线程死锁,因为任务在两个线程中执行(一个主线程、一个子线程)。
(5)GCD
队列组
- 队列组创建
dispatch_group_t groupt = dispatch_group_create();
- 创建并发队列
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
- 并发执行任务:
// 并发执行任务
dispatch_group_async(groupt, queue, ^{
NSLog(@"任务1 ---%@",[NSThread currentThread]);
NSLog(@"任务2 ---%@",[NSThread currentThread]);
});
- 任务执行完成后回调 会通知这个方法
dispatch_group_notify()
//等前面的任务 并发完成后 执行 notify
dispatch_group_notify(groupt, queue, ^{
//回到主线程队列
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"任务3 ---%@",[NSThread currentThread]);
});
});//也可以这样写
dispatch_group_notify(groupt, dispatch_get_main_queue(), ^{
//回到主线程队列
NSLog(@"任务3 ---%@",[NSThread currentThread]);
})
(6)多线程隐患
- 一段资源同时被 多条线程访问,会引发数据混乱。
- 多个线程同时访问同一个对象、变量、文件会发生数据错误。
二. 线程同步方案(线程锁)
OC 线程加锁方案:
(1) OSSPinLock: 自旋锁
(2) os_unfair_lock : 互斥锁
(3) pthread_mutex:跨平台加锁方案
(4) dispatch_semaphore:推荐用法
(5) 串行队列: dispatch_create(“myQueue”,DISPATCH_QUEUE_SERIAL)
(6) NSLock :就是对pthread_metux 的封装
(7) NSRecursiveLock: 递归锁
(8) NSCondition; 条件锁
(9) NSConditionLock: 顺序执行
(10) @synchronized:最简单的写法
分别介绍:
1.OSSPinLock: 自旋锁, 性能很高,但是不推荐使用.
导入头文件:#import <libkern/OSAtomic.h>
创建锁:
@property (assign, nonatomic) OSSpinLock lock;
// 初始化锁
self.lock = OS_SPINLOCK_INIT;
加锁:OSSpinLockLock(&_lock);
添加关键代码<><><>
解锁:OSSPinLockUnLock(&_lock)
注意: lock 要做成 全局变量,要使用同一锁才可以。如果有两个方法:判断两个方法 是否能同时执行,如果不能同时执行 则需要共用一把锁。
原理:第二条线程 会等待 (此时会存在线程等待)第一条线程解锁,才会继续执行。
p.s.OSSpinLock
的线程等待会处于忙等状态 (while(1);)
会一直占有CPU 的资源 并没有睡觉 休息。 目前此 锁已经不安全了 会出现问题:线程优先级反转问题。ios10 以后使用会警告️。
2. os_unfair_lock
现在它代替 OSSpinLock
iOS 10 以后系统使用
导入头文件:import<os/lcok.h>
注意:os_unfair_lock
是一个C语言的结构体,如果要使用属性调用需要使用assign修饰
// Low-level lock的特点等不到锁就休眠
@property (assign, nonatomic) os_unfair_lock moneyLock;
使用方法:初始化、加锁、解锁
// 初始化锁
self.ticketLock = OS_UNFAIR_LOCK_INIT;
- (void)__saleTicket
{
// 加锁
os_unfair_lock_lock(&_ticketLock);
[super __saleTicket];
//解锁
os_unfair_lock_unlock(&_ticketLock);
}
注意:如果忘记解锁,会出现线程死锁现象。
3. pthread_mutex
:多平台通用,跨平台:互斥锁。等待锁的线程或处于休眠状态。区别于 自旋锁(OSSpinLock),自旋锁不会使线程休眠.
使用方式:导入头文件:#import<pthread.h>
静态初始化:pthread_mutex mutex = PTHREAD_MUTEX_INITIALIZER
但是不能直接 赋值给类属性,因为 PTHREAD_MUTEX_INITIALIZER
是一个结构体。
动态初始化方式:
@property (assign, nonatomic) pthread_mutex_t ticketMutex;
// 初始化属性
// pthread_mutexattr_t attr;
// pthread_mutexattr_init(&attr);
// 设置属性, PTHREAD_MUTEX_DEFAULT是默认锁的属性模式
// pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// // 初始化锁,传入属性的地址
// pthread_mutex_init(&ticketMutex, &attr);
// // 销毁属性
// pthread_mutexattr_destroy(&attr);
加锁:pthread_mutex_lock(&ticketMutex)
关键代码:<><>
解锁:pthread_mutex_unlock(&ticketMutex)
注意: 这里的属性设置pthread_mutex_init(&ticketMutex, NULL);
可以传NULL,默认就等同于:PTHREAD_MUTEX_DEFAULT
最后注意把mutex 销毁掉
- (void)dealloc
{
pthread_mutex_destroy(&_moneyMutex);
pthread_mutex_destroy(&_ticketMutex);
}
递归锁
:一直调用一个关键方法函数,又需要加锁 则需要递归锁。方法:只需要改变 mutex的属性即可: 修改为:PTHREAD_MUTEX_RECURSIVE
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
递归锁:允许 同一个线程对一把锁进行重复加锁。
pthread_mutex
: 条件 用法 条件锁。
pthread_cond_t
:条件
pthread_cond_t
:(&条件,锁);
条件锁
:线程1 跟 线程 2,线程1 需要等待线程2 做完一些事情后 唤醒 线程1。所以需要 等待(pthread_cond_wait),然后 线程2 执行唤醒操作: pthread_cond_signal(&条件)
用途:
pthread_mutex_lock(&_mutex);
NSLog(@"__remove - begin");
if (self.data.count == 0) {
// 等待
pthread_cond_wait(&_cond, &_mutex);
}
[self.data removeLastObject];
NSLog(@"删除了元素");
pthread_mutex_unlock(&_mutex);
pthread_cond_signal(&条件)
唤醒线程操作,激活等待这个 条件(cond)的线程。
pthread_cond_broadcast(&cond)
, 唤醒所有等待这个条件(cond)的线程。
4.NSLock
,NSRecursiveLock
NSlock
: 底层就是对pthread_mutex
普通锁(PTHERAD_MUTEX_NORMAL
)的封装 也是互斥锁
NSRecursiveLock
:递归锁,底层也是对 pthread_mutex
的封装,只不过他是对 mutex 递归锁(PTHREAD_MUTEX_RECURSIVE
)的封装。
使用:
@property (strong, nonatomic) NSLock *ticketLock;
//初始化
self.ticketLock = [[NSLock alloc] init];
// 加锁
[self.ticketLock lock];
//关键代码
[super __saleTicket];
//解锁
[self.ticketLock unlock];
5.NSCondition
是对 mutex跟cond条件的封装,内部存在锁
使用方法:
#import "NSConditionDemo.h"
@interface NSConditionDemo()
@property (strong, nonatomic) NSCondition *condition;
@property (strong, nonatomic) NSMutableArray *data;
@end
@implementation NSConditionDemo
- (instancetype)init
{
if (self = [super init]) {
// 初始化
self.condition = [[NSCondition alloc] init];
self.data = [NSMutableArray array];
}
return self;
}
- (void)otherTest
{//两条线程 执行方法
[[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
[[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}
// 线程1
// 删除数组中的元素
- (void)__remove
{
//加锁
[self.condition lock];
NSLog(@"__remove - begin");
if (self.data.count == 0) {
// 等待信号
[self.condition wait];
}
[self.data removeLastObject];
NSLog(@"删除了元素");
//解锁
[self.condition unlock];
}
// 线程2
// 往数组中添加元素
- (void)__add
{
[self.condition lock];
sleep(1);
[self.data addObject:@"Test"];
NSLog(@"添加了元素");
// 发送信号
[self.condition signal];
// 广播
// [self.condition broadcast];
[self.condition unlock];
}
6.NSConditionLock
: 条件锁,对NSCondition
的进一步的封装。增加条件的 值 的设置
使用:能做到线程依赖,根据条件值 :1—>2—>3 方法顺序执行。
直接调用 lock 方法,则表明直接加锁,不用等条件值
子线程 顺序执行 串行
初始化:
NSConditionLock * lock = [NSConditionLock alloc] initWithCondition: 1 ];
- (void)otherTest // 三条线程
{
[[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
[[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
[[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
}
- (void)__one
{
[self.conditionLock lock];// 直接加锁,不用等条件值
NSLog(@"__one");
sleep(1);
[self.conditionLock unlockWithCondition:2];
}
- (void)__two
{
[self.conditionLock lockWhenCondition:2];
NSLog(@"__two");
sleep(1);
[self.conditionLock unlockWithCondition:3];
}
- (void)__three
{
[self.conditionLock lockWhenCondition:3];
NSLog(@"__three");
[self.conditionLock unlock];// 最后执行解锁
}
7.dispatch_queue
直接使用 GCD的串行队列 也可以实现线程同步
8. dispatch_semaphore
: semaphore
信号量,信号量的初始值可以用来控制线程并发访问的最大数量。如果设置为 1 则为串行 执行,简介达到 线程同步的目的
// 控制同时执行线程的的数量
使用方法:
// 信号量
@property (strong, nonatomic) dispatch_semaphore_t semaphore;
// 初始化。最大并发数是 1
self.semaphore = dispatch_semaphore_create(1);
- (void)otherTest
{
for (int i = 0; i < 20; i++) {
[[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
}
}
// 线程
- (void)test
{
// 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
// 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码// DISPATCH_TIME_FOREVER 这个是 一直等待的意思
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
sleep(2);
NSLog(@"test - %@", [NSThread currentThread]);
// 让信号量的值+1
dispatch_semaphore_signal(self.semaphore);
}
9.@synchronized
:是对 pthread_mutex
递归锁的封装,是最简单的 线程同步方法.
使用方法:没有提示 ,因为性能较差,apple 不推荐使用
@synchronized( self // 锁对象){
//关键代码
}
// 括号 里面 写锁 对象,根据 括号里面的对象 作为锁对象。同一个代表同一个锁,支持递归加锁。
三.总结

1.推荐使用:dispatch_samephore
跟 pthread_mutex
方案,因为OSSpinLock
有安全隐患不推荐使用了,os_unfair_lock
只有 ios 10 以后才可以使用。也可以 iOS10 之前使用上面两种方案,iOS10 以后使用 os_unfair_lock
。
2.atomic
与nonatomic
原子属性
atomic
: 原子属性,给属性加上 atomic
修饰,相当于属性的getter
跟 setter
方法都是原子属性操作,即:setter
跟getter
方法 都是线程同步的,就是加锁。
但是:他不能保证使用属性的过程是线程安全的。
比如:使用 属性 @porperty(atomic,strong) NSMutableArray *dataArr;
使用 p.data
是线程安全的,但是[p.data addobject:@”1”];
往里面添加 object 就是不线程安全的了。
不使用 atomic
的原因 是 太消耗性能了,经常在 Mac 编程中使用。
3. iOS中的 读写安全方案
文件(IO)操作:读取文件,与写入操作。要求:写入文件:只允许一条线程,读取文件 可以允许多条线程。 多读单写 实现

解决方案:pthread_rwlcok
:读写锁
dispatch_barrier_async
: 异步栅栏操作
-
pthread_rwlcok
使用方式:pthread_rwlcok使用.png
使用方式:
dispatch_barrier_async
异步栅栏操作: - 这个函数传入的并发队列必须是自己通过
dispatch_queue_create()
创建的。 - 如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于
dispatch_async
函数的效果。
使用:
self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);//如果是串行跟全局并发队列 则不起作用
for (int i = 0; i < 10; i++) {
dispatch_async(self.queue, ^{
[self read];
});
dispatch_async(self.queue, ^{
[self read];
});
dispatch_async(self.queue, ^{
[self read];
});
dispatch_barrier_async(self.queue, ^{
[self write];
});
}
- (void)read {
sleep(1);
NSLog(@"read");
}
- (void)write
{
sleep(1);
NSLog(@"write");
}
网友评论