本文主要内容
一.GCD相关
二.NSOperation相关
三.NSThread相关
四.多线程与锁
一.GCD相关
- 同步/异步和串行/并发
- dispatch_barrier_async()
- dispatch_group_async()
1.1、同步/异步和串行/并发
- dispatch_sync(serial_queue,^{ // 使命}); // 同步串行
- dispatch_async(serial_queue,^{ // 使命}); // 异步串行
- dispatch_sync(concurrent_queue,^{ // 使命}); // 同步并行
- dispatch_async(concurrent_queue,^{ // 使命}); // 异步并行
同步串行
问题一
- (void)viewDidLoad { // 使命1
// 同步主行列中
dispatch_sync(dispatch_get_main_queue(),^{ // 使命2
[self doSomething];
});
}
剖析:以上代码会发生死锁,同步分派到主行列的使命会发生由行列引起的循环等候。
iOS中默许会有一个主行列、主线程,主行列是一个串行行列,viewDidLoad办法在主行列傍边,能够看成使命1,在主线程中运转。Block相当于在主行列中添加的使命2。dispatch_sync即同步阐明使命2也在主线程中运转,,使命1完结后才干履行使命2,可是使命1在履行过程中的某个时间就要开端履行使命2,使命2依靠于使命1的完结,使命1也依靠于使命2的完结,由此发生了彼此等候,导致死锁!
主列队提交的使命,不论经过同步办法仍是异步办法,都要在主线程中处理和履行;
iOS中默许有一个主行列和主线程,主行列是一个串行行列;
viewDidLoad在主行列中而且在主线程中运转。
问题二
- (void)viewDidLoad { // 使命1
// 同步串行行列中
dispatch_sync(serialQueue,^{ // 使命2
[self doSomething];
});
}
剖析:以上代码没有问题。
iOS默许有一个主行列和主线程,主行列是一个串行行列。viewDidLoad在主行列中能够看成使命1,在主线程中运转。Block是在另一个串行行列中,能够看成使命2,dispatch_sync即同步阐明使命2也在主线程运转,因为二者不在同一个行列,不会发生死锁,可是使命2会延迟使命1的履行。
同步并发
问题
- (void)viewDidLoad { // 使命1
NSLog(@"1");
// 同步大局并发行列
dispatch_sync(global_queue,^{ // 使命2
NSLog(@"2");
dispatch_sync(global_queue,^{ // 使命3
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
剖析:输出12345。
iOS默许存在一个主行列和主线程,主行列是一个串行行列。viewDidLoad在主行列中,能够看成使命1(打印1),在主线程中运转。global_queue是大局并发行列,里边有使命2和使命3。dispatch_sync阐明global_queue中的使命也在主线程运转(会阻断线程,强制履行自己的)。因为global_queue和主线程行列不是同一个行列,不会形成死锁。因为global_queue是大局并发行列,一个使命不必管前面的使命是否履行完毕,所以使命2未完结时(打印2)就能够履行使命3(打印3),然后持续履行使命2(打印4、打印5)、使命1,而且都是在主线程履行。
- 只要是同步提交的使命,不管提交到串行行列仍是并发行列,都是在当时线程履行;
- 并发行列:提交到并发行列的一切使命或Block能够并发履行;
异步串行
- (void)viewDidLoad {
// 异步主行列
dispatch_async(dispatch_get_main_queue(),^{
[self doSomething];
});
}
剖析:iOS中默许有一个主行列和主线程,主行列是一个串行行列。viewDidLoad办法在主行列中,能够看成使命1,在主线程运转。Block相当于在主行列中添加使命2。dispatch_async阐明使命2在子线程运转,也便是不会阻挡使命1的运转,使命1完结后才干履行使命2.
- 异步会敞开新的线程;
问题
- (void)viewDidLoad {
// 异步主行列
dispatch_async(global_queue,^{
NSLog(@"1");
[self performSelector: @selector(printLog) withObject: nil, afterDelay: 0];
NSLog(@"3");
});
}
- (void)printLog { NSLog(@"2");}
剖析:输出成果为13。
global_queue是大局行列,选用dispatch_async,会拓荒一个子线程,实践上使命会在GCD底层所维护的线程池傍边某个线程中履行处理。子线程的runloop默许是不敞开的,而
经过异步办法分派使命到大局并发行列后,会在GCD底层所维护的线程池傍边某个线程中履行处理,在GCD底层所维护的线程池中的线程默许不会敞开对应的runloop,而performSelector:withObject:afterDelay是在没有runloop的情况下会失效,所以此办法不履行。
1.2、dispatch_barrier_async()
问题
怎样运用GCD完结多读单写?
剖析:需求满足读者和读者并发、读者和写者互斥、写者和写者互斥。
1.读处理之间需求并发的,用到并发行列,因为读取操作,往往需求立刻回来成果,故选用同步。这些读处理答应在对个子线程。2.写处理时其它操作都不能履行。运用栅门函数异步操作,原因是栅门函数同步操作会堵塞当时线程,假如当时线程还有其它操作,就会影响用户体会。
多读单写计划:运用GCD供给的栅门函数。
dispatch_barrier_async(concurrent_queue,^{ //写操作 });
关键代码
// 界说一个用户类UserCenter
#import "UserCenter.h"
@interface UserCenter() {
// 界说一个并发行列
dispatch_queue_t concurrent_queue;
// 用户数据中心,可能多个线程需求数据拜访
NSMutableDictionary *userCenterDic;
}
@end
// 多读单写模型
@implementation UserCenter
- (id)init {
self = [super init];
if (self) {
// 经过宏界说 DISPATCH_QUEUE_CONCURRENT 创立一个并发行列
concurrent_queue = dispatch_queue_create("read_write_queue",DISPATCH_QUEUE_CONCURRENT);
// 创立数据容器
userCenterDic = [NSMutableDictionary dictionary];
}
return self;
}
// 读取
- (id)objectForKey:(NSString *)key {
__block id obj;
// 同步读取指定数据
dispatch_sync(concurrent_queue,^{
obj = [userCenterDic objectForKey: key];
});
return obj;
}
// 写入
- (void)setObject:(id)obj forKey:(NSString *)key {
// 异步栅门调用设置数据
dispatch_barrier_async(concurrent_queue, ^{
[userCenterDic setObject: obj forKey: key];
});
}
@end
dispatch_barrier_sync和dispatch_barrier_async差异
共同点
- 它们前面的使命先履行完,它们的使命履行完,再履行后边的使命;
不同点
- dispatch_barrier_sync会堵塞当时线程,等它的使命履行完毕才干往下进行;
- dispatch_barrier_async不会堵塞当时线程,答应其他非当时行列的使命持续履行。
- 留意⚠️:运用栅门函数时,运用自界说行列才有含义,假如运用串行行列或体系的大局并发行列,栅门函数就相当于一个同步函数。
1.3、dispatch_group_async()
问题
运用GCD完结如下需求:A、B、C三个使命并发,完结后履行使命D
运用GCD中的dispatch_group_async()函数
关键代码
#import "GroupObject.h"
@interface GroupObject() {
dispatch_queue_t concurrent_queue;
NSMutableArray <NSURL *> *arrayURLs;
}
@end
@implementation GroupObject
- (id)init {
self = [super init];
if (self) {
// 创立并发行列
concurrent_queue = dispatch_queue_create("concurrent_queue",DISPATCH_QUEUE_CONCURRENT);
arrayURLs = [NSMutableArray array];
}
return self;
}
- (void)handle {
// 创立一个group
dispatch_group_t group = dispatch_group_create();
// for循环遍历各个元素履行操作
for (NSURL *url in arrayURLs) {
// 异步组分派到并发行列傍边
dispatch_group_async(group, concurrent_queue, ^{
// 根据url去下载图片
NSLog(@"url is %@",url);
});
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 当添加到组中的一切使命履行完结之后会调用该Block
NSLog(@"一切图片已经悉数下载完结");
});
}
@end
此示例实践中的应用场景为:同步并发下载多张图片,将这些图片合成为一张图片,需求确保多张图片下载完结后才干合成
。
二.NSOperation相关
需求和NSOperationQueue合作运用来完结多线程计划。
- 添加使命依靠
- 使命履行状况操控
- 最大并发量
2.1、操控使命的状况类型
- isReady:当时使命是否处于安排妥当状况;
- isExecuting:当时使命是否正在履行;
- isFinished:当时使命是否履行完结;
- isCancelled:当时使命是否被符号为撤销(不是判别是否被撤销,是符号)
- 经过KVO进行操控的。
2.2、状况操控
问题1:怎样操控NSOperation的状况?
- 假如重写了main办法,底层操控使命履行状况以及使命推出;
- 假如重写了start办法,自行操控使命状况,在合适的机遇修改对应的状况。
问题2:体系是怎样移除一个isFinished=YES的NSOperation的?
- 经过KVO
小结 NSOperation:主行列默许在主线程履行,自界说行列默许在后台履行,会拓荒子线程。
三.NSThread相关
3.1、发动流程
- 1.调用start()办法,发动线程;
- 2.在start()内部会创立一个pthread线程,指定pthread线程的发动函数;
- 3.在发动函数中会调用NSThread界说的main()函数;
- 4.在main()函数中会调用performSelector:函数,来履行咱们创立的函数; // 常驻线程
- 5.指定函数运转完结,会调用exit()函数,退出线程。
四.对线程与锁相关
iOS傍边有哪些锁?
- NSRecursiveLock
- NSLock
- dispatch_semaphore_t
4.1 @synchronized互斥锁
- 一般在创立单例目标的时候运用,确保在多线程环境下,创立的单例目标是唯一的。
4.2 @atomic自旋锁
- 特点关键字;
- 对被润饰目标进行原子操作,不担任运用。
原子操作
:不会被线程调度打断的操作。这种操作一旦开端,就一向运转到完毕,中间不会切换到另一个线程;
不担任运用
:特点赋值时,能够确保线程安全;对特点进行操作,不能确保线程安全。
示例
@property(atomic) NSMutableArray *array;
self.array = [NSMutableArray array]; // 特点赋值,线程安全
[self.array addObject: obj]; // 特点操作,线程不安全
4.3 OSSpinLock自旋锁
- 循环等候拜访,不开释当时资源。
- 用于轻量级数据拜访,简略的int值+1/-1操作
运用场景
- 内存引用计数加1或减1;
- runtime也有运用到。
4.4 NSLock互斥锁和NSRecursiveLock递归锁
示例
- (void)methodA {
[lock lock];
[self methodB];
[lock unlock];
}
- (void)methodB {
[lock lock];
// 操作逻辑
[lock unlock];
}
`剖析`:如上代码会发生死锁。对同一把锁调用两次,因为重入的原因形成死锁。
`处理计划`:运用递归锁,能够重入。
- (void)methodA {
[recursiveLock lock];
[self methodB];
[recursiveLock unlock];
}
- (void)methodB {
[recursiveLock lock];
// 操作逻辑
[recursiveLock unlock];
}
4.5 dispatch_semaphore_t信号量
触及的函数
- dispatch_semaphore_create(5)
创立信号量,指定最大并发数
struct semaphore {
int value;
List <thread>;
}
- dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER)
等候信号量>=1;假如当时信号量>=1,信号量减1,程序持续履行;假如信号量<=0,原地等候,不答应程序持续履行。
dispatch_semaphore_wait() {
S.value = S.value - 1;
// 堵塞是一个主动行为
if S.value < 0 then Block(S.list);
}
- dispatch_semaphore_signal(semaphore)
程序履行完毕,发送信号,开释资源,信号量加1。
dispatch_semaphore_singal() {
S.value = S.value + 1;
// 唤醒是一个被动行为
if S.value <= 0 then wakeup(S.list);
}
小结
1.锁分为互斥锁和自旋锁;
2.互斥锁和自旋锁的差异:
自旋锁:忙等候,即在拜访被锁资源时,调用者线程不会休眠,而是不停循环企图拜访,直到被锁资源开释。
互斥锁:会休眠,即在拜访被锁资源时,调用者线程会休眠,此刻CPU能够调度其他线程作业,直到被锁资源开释,此刻会唤醒休眠线程。
本文总结
基础小结
-
行列概念
:行列是使命的容器; -
串行行列、并发行列的差异
:
串行行列
:
1.一次只是调度一个使命,行列中的使命一个个履行(一个使命完结后,再运转下一个使命);遵从FIFO准则:先进先出,后进后出。
2.串行行列使命之间彼此包含,容易形成(行列)死锁。
并发行列
:
1.不需求把一个使命完结后,再运转下一个使命;遵从FIFO准则,常识不需求等候使命完结;
2.并发行列不会形成死锁; 3.只要并发行列+异步,才会有多线程的作用。只要当时一条线程能够运用,并发行列中使命尽管能够快速取出分派,可是只要一条线程(主线程),也只能一个个排队履行。 -
并行、并发的差异
:
并行:同一时间,多条指令在多个处理器一起履行;
并发:同一时间,只能处理一条指令,可是多个指令被快速的轮换履行,到达了具有一起履行的作用。 -
异步和同步的差异
:
异步:能够敞开新的线程;
同步:不能够敞开新的线程,在当时线程运转。 -
同步异步、串行并行形象理解
:
串行行列
使命一个个履行,后边使命需求等候前面使命完结才干履行!
并行行列
使命一个个履行,不必等候前面是否完结!
-
行列中履行使命的作业场景形象描绘
:
1.从容器(行列)中取出使命(履行代码),放到传送带(线程)上。假如容器是串行行列,则完结一个,取出一个;假如容器是并发行列,则一向不停的投进。
2.使命(履行代码)放到传送带(线程)的一刹那,CPU(操作工)看了一眼上面的标签(同步或异步)。假如标签是同步,就将它放到当时传送带;假如标签是异步,就新添加一条传送带,然后把使命放上去。
问题1:怎样用GCD完结多读单写?
即对dispatch_barrier_async()的运用,详见上文。
问题2:iOS体系为咱们供给了几种多线程技能?各自的特点是什么?
-
GCD 用于简略的线程同步,子线程分派,处理如多读单写的问题,
-
NSOperation和NS OperationQueue
便利操控使命状况、操控添加依靠和移除依靠。可用于杂乱线程操控,如第三方AFNetWorking和SDWebImage。 -
NSThread
用于完结常驻线程(经过在main函数中调用performSelector:函数创立自己的函数)。
问题3:NSOperation目标在Finished之后是怎样从queue傍边移除掉的?
NSOperation目标在Finished之后,会在内部以KVO的办法告诉对应的NSOperationQueue,到达对NSOperation目标进行移除的目的。
问题4:运用过哪些锁?怎么运用的?
@synchronized互斥锁 @atomic自旋锁 OSSpinLock自旋锁 NSLock互斥锁和NSRecursiveLock递归锁
有任何问题,欢迎各位谈论指出!觉得博主写的还不错的费事点个赞喽