1.为什么用GCD
GCD 是苹果公司为多核的并行运算提出的解决方案。
- 它会主动利用更多的CPU内核(比方双核、四核),
- 会主动办理线程的生命周期(创立线程、调度使命、销毁线程)
程序员只需求告诉 GCD 想要履行什么使命,不需求编写任何线程办理代码。
2.使命(同步异步) & 行列(串行并发)
使命:便是你在线程中履行的那段代码。履行使命有两种办法:『同步履行』和『异步履行』。两者的首要区别是:是否等候行列的使命履行结束,以及是否具有敞开新线程的才能。
- 同步 在当时线程中履行使命,使命未履行完时,会堵塞线程,不会拓荒线程。运用办法如下
dispatch_sync(queue, ^{
// do something
});
- 异步 能够在新的线程中履行使命,具有敞开新线程的才能。运用办法如下
dispatch_async(queue, ^{
// do something
});
GCD 中有两种行列:『串行行列』和『并发行列』,两者都契合 FIFO(先进先出)的原则。
- 串行 只敞开一个线程,一个使命履行结束后,再履行下一个使命。
- 并发 能够敞开多个线程,而且一起履行使命。
/*
第一个参数表明行列的唯一标识符,用于 DEBUG,可为空。
行列的称号推荐运用应用程序 ID 这种逆序全程域名。
第二个参数用来识别是串行行列仍是并发行列。
`DISPATCH_QUEUE_SERIAL` 表明串行行列,`DISPATCH_QUEUE_CONCURRENT` 表明并发行列。
*/
dispatch_queue_t queue = dispatch_queue_create("com.test.company", DISPATCH_QUEUE_SERIAL);
注意:并发行列的并发功用只要在异步(dispatch_async)办法下才有用。
3.组合运用
同步串行
使命一个接一个履行,不拓荒线程
/**
串行行列,同步履行
*/
- (void)createSerialQueueWithSync {
dispatch_queue_t serialQueue = dispatch_queue_create("com.neusoft", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
NSLog(@"A==%@", [NSThread currentThread]);
dispatch_sync(serialQueue, ^{
NSLog(@"B==%@", [NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
NSLog(@"C==%@", [NSThread currentThread]);
});
NSLog(@"D==%@", [NSThread currentThread]);
}
成果如下
同步并发
使命一个接一个履行,不拓荒线程
/**
并行行列,同步履行
*/
- (void)createConcurrentQueueWithSync {
dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.neusoft", DISPATCH_QUEUE_CONCURRENT);
// dispatch_queue_t conCurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//这个是全局并行行列,一般并行使命都会加到这里边去
dispatch_sync(conCurrentQueue, ^{
NSLog(@"A==%@", [NSThread currentThread]);
});
dispatch_sync(conCurrentQueue, ^{
NSLog(@"B==%@", [NSThread currentThread]);
});
dispatch_sync(conCurrentQueue, ^{
NSLog(@"C==%@", [NSThread currentThread]);
});
NSLog(@"D==%@", [NSThread currentThread]);
}
打印成果如下
异步串行
使命一个接一个履行,会拓荒线程。
/**
串行行列,异步履行
*/
- (void)createSerialQueueWithAsync {
dispatch_queue_t serialQueue = dispatch_queue_create("com.neusoft", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
NSLog(@"A==%@", [NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"B==%@", [NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"C==%@", [NSThread currentThread]);
});
NSLog(@"D==%@", [NSThread currentThread]);
}
打印成果如下
异步并发
使命乱序履行,拓荒线程。
/**
并行行列,异步履行
*/
- (void)createConcurrentQueueWithAsync {
dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.neusoft", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(conCurrentQueue, ^{
NSLog(@"A==%@", [NSThread currentThread]);
});
dispatch_async(conCurrentQueue, ^{
NSLog(@"B==%@", [NSThread currentThread]);
});
dispatch_async(conCurrentQueue, ^{
NSLog(@"C==%@", [NSThread currentThread]);
});
NSLog(@"D==%@", [NSThread currentThread]);
}
打印成果如下
4.GCD其他用法
dispatch_barrier
咱们有时需求履行两组操作,且要求第一组(使命1、2、3)操作履行完之后,才能开端履行第二组(使命4、5、6)操作。这时就需求用到栅门函数,栅门函数的效果如下图所示
从图中咱们能够看出
- 对于串行行列来说,使命是一个接一个履行的,所以对串行行列运用栅门函数含义不大
- 对于并发行列来说,使命是乱序履行的,所以运用栅门函数能够很好的操控行列内使命履行的次序
运用
举例如下
- (void)test {
dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"开端——%@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"推迟2s的使命1——%@", [NSThread currentThread]);
});
NSLog(@"第一次结束——%@", [NSThread currentThread]);
// dispatch_barrier_async(queue, ^{
// NSLog(@"----------栅门使命----------%@", [NSThread currentThread]);
// });
// NSLog(@"栅门结束——%@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(1);
NSLog(@"推迟1s的使命2——%@", [NSThread currentThread]);
});
NSLog(@"第2次结束——%@", [NSThread currentThread]);
}
不运用栅门函数
开端——<NSThread: 0x600002384f00>{number = 1, name = main}
第一次结束——<NSThread: 0x600002384f00>{number = 1, name = main}
第2次结束——<NSThread: 0x600002384f00>{number = 1, name = main}
推迟1s的使命2——<NSThread: 0x6000023ec300>{number = 5, name = (null)}
推迟2s的使命1——<NSThread: 0x60000238c180>{number = 7, name = (null)}
运用栅门函数
开端——<NSThread: 0x600000820bc0>{number = 1, name = main}
第一次结束——<NSThread: 0x600000820bc0>{number = 1, name = main}
栅门结束——<NSThread: 0x600000820bc0>{number = 1, name = main}
第2次结束——<NSThread: 0x600000820bc0>{number = 1, name = main}
推迟2s的使命1——<NSThread: 0x600000863c80>{number = 4, name = (null)}
----------栅门使命----------<NSThread: 0x600000863c80>{number = 4, name = (null)}
推迟1s的使命2——<NSThread: 0x600000863c80>{number = 4, name = (null)}
总结一句话便是: 先履行栅门前使命
,再履行栅门使命
,最终履行栅门后使命
。
问题
1、dispatch_barrier_async
和dispatch_barrier_sync
有什么区别,该用那个?
两者效果相同,都是先履行栅门前使命
,再履行栅门使命
,最终履行栅门后使命
。但是dispatch_barrier_sync
会堵塞线程,影响后面的使命履行(尽量少用)。
比方将上例的dispatch_barrier_async
改成dispatch_barrier_sync
,成果如下
开端——<NSThread: 0x600001040d40>{number = 1, name = main}
第一次结束——<NSThread: 0x600001040d40>{number = 1, name = main}
推迟2s的使命1——<NSThread: 0x60000100ce40>{number = 6, name = (null)}
----------栅门使命----------<NSThread: 0x600001040d40>{number = 1, name = main}
栅门结束——<NSThread: 0x600001040d40>{number = 1, name = main}
第2次结束——<NSThread: 0x600001040d40>{number = 1, name = main}
推迟1s的使命2——<NSThread: 0x60000100ce40>{number = 6, name = (null)}
注意:栅门函数有必要是自定义的并发行列才有用,且有必要是同一行列中的线程才有用。
读写锁
读写锁在读多写少的场景效率很高,它的定义是:同一时刻有多个读者,同一时刻只能有一个写者,读者和写者不能一起存在。
工作原理:读写锁实际是一种特殊的自旋锁,假如读写锁当时没有读者,也没有写者,那么写者能够马上获得读写锁,否则它有必要自旋在那里, 直到没有任何写者或读者。
写的那个时刻段,不能有任何读者+其他写者,因而依据栅门函数原理,咱们能够把读写操作了解成下图
- (id)readDataForKey:(NSString*)key {
__block id value;
dispatch_sync(_concurrentQueue, ^{
value = [self valueForKey:key];
});
return value;
}
- (void)writeData:(id)data forKey:(NSString*)key {
dispatch_barrier_async(_concurrentQueue, ^{
[self setValue:data forKey:key];
});
}
为什么用dispatch_sync
完结读,用dispatch_barrier_async
完结写?
- 读:运用
dispatch_sync
,会使 value 获取到值后,再回来给读者- 若运用
dispatch_async
则会先回来空的result value
,再经过[self valueForKey:key]
办法获取到值
- 若运用
- 写:写的那个时刻段,不能有任何读者+其他写者,
dispatch_barrier_async
满足等行列中前面的读写使命都履行完了再来履行当时使命,而不堵塞当时线程
dispatch_group
调度组有以下才能
- 将使命分组履行
dispatch_group_enter(group), dispatch_group_leave(group)
- 能监听使命组完结
dispatch_notify(group, queue, ^{});
- 并设置等候时刻
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_group_async
- (void)createGroupQueue {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"A---%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"B---%@", [NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
NSLog(@"C---%@", [NSThread currentThread]);
});
dispatch_notify(group, queue, ^{
NSLog(@"行列组使命履行结束");
});
}
dispatch_group_enter & dispatch_group_leave
- (void)test2 {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"A---%@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"B---%@", [NSThread currentThread]);
}
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"C---%@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_notify(group, queue, ^{
NSLog(@"行列组使命履行结束");
});
}
dispatch_group_wait
dispatch_group_wait
能够设置调度组的等候时刻,这也意味着会堵塞当时线程,等候调度组完结使命,若在指定时刻内完结使命,会回来 0;否则回来非 0。
语法:long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)
- group:需求等候的调度组
- timeout:等候的超时时刻(即等多久)
- 设置为
DISPATCH_TIME_NOW
意味着不等候直接判定调度组是否履行结束 - 设置为
DISPATCH_TIME_FOREVER
则会堵塞当时调度组,直到调度组履行结束 - 设置具体超时时刻
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC))
- 设置为
- 回来值:为
long
类型- 0 表明在指定时刻内调度组完结了使命
- 非0 表明指定时刻内调度组没有准时完结使命
举例如下
- (void)test {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"恳求一完结");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"恳求二完结");
dispatch_group_leave(group);
});
// long timeout = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// 设置 1 秒超时,在这 1 秒期间会堵塞当时线程
long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
NSLog(@"timeout=%ld", timeout);
if (timeout == 0) {
NSLog(@"准时完结使命");
} else {
NSLog(@"超时");
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"告诉,所有恳求完结");
});
}
成果如下
timeout=49 // 49 便是非 0,即超时回来过错
超时
恳求一完结
恳求二完结
告诉,所有恳求完结
dispatch_semaphore
信号量可用于操控 GCD 最大并发数,还能够当锁运用(信号量锁的效率也很高)。
-
dispatch_semaphore_create(intptr_t value);
:创立信号量 -
dispatch_semaphore_wait(semaphore, dispatch_time_t timeout)
:等候信号量,信号量-1
。当信号量< 0
时会堵塞当时线程,依据传入的等候时刻决议接下来的操作(假如永久等候将比及信号(signal)
才履行下去) -
dispatch_semaphore_signal(semaphore);
:释放信号量,信号量+1
。当信号量>= 0
会履行wait
之后的代码
比方AFURLSessionManager.m
中的tasksForKeyPath:
办法。经过引进信号量的办法,等候异步履行使命成果,获取到 tasks,然后再回来该 tasks。
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
dispatch_once
咱们在创立单例、或许有整个程序运转过程中只履行一次的代码时,咱们就用到了 GCD 的 dispatch_once
办法。运用 dispatch_once
办法能确保某段代码在程序运转过程中只被履行 1 次,而且即使在多线程的环境下,dispatch_once
也能够确保线程安全。
/**
* 一次性代码(只履行一次)dispatch_once
*/
- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只履行 1 次的代码(这里边默认是线程安全的)
});
}
有时dispatch_once
可用作优化,类似程序运转期间要屡次获取的值,能够只履行一次耗时操作,然后把值存起来供下次直接运用,比方下例中获取设备等级的代码
+ (NSInteger)getDeviceLevel {
static dispatch_once_t deviceLevelOnce;
static NSInteger deviceLevel;
dispatch_once(&deviceLevelOnce, ^{
struct utsname systemInfo;
uname(&systemInfo);
NSString *deviceModel = [NSString stringWithCString:systemInfo.machine encoding:NSASCIIStringEncoding];
NSBundle *bundle = [NSBundle c4_localizedBundle];
NSString *path = [bundle pathForResource:@"Apple_mobile_device_types" ofType:@"plist"];
NSDictionary *deviceDict = [NSDictionary dictionaryWithContentsOfFile:path];
NSDictionary *dict = deviceDict[deviceModel];
NSString *level = dict[@"level"];
if (!level) {
deviceLevel = 4;
} else {
deviceLevel = level.integerValue;
}
});
return deviceLevel;
}
dispatch_apply
//快速迭代办法
dispatch_apply(size_t iterations,
dispatch_queue_t DISPATCH_APPLY_QUEUE_ARG_NULLABILITY queue,
DISPATCH_NOESCAPE void (^block)(size_t iteration));
当 queue 是串行行列时,它的效果和 for 循环相同,所以一般用于并行行列。
运用办法如下
- (void)testApply {
NSLog(@"dispatch_apply 开端履行");
dispatch_apply(6, dispatch_get_global_queue(0, 0), ^(size_t iteration) {
NSLog(@"%zd---%@", iteration, [NSThread currentThread]);
});
NSLog(@"dispatch_apply 结束履行");
}
成果如下
dispatch_apply 开端履行
1---<NSThread: 0x600003340d40>{number = 9, name = (null)}
2---<NSThread: 0x600003334000>{number = 11, name = (null)}
3---<_NSMainThread: 0x600003328080>{number = 1, name = main}
4---<NSThread: 0x60000331a4c0>{number = 12, name = (null)}
0---<NSThread: 0x60000331a3c0>{number = 10, name = (null)}
5---<NSThread: 0x600003348f40>{number = 13, name = (null)}
dispatch_apply 结束履行
无论是在串行行列,仍是并发行列中,dispatch_apply 都会等候全部使命履行结束,这点就像是同步操作,也像是行列组中的dispatch_group_wait
办法。
dispatch_source
常用应用场景是 GCD Timer。
一般运用NSTimer
来处理定时逻辑,但NSTimer
是依赖 Runloop 的,而 Runloop 能够运转在不同的形式下。假如NSTimer
添加在一种形式下,当 Runloop 运转在其他形式下的时候,定时器就挂机了;又假如 Runloop 在堵塞状态,NSTimer
触发时刻就会推迟到下一个 Runloop 周期。因而NSTimer
在计时上会有误差,并不是特别准确,而 GCD 定时器不依赖 Runloop ,计时精度要高很多
常用 API
-
dispatch_source_create
: 创立事情源 -
dispatch_source_set_event_handler
: 设置数据源回调 -
dispatch_source_merge_data
: 设置事情源数据 -
dispatch_source_get_data
: 获取事情源数据 -
dispatch_resume
: 继续 -
dispatch_suspend
: 挂起 -
dispatch_cancle
: 撤销
创立 GCD Timer 例子,摘自 YYTimer
@property (nonatomic, strong) dispatch_source_t timer;
//创立timer
_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
//设置timer首次履行时刻,距离,准确度
dispatch_source_set_timer(_source, dispatch_time(DISPATCH_TIME_NOW, (start * NSEC_PER_SEC)), (interval * NSEC_PER_SEC), 0);
//设置timer事情回调
__weak typeof(self) _self = self;
dispatch_source_set_event_handler(_source, ^{
[_self fire];
});
//默认是挂起状态,需求手动激活
dispatch_resume(_source);