多线程实现的几种方案,主要包括pthread、NSThread、GCD、NSOperation。PS:其中pthread和NSThread需要我们管理线程生命周期,比较麻烦,不是很常用,我们重点关注GCD和NSOperation。This is GCD。
GCD,Grand Central Dispath是异步执行任务的技术之一,开发者只需要制定执行的任务,将任务放入队列中,GCD会自动在系统中创建线程来计划执行这些任务。 伟大的中心调度器,听起来就很牛逼了,功能也是比较强大的。
我们知道任务是放在队列中执行的,设置好执行的顺序是并行还是串行,然后设置执行方式是同步还是异步就好了,剩下的事情就交给系统进行处理了。【系统是怎么处理的呢?这里先留个扣子】
- 创建队列(队列的名称采用应用ID的方式,即按照FQDN-逆序全域名的方式)
// 串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.silence.gcd.mySerialQueue", DISPATCH_QUEUE_SERIAL);
// 并行队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.silence.gcd.myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
- 设置执行方式
// 同步执行
dispatch_sync(serialQueue, ^{
// 所要执行的任务
NSLog(@"当前线程===》%@",[NSThread currentThread]);
});
// 打印信息:当前线程===》<NSThread: 0x600003e493c0>{number = 1, name = main}
// 可以看出没有创建新的线程,默认在主线程顺序执行
// 异步执行
dispatch_async(serialQueue, ^{
// 所要执行的任务
NSLog(@"当前线程===》%@",[NSThread currentThread]);
});
// 打印信息:当前线程===》<NSThread: 0x600001a49a00>{number = 3, name = (null)}
// 可以看出创建了新的线程,在新的线程中执行的任务
- 设置优先级
这里就说到全局队列的一个特性,设置优先级,我们自己创建的队列不可以,那么怎么给我们自己创建的队列设置优先级呢?可以将我们创建的队列作为全局队列的子队列,然后设置优先级。PS:使用时尽量用DISPATCH_QUEUE_PRIORITY_DEFAULT,而且最好不要用这一特性,会造成优先级倒置的问题。
// 获取全局队列,设置最低的优先级
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0);
// 创建我们自己的队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.silence.tongbu.createGlobalQueue", DISPATCH_QUEUE_SERIAL);
// 将我们的队列设置成为全局队列的子队列
dispatch_set_target_queue(serialQueue, globalQueue);
dispatch_async(serialQueue, ^{
NSLog(@"任务1:当前线程==》%@",[NSThread currentThread]);
});
// 设置最高优先级
dispatch_queue_t serialQueue2 = dispatch_queue_create("com.silence.tongbu.createGlobalQueue2", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(serialQueue2, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
dispatch_async(serialQueue2, ^{
NSLog(@"任务2:当前线程==》%@",[NSThread currentThread]);
});
2019-05-30 11:57:53.972219+0800 同步[13224:1596637] 任务2:当前线程==》<NSThread: 0x6000031ec8c0>{number = 3, name = (null)}
2019-05-30 11:57:53.972237+0800 同步[13224:1596638] 任务1:当前线程==》<NSThread: 0x6000031fed80>{number = 4, name = (null)}
4.延迟执行dispatch_after(dispatch_time_t when, dispatch_queue_t queue,dispatch_block_t block);
参数when延迟多久,queue追加到那个队列中,block执行什么任务。
// 设置延迟多久执行
dispatch_time_t timeQueue = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
NSLog(@"我要潜水3秒钟,waiting...");
dispatch_after(timeQueue, dispatch_get_main_queue(), ^{
NSLog(@"我来也......^v^");
});
- 队列组Queue Group,解决执行多个并行队列任务时,想在全部执行结束后执行结束处理。
// 队列组,主要是为了想再多个并行任务都执行完成之后,执行结束操作
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t queueGroup = dispatch_group_create();
dispatch_sync(globalQueue, ^{
NSLog(@"task 1");
});
dispatch_sync(globalQueue, ^{
NSLog(@"task 2");
});
// 所有的队列任务都完成之后,执行结束操作
dispatch_group_notify(queueGroup, globalQueue, ^{
NSLog(@"ALL tasks are done");
});
6.信号量(旗语)Semaphore,是一种控制可访问资源的信号标识,根据我们设定的信号计数,系统分配执行的线程数。
如何使用信号量? 先创建一个信号量,然后等待信号(线程等待),最后信号来了,唤醒等待的线程。
1>创建信号量,参数:信号计数的个数
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
2>等待(减少)信号
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
3>(增加)信号来了,唤醒等待程序
dispatch_semaphore_signal(semaphore);
PS:执行一个同步任务,只有两个2资源,只能创建2个线程,怎么执行三个任务?
// 设置信号量的数量为2,同时只能开启2个线程
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.silence.gcd.myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
// 等待(减少)信号量。 *减少计数信号量。 如果结果值小于零,此功能在返回前等待信号发生。
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"Task 1");
// 信号来了,可以通行。 *增加计数信号量。 如果前一个值小于零,此函数在返回之前唤醒等待线程。
dispatch_semaphore_signal(semaphore);
});
dispatch_async(concurrentQueue, ^{
// 等待(减少)信号
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"Task 2");
// (增加)信号来了,唤醒等待信号
dispatch_semaphore_signal(semaphore);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"Task 3");
});
// 打印信息
2019-06-03 15:41:11.748542+0800 semaphore[25788:4575492] Task 1
2019-06-03 15:41:11.748603+0800 semaphore[25788:4575494] Task 3
2019-06-03 15:41:11.748680+0800 semaphore[25788:4575493] Task 2
7.使用场景1,GCD加载耗时任务,完成之后返回到主线程
dispatch_queue_t queue = dispatch_queue_create("com.silence.tongbu.mySerialQueue",DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
// 耗时操作,加载一张图片
NSURL *url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1523902227523&di=474686c26c7a7d2b815305dd45f0e046&imgtype=0&src=http%3A%2F%2Fcdnq.duitang.com%2Fuploads%2Fitem%2F201504%2F30%2F20150430125352_aeTLk.jpeg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
// 加载完成之后,回到主线程,将图片显示出来
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
使用场景2,并行队列任务中,想实现任务A和任务C完成之后,再执行任务B
- 实现方式1,使用栅栏函数(就像一道栅栏一样将任务隔开,栅栏函数之前的任务完成之后,再执行栅栏之后的任务)
UIImage * (^loadPicTask) (void) = ^(){
// 耗时操作,加载一张图片
NSURL *url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1523902227523&di=474686c26c7a7d2b815305dd45f0e046&imgtype=0&src=http%3A%2F%2Fcdnq.duitang.com%2Fuploads%2Fitem%2F201504%2F30%2F20150430125352_aeTLk.jpeg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
return image;
};
// 通过栅栏函数的方式,先实现任务A和任务C完成之后,再执行任务B
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.silence.barrier.myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
// 任务A
dispatch_async(concurrentQueue, ^{
NSLog(@"任务A:获取图片==》%@,当前线程==》%@",loadPicTask(),[NSThread currentThread]);
});
// 任务C
dispatch_async(concurrentQueue, ^{
// 耗时操作,加载一张图片
NSLog(@"任务C:获取图片==》%@,当前线程==》%@",loadPicTask(),[NSThread currentThread]);
});
// 栅栏函数
dispatch_barrier_async(concurrentQueue, ^{
// 这里省略了一万字.......
NSLog(@"------------------------这是著名的栅栏(函数),这之前的先执行,这之后的后执行----------------------------------------");
});
// 任务B
dispatch_async(concurrentQueue, ^{
// 耗时操作,加载一张图片
NSLog(@"任务B:获取图片==》%@,当前线程==》%@",loadPicTask(),[NSThread currentThread]);
});
// 打印结果如下,情况1
// 任务C:获取图片==》<UIImage: 0x6000019c18f0>, {1200, 1200},当前线程==》<NSThread: 0x60000278df40>{number = 3, name = (null)}
// 任务A:获取图片==》<UIImage: 0x6000019c5260>, {1200, 1200},当前线程==》<NSThread: 0x60000279f440>{number = 4, name = (null)}
// ------------------------这是著名的栅栏(函数),这之前的先执行,这之后的后执行----------------------------------------
// 任务B:获取图片==》<UIImage: 0x6000019cda40>, {1200, 1200},当前线程==》<NSThread: 0x60000279f440>{number = 4, name = (null)}
// 打印结果如下,情况2
// 任务A:获取图片==》<UIImage: 0x60000341e3e0>, {1200, 1200},当前线程==》<NSThread: 0x600000a46740>{number = 3, name = (null)}
// 任务C:获取图片==》<UIImage: 0x60000341e3e0>, {1200, 1200},当前线程==》<NSThread: 0x600000a74a00>{number = 4, name = (null)}
// ------------------------这是著名的栅栏(函数),这之前的先执行,这之后的后执行----------------------------------------
// 任务B:获取图片==》<UIImage: 0x60000341e3e0>, {1200, 1200},当前线程==》<NSThread: 0x600000a74a00>{number = 4, name = (null)}
// 不管你打印的是那种结果,任务B总是在任务A和任务C之后执行。
- 实现方式2,使用信号量,通过设置信号计数为2,这样每次只能执行2个任务(A和C),最后在执行任务B
// 通过信号量的方式,先实现任务A和任务C完成之后,再执行任务B
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.silence.barrier.myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
// 任务A
dispatch_async(concurrentQueue, ^{
// 等待(减少)信号
dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
NSLog(@"任务A:获取图片==》%@,当前线程==》%@",loadPicTask(),[NSThread currentThread]);
// (增加)信号来了,唤醒等待线程
dispatch_semaphore_signal(semaphore);
});
// 任务C
dispatch_async(concurrentQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 耗时操作,加载一张图片
NSLog(@"任务C:获取图片==》%@,当前线程==》%@",loadPicTask(),[NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
// 任务B
dispatch_async(concurrentQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 耗时操作,加载一张图片
NSLog(@"任务B:获取图片==》%@,当前线程==》%@",loadPicTask(),[NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
// 打印结果如下
2019-06-03 16:16:51.676707+0800 semaphore[26364:4699323] 任务C:获取图片==》<UIImage: 0x60000285e610>, {1200, 1200},当前线程==》<NSThread: 0x600001632180>{number = 3, name = (null)}
2019-06-03 16:16:51.746762+0800 semaphore[26364:4699325] 任务A:获取图片==》<UIImage: 0x600002840000>, {1200, 1200},当前线程==》<NSThread: 0x600001631f80>{number = 4, name = (null)}
2019-06-03 16:16:51.936077+0800 semaphore[26364:4699322] 任务B:获取图片==》<UIImage: 0x600002853020>, {1200, 1200},当前线程==》<NSThread: 0x60000160de80>{number = 5, name = (null)}
- 实现方式3,使用dispatch_group_t调度组(队列组)方式,将任务A和任务C放在一个调度组中执行,任务B在该调度组执行完成之后执行。
// 通过队列组的方式,先实现任务A和任务C完成之后,再执行任务B
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.silence.barrier.myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
// 创建队列组
dispatch_group_t queueGroup = dispatch_group_create();
// 任务A
dispatch_group_async(queueGroup, concurrentQueue, ^{
NSLog(@"任务A:获取图片==》%@,当前线程==》%@",loadPicTask(),[NSThread currentThread]);
});
// 任务C
dispatch_group_async(queueGroup,concurrentQueue, ^{
NSLog(@"任务C:获取图片==》%@,当前线程==》%@",loadPicTask(),[NSThread currentThread]);
});
// 等收到A,C任务都执行完成之后,再任务B
dispatch_group_notify(queueGroup,concurrentQueue, ^{
NSLog(@"任务B:获取图片==》%@,当前线程==》%@",loadPicTask(),[NSThread currentThread]);
});
关键字和概念可以参考:
OC的多线程1————关键词 以及 解决方案