iOS 多线程 GCD part3:API
0. 预备知识
GCD对时间的描述有些新奇
#define NSEC_PER_SEC 1000000000ull
#define NSEC_PER_MSEC 1000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull
MSEC:毫秒
USEC:微秒
NSEC:纳秒
SEC:秒
NSEC_PER_SEC,每秒有多少纳秒
USEC_PER_SEC,每秒有多少毫秒
#define NSEC_PER_SEC 1000000000ull //GCD最常用的时间描述
打现在开始往后5秒
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));1->开始时间,2->延后时间
1. dispatch_group
1.1 dispatch_group_notify
当多个任务
在并行队列内无序的进行,需要在多个任务
全部完成后立刻开启新任务
,那么这时就是需要用到
dispatch_group_async
与dispatch_group_notify
的组合
dispatch_queue_t coucurrent_queue = dispatch_queue_create("com.pogong.www", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, coucurrent_queue, ^{
for (int i = 0; i<10; i++) {
NSLog(@"group-01 - %@", [NSThread currentThread]);
}
});
dispatch_group_async(group, coucurrent_queue, ^{
for (int i = 0; i<10; i++) {
NSLog(@"group-02 - %@", [NSThread currentThread]);
}
});
dispatch_group_notify(group, coucurrent_queue,^{
NSLog(@"op finish,start new op");
});
1.2 dispatch_group_wait
还是1.1同样的需求:当任务在并行队列内无序的进行,需要在多个任务
结束后立刻开启新任务
也可以用dispatch_group_async
与dispatch_group_wait
的组合来完成
dispatch_queue_t coucurrent_queue = dispatch_queue_create("com.pogong.www", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, coucurrent_queue, ^{
for (int i = 0; i<10; i++) {
NSLog(@"group-01 - %@", [NSThread currentThread]);
}
});
dispatch_group_async(group, coucurrent_queue, ^{
for (int i = 0; i<10; i++) {
NSLog(@"group-02 - %@", [NSThread currentThread]);
}
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"op finish");
只是需要注意的是dispatch_group_wait
会阻塞当前线程,或者说GCD的所有带_wait
都会阻塞当前线程
dispatch_group_wait
的第二参数为超时时间,DISPATCH_TIME_FOREVER
代表一直等下去.
也可以将以上dispatch_group_wait
代码换成:
int delayInSeconds = 20;
dispatch_time_t cheak_Time = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));
long res = dispatch_group_wait(group, cheak_Time);
if (res == 0) {
NSLog(@"zc done");
}else{
NSLog(@"zc ing");
}
设置超时时间为20s,
1.group任务提前完成,提前返回
2.按指定时间返回
dispatch_group_wait
的返回值为0代表group的任务全部完成,否则就代表任务还在进行中
1.3 dispatch_async+dispatch_group_enter+dispatch_group_leave
dispatch_group_t group = dispatch_group_create();
NSMutableArray * array = [NSMutableArray array];
for (int i=0; i<5000; i++) {
dispatch_group_enter(group);//enter和leave成对出现
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"add in %@", [NSThread currentThread]);
[array addObject:[NSNumber numberWithInt:i]];
dispatch_group_leave(group);//enter和leave成对出现
});
}
NSLog(@"before wait %@", [NSThread currentThread]);
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
还是1.1同样的需求:当任务在并行队列内无序的进行,需要在多个任务
结束后立刻开启新任务
也可以用dispatch_async
+dispatch_group_enter
+dispatch_group_leaver
的组合来完成
2.dispatch_after
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^{
NSLog(@"zc after");
});
dispatch_after的用法如上方代码,就不多说了
3.信号量dispatch_semaphore
首先介绍一下信号量(semaphore)的概念。信号量是持有计数的信号,不过这么解释等于没解释。我们举个生活中的例子来看看。
假设有一个房子,它对应进程的概念,房子里的人就对应着线程。一个进程可以包括多个线程。这个房子(进程)有很多资源,比如花园、客厅等,是所有人(线程)共享的。
但是有些地方,比如卧室,最多只有两个人能进去睡觉。怎么办呢,在卧室门口挂上两把钥匙。进去的人(线程)拿着钥匙进去,没有钥匙就不能进去,出来的时候把钥匙放回门口。
这时候,门口的钥匙数量就称为信号量(Semaphore)。很明显,信号量为0时需要等待,信号量不为零时,减去1而且不等待。
此段描述完全摘自:bestswifer iOS多线程编程——GCD与NSOperation总结
3.1 多线程资源单一占用
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
NSMutableArray * muArr = [NSMutableArray array];
for (int i = 0; i< 1000; i++) {
dispatch_async(q, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
[muArr addObject:@(i)];
NSLog(@"%d",i);
dispatch_semaphore_signal(sem);
});
}
dispatch_semaphore_create
,创建一个信号量为1的信号
dispatch_semaphore_wait
,只有信号量大于0才能通过dispatch_semaphore_wait
,通过的同时信号量减1
dispatch_semaphore_signal
,信号量加1
3.2 超时显示
- (void)downloadFile
{
_semaphore = dispatch_semaphore_create(0);
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://baobab.wdjcdn.com/14525705791193.mp4"] cachePolicy:1 timeoutInterval:30];
[[self.session downloadTaskWithRequest:request] resume];
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));
NSLog(@"zc wait");
long res = dispatch_semaphore_wait(_semaphore, popTime);
if (res) {
NSLog(@"zc timed out");
}else{
NSLog(@"zc timed in");
}
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
dispatch_semaphore_signal(_semaphore);
NSLog(@"zc done");
}
还是那句所有GCD带_wait
函数都会阻塞当前线程
开始下载创建信号,
下载成功,增加信号量,
任务提前完成,提前返回
按指定时间返回
dispatch_semaphore_wait
的返回值为0代表任务完成,否则就代表任务还在进行中
3.3 dispatch_semaphore_wait超时时间的理解
overTime不是调用dispatch_semaphore_wait后等待的时间,而是信号量创建后的时间
-(void)overTimeTest{
dispatch_semaphore_t signal = dispatch_semaphore_create(0);
double delayInSeconds = 5.0;
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"1 start wait");
dispatch_semaphore_wait(signal, overTime);
NSLog(@"需要线程同步的操作1 开始");
dispatch_semaphore_signal(signal);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(3);
NSLog(@"2 start wait");
dispatch_semaphore_wait(signal, overTime);
NSLog(@"需要线程同步的操作2");
dispatch_semaphore_signal(signal);
});
}
以上两个block内的dispatch_semaphore_wait
调用相差3秒,但执行dispatch_semaphore_wait
却是同时的.
4.dispatch_barrier_async
多个线程对内存中的数组或是字典进行读的操作是OK,
但如果多个线程对内存中的数组或是字典进行读+写的操作,就会有问题
NSMutableArray * muArr = [NSMutableArray array];
[muArr addObject:@"1"];
[muArr addObject:@"2"];
[muArr addObject:@"3"];
dispatch_queue_t con_queue = dispatch_queue_create("com.pogong.www", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(con_queue, ^{
NSLog(@"%@",muArr);
});
dispatch_async(con_queue, ^{
NSLog(@"%@",muArr);
});
dispatch_barrier_async(con_queue, ^{
NSLog(@"add add add");
[muArr addObject:@"4"];
});
dispatch_async(con_queue, ^{
NSLog(@"%@",muArr);
});
dispatch_async(con_queue, ^{
NSLog(@"%@",muArr);
});
dispatch_barrier_async
很好解决了多线程读写安全进行的问题,
在原有的多线程执行时,加入一个dispatch_barrier_async
,
会像有栅栏一样挡住多线程执行,而先执行dispatch_barrier_async
,
在执行完dispatch_barrier_async
之后,再继续多线程的操作
5. dispatch_source
Dispatch Source用于监听系统的底层对象,比如文件描述符,Mach端口,信号量等。主要处理的事件如下表
DISPATCH_SOURCE_TYPE_DATA_ADD 数据增加
DISPATCH_SOURCE_TYPE_DATA_OR 数据OR
DISPATCH_SOURCE_TYPE_MACH_SEND Mach端口发送
DISPATCH_SOURCE_TYPE_MACH_RECV Mach端口接收
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 内存情况
DISPATCH_SOURCE_TYPE_PROC 进程事件
DISPATCH_SOURCE_TYPE_READ 读数据
DISPATCH_SOURCE_TYPE_SIGNAL 信号
DISPATCH_SOURCE_TYPE_TIMER 定时器
DISPATCH_SOURCE_TYPE_VNODE 文件系统变化
DISPATCH_SOURCE_TYPE_WRITE 文件写入
方法
dispatch_source_create:创建dispatch source,创建后会处于挂起状态进行事件接收,需要设置事件处理handler进行事件处理。
dispatch_source_set_event_handler:设置事件处理handler
dispatch_source_set_cancel_handler:事件取消handler,就是在dispatch source释放前做些清理的事。
dispatch_source_cancel:关闭dispatch source,这样后续触发的事件时不去调用对应的事件处理handler,但已经在执行的handler不会被取消.
5.1 dispatch_source_set_timer
UITableView在被拖拽时NSTimer就不起作用了
必须加[[NSRunLoop currentRunLoop] addTimer:anyTimer forMode:NSRunLoopCommonModes];
而dispatch source timer与runloop是没有关系的,所以可以放心使用
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0, 0, DISPATCH_TARGET_QUEUE_DEFAULT);
dispatch_source_set_event_handler(source, ^(){
NSLog(@"sourceTimer Time log");
});
dispatch_source_set_timer(source, DISPATCH_TIME_NOW,5*NSEC_PER_SEC,1*NSEC_PER_SEC);//1->源,2->开始时间,3->间隔时间,4->误差秒数
_source = source;
dispatch_resume(_source);
5.2 文件监听
监视文件夹内文件变化
NSURL * directoryURL = [NSURL URLWithString:_path]; // assume this is set to a directory
int const fd = open([[directoryURL path] fileSystemRepresentation], O_EVTONLY);
if (fd < 0) {
char buffer[80];
strerror_r(errno, buffer, sizeof(buffer));
NSLog(@"Unable to open \"%@\": %s (%d)", [directoryURL path], buffer, errno);
return;
}
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd,
DISPATCH_VNODE_WRITE | DISPATCH_VNODE_DELETE, DISPATCH_TARGET_QUEUE_DEFAULT);
dispatch_source_set_event_handler(source, ^(){
unsigned long const data = dispatch_source_get_data(source);
if (data & DISPATCH_VNODE_WRITE) {
NSLog(@"The directory changed.");
}
if (data & DISPATCH_VNODE_DELETE) {
NSLog(@"The directory has been deleted.");
}
});
dispatch_source_set_cancel_handler(source, ^(){
close(fd);
});
_source = source;
dispatch_resume(_source);
还要注意需要用DISPATCH_VNODE_DELETE
去检查监视的文件或文件夹是否被删除,如果删除了就停止监听
6. dispathc_once
dispathc_once
函数可以确保某个 block 在应用程序执行的过程中只被处理一次,而且它是线程安全的。所以单例模式可以很简单的实现,写单例必备
+ (Manager *)sharedInstance {
static Manager *sharedManagerInstance = nil;
static dispatch_once_t once;
dispatch_once($once, ^{
sharedManagerInstance = [[Manager alloc] init];
});
return sharedManagerInstance;
}
7. dispathc_apply
7.1 并行队列完成任务,开启新任务
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%d\n",index);
});
NSLog(@"done");
打印结果为:
2017-03-04 21:40:43.454 GCDTrain[1289:42894] 0
2017-03-04 21:40:43.454 GCDTrain[1289:42937] 3
2017-03-04 21:40:43.454 GCDTrain[1289:42940] 2
2017-03-04 21:40:43.454 GCDTrain[1289:42894] 4
2017-03-04 21:40:43.454 GCDTrain[1289:42894] 5
2017-03-04 21:40:43.454 GCDTrain[1289:42938] 1
2017-03-04 21:40:43.455 GCDTrain[1289:42894] 8
2017-03-04 21:40:43.454 GCDTrain[1289:42940] 6
2017-03-04 21:40:43.455 GCDTrain[1289:42937] 7
2017-03-04 21:40:43.455 GCDTrain[1289:42938] 9
2017-03-04 21:40:43.455 GCDTrain[1289:42894] done
所以这又是一种对多线程无序的限定的API,dispatch_apply
block内的所有任务被执行完之后才会执行后面的代码,当然dispatch_apply
与前面提到的所有带_wait
的API一样都是阻塞当前线程的
dispatch_apply
与带_wait
的API也有不同,dispatch_apply
比较适合做些重复的+执行次数确定的任务
7.2 防止开启线程过多
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 999; i++){
dispatch_async(queue, ^{
NSLog(@"%d,%@",i,[NSThread currentThread]);
});
}
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(999, queue, ^(size_t i){
NSLog(@"%d,%@",i,[NSThread currentThread]);
});
看两份代码的打印结果可知道,不用dispatch_apply
的并行+异步会开启许多线程,而我们已经知道:「使用太多线程会导致消耗大量内存」,所以在这种场景下应该使用dispatch_apply
8.dispatch_set_target_queue
8.1dispatch_set_target_queue可以设置queue的优先级
dispatch_queue_create
创建队列的优先级跟global dispatch queue
的默认优先级一样,假如我们需要设置队列的优先级,可以通过dispatch_set_target_queue
方法
dispatch_queue_t serialQueue = dispatch_queue_create("com.pogong.www", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
dispatch_set_target_queue(serialQueue, globalQueue);
//这样serialQueue的优先级和globalQueue的优先级一样
8.2 定义队列层级关系
将多个队列用dispatch_set_target_queue
设置为某个串行队列的下属队列,可以防止并行执行
- 加队列层级关系
dispatch_queue_t targetQueue = dispatch_queue_create("target_queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(queue1, targetQueue);//加层级关系
dispatch_set_target_queue(queue2, targetQueue);//加层级关系
dispatch_async(queue1, ^{
NSLog(@"do job1");
});
dispatch_async(queue2, ^{
NSLog(@"do job2");
});
dispatch_async(queue2, ^{
NSLog(@"do job3");
});
dispatch_async(queue2, ^{
NSLog(@"do job4");
});
dispatch_async(queue2, ^{
NSLog(@"do job5");
});
dispatch_async(queue2, ^{
NSLog(@"do job6");
});
2017-03-04 22:07:40.723 GCDTrain[1400:51089] do job1
2017-03-04 22:07:40.723 GCDTrain[1400:51089] do job2
2017-03-04 22:07:40.724 GCDTrain[1400:51089] do job3
2017-03-04 22:07:40.724 GCDTrain[1400:51089] do job4
2017-03-04 22:07:40.724 GCDTrain[1400:51089] do job5
2017-03-04 22:07:40.725 GCDTrain[1400:51089] do job6
- 不加队列层级关系
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue1, ^{
NSLog(@"do job1");
});
dispatch_async(queue2, ^{
NSLog(@"do job2");
});
dispatch_async(queue2, ^{
NSLog(@"do job3");
});
dispatch_async(queue2, ^{
NSLog(@"do job4");
});
dispatch_async(queue2, ^{
NSLog(@"do job5");
});
dispatch_async(queue2, ^{
NSLog(@"do job6");
});
2017-03-04 22:01:04.781 GCDTrain[1365:48597] do job2
2017-03-04 22:01:04.781 GCDTrain[1365:48598] do job1
2017-03-04 22:01:04.781 GCDTrain[1365:48599] do job3
2017-03-04 22:01:04.781 GCDTrain[1365:48609] do job5
2017-03-04 22:01:04.781 GCDTrain[1365:48608] do job4
2017-03-04 22:01:04.782 GCDTrain[1365:48597] do job6
打印结果一目了然
9. dispatch IO
在书上,在别人的文章里对dispatch IO
都是一段引用苹果官方的代码,跑不起来,自己真会用了再说
10. 未完待续
还有很多API并未见过介绍,所以.....
文章参考:
Objective-C高级编程:iOS与OS X多线程和内存管理
bestswifer iOS多线程编程——GCD与NSOperation总结
戴铭 细说GCD如何用