注:本文是根据慕课网上的多线程视频总结而来.
iOS 多线程主要有四种方式:
1.pThread 2.NSThread 3.GCD 4.NSOpertation
<一> pThread
pThread 是基于 C++语言实现的,在 iOS 项目内一般很少使用,在使用这种方式的时候,需要导入<pthread.h>系统头文件.
下面是使用方式:
- (IBAction)pThread:(id)sender {
NSLog(@"pThread___主线程");
pthread_t pthread;
//第一个参数为 pthread 指针,第三个参数run 是任务名称(可理解为方法名称)
pthread_create(&pthread, NULL, run, NULL);
}
void *run(void *data) {
NSLog(@"pThread___子线程");
for (int i = 1; i < 10; i++) {
NSLog(@"pThread === %d",i);
sleep(1);
}
return NULL;
}
<二> NSThread
NSThread 是苹果封装过的面向对象的一种多线程方式.
NSThread 有三种创建方式,如下:
1.通过 alloc init 方式创建
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(runNSThread) object:nil];
2.通过 detachNewThreadSelector 方式创建并执行线程
[NSThread detachNewThreadSelector:@selector(runNSThread) toTarget:self withObject:nil];
3.通过 performSelectorInBackground 方式创建并执行线程
[self performSelectorInBackground:@selector(runNSThread) withObject:nil];
NSThread的三种创建方式种,方式一可以对线程属性进行设置,便于后期调试或业务逻辑的判断,后两种方式的优势是格式比较简单,方便使用.
关于方式一的线程属性设置:
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(runNSThread) object:nil];
//设置线程名称,可根据线程名称做业务判断
[thread1 setName:@"Name_thread_1"];
//设置线程优先级,其值介于 0 ~ 1 之间,注意:优先级的设置并不代表高优先级线程执行完毕之后才执行低优先级的线程.高优先级只是优先执行,在高优先级线程执行期间,低优先级线程也会进行执行.
[thread1 setThreadPriority:0.2];
[thread1 start];
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(runNSThread) object:nil];
[thread2 setName:@"Name_thread_2"];
[thread2 setThreadPriority:0.5];
[thread2 start];
NSThread 回到主线程的方式为:
[self performSelectorOnMainThread:@selector(runMainThread) withObject:nil waitUntilDone:YES];
<三> GCD
GCD是苹果为多核设备设计的多线程解决方案,可以合理的利用设备的 CPU 性能.iOS 开发中经常使用 GCD 这种方式.
GCD 的常见用法:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"start task_1");
[NSThread sleepForTimeInterval:3]; //模拟耗时任务
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"task_1 end, refresh UI");
});
});
GCD 的全局并发队列
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
NSLog(@"start task_1");
[NSThread sleepForTimeInterval:2];
NSLog(@"task_1 end");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"start task_2");
[NSThread sleepForTimeInterval:2];
NSLog(@"task_2 end");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"start task_3");
[NSThread sleepForTimeInterval:2];
NSLog(@"task_3 end");
});
GCD 的串行队列
//串行队列,将所有任务都放在同一个线程内,先进先出,保证执行顺序 (串行队列只开辟了一个线程)
//dispatch_queue_create(_,_) 第一个参数是线程标识符, 第二个参数是设置该线程是 串行 还是并行,默认(NULL)为串行(DISPATCH_QUEUE_SERIAL),并行为 DISPATCH_QUEUE_CONCURRENT
dispatch_queue_t queue = dispatch_queue_create("dj.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"start task_1");
[NSThread sleepForTimeInterval:2];
NSLog(@"task_1 end");
});
dispatch_async(queue, ^{
NSLog(@"start task_2");
[NSThread sleepForTimeInterval:2];
NSLog(@"task_2 end");
});
dispatch_async(queue, ^{
NSLog(@"start task_3");
[NSThread sleepForTimeInterval:2];
NSLog(@"task_3 end");
});
当需要实现多个任务全部执行完毕后再执行下一步操作时,我们可以使用 GCD 的 group 功能.
dispatch_queue_t queue = dispatch_queue_create("dj.gcd.group", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"start task_1");
[NSThread sleepForTimeInterval:2];
NSLog(@"task_1 end");
});
dispatch_group_async(group, queue, ^{
NSLog(@"start task_2");
[NSThread sleepForTimeInterval:2];
NSLog(@"task_2 end");
});
dispatch_group_async(group, queue, ^{
NSLog(@"start task_3");
[NSThread sleepForTimeInterval:2];
NSLog(@"task_3 end");
});
dispatch_group_notify(group, queue, ^{
//所有任务结束后的回调
NSLog(@"All tasks end");
//回调所用的线程并不是新开辟的线程,系统在这里做了优化,使用了group多个任务线程的其中一个(随机选择)线程来做回调.故刷新 UI 需要回到主线程.
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主线程刷新 UI");
});
});
上面的方案只有在执行同步操作时才会生效,当我们执行异步操作时,会发现在任务执行结束前,我们就已经收到了 notify 的回调,这是因为我们在执行异步操作时,代码会脱离dispatch_group_async 的范围,导致提前收到 notify 回调.
下面是在 group 内执行异步操作时的解决方案.
//解决 group 在执行异步代码时遇到的提前收到 notify 回调的问题
dispatch_queue_t queue = dispatch_queue_create("dj.gcd.group", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group); //进入
dispatch_group_async(group, queue, ^{
[self sendRequest1:^{
NSLog(@"request1 done");
dispatch_group_leave(group); //离开
}];
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
[self sendRequest2:^{
NSLog(@"request2 done");
dispatch_group_leave(group);
}];
});
dispatch_group_notify(group, queue, ^{
//所有任务结束后的回调
NSLog(@"All tasks end");
//回调所用的线程并不是新开辟的线程,系统在这里做了优化,使用了group多个任务线程的其中一个(随机选择)线程来做回调.故刷新 UI 需要回到主线程.
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主线程刷新 UI");
});
});
}
- (void)sendRequest1:(void(^)())block {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"start task_1");
[NSThread sleepForTimeInterval:2];
NSLog(@"task_1 end");
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block();
}
});
});
}
- (void)sendRequest2:(void(^)())block {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"start task_2");
[NSThread sleepForTimeInterval:2];
NSLog(@"task_2 end");
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block();
}
});
});
}
有时候我们需要在项目中执行延时操作,这时我们可以使用 dispatch_after 功能来实现.
//GCD 延迟操作
- (IBAction)GCDAfter:(id)sender {
NSLog(@"---begin---");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"delay excute");
//GCDAfter 弊端:当调用dispatch_after时,该方法无法取消,必被执行. 例如:采用 dispatch_after 在 2秒后执行操作,但在 2秒未到时退出界面,那么dispatch_after操作仍会被执行.此时由于控制器已被 dealloc, 那么调用 dispatch_after 的业务逻辑就可能会造成程序 crash.
});
}
GCD 的另一个常用功能是在单例中的使用
+(instancetype)instance {
static dispatch_once_t onceToken;
static TestSingle *single = nil;
dispatch_once(&onceToken, ^{
single = [[TestSingle alloc] init];
NSLog(@"init testSingle");
});
return single;
}
<四> NSOperation
NSOperation 里常用的包括:NSInvocationOperation, NSBlockOperation, NSOperationQueue.
NSOperation有两种执行方式,第一种是采用 start 的方式执行,该方式是同步执行,会阻塞当前所在线程(主线程或子线程). 第二种方式是采用 NSOperationQueue 的方式,该方式是异步执行.
- NSInvocationOperation
NSInvocationOperation *invocationOper = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(method name) object:nil];
[invocationOper start]; //以start方式执行时是同步执行,会阻塞当前所在线程(主线程或子线程)
- NSBlockOperation
NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{
//do something
}];
[blockOper start]; //以start方式执行时是同步执行,会阻塞当前线程(主线程或子线程)
3.NSOperationQueue
@property (nonatomic, strong) NSOperationQueue *opertationQueue;
if (!self.opertationQueue) {
self.opertationQueue = [[NSOperationQueue alloc] init];线程数
}
[self.opertationQueue addOperation:blockOper]; //通过NSOperationQueue 方式执行时,是异步执行
NSOperationQueue可以设置最大同时执行的线程数
[self.opertationQueue setMaxConcurrentOperationCount:4];
4.自定义 NSOperation
//自定义
CustomOpertation *cusA = [[CustomOpertation alloc] initWithName:@"operA"];
CustomOpertation *cusB = [[CustomOpertation alloc] initWithName:@"operB"];
CustomOpertation *cusC = [[CustomOpertation alloc] initWithName:@"operC"];
CustomOpertation *cusD = [[CustomOpertation alloc] initWithName:@"operD"];
//依赖关系的设置 [A addDependency:B]; A 的执行依赖于 B, 需要等待 B 执行结束后才执行.使用依赖时需注意避免循环依赖.
[cusD addDependency:cusA];
[cusA addDependency:cusC];
[cusC addDependency:cusB];
[self.opertationQueue addOperation:cusA];
[self.opertationQueue addOperation:cusB];
[self.opertationQueue addOperation:cusC];
[self.opertationQueue addOperation:cusD];
#import "CustomOpertation.h"
@interface CustomOpertation ()
@property (nonatomic, copy) NSString *operName;
@property (nonatomic, assign) BOOL over;
@end
@implementation CustomOpertation
- (instancetype)initWithName:(NSString *)name {
if (self = [super init]) {
self.operName = name;
}
return self;
}
- (void)main {
//模拟耗时操作
// for (int i = 0; i < 3; i++) {
// NSLog(@"%@ %d",self.operName, i);
// [NSThread sleepForTimeInterval:1];
// }
//模拟异步操作,此时 main 函数很快执行完毕,则依赖关系也失效
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:1];
if (self.cancelled) {
return ;
} else {
NSLog(@"%@",self.operName);
//关闭异步操作
self.over = YES;
}
});
//占用异步操作,使main 函数无法立即结束(避免依赖关系失效)
while (!self.over && !self.cancelled) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
5.多线程资源抢占的解决方案
#import "TicketManager.h"
#define total 50
@interface TicketManager()
@property int tickets; //剩余票数
@property int saleCount; //已卖票数
@property (nonatomic, strong) NSThread *threadBJ; //线程1
@property (nonatomic, strong) NSThread *threadSH; //线程2
@property (nonatomic, strong) NSCondition *ticketCondition; //加锁操作
@end
@implementation TicketManager
- (instancetype)init {
if (self = [super init]) {
self.ticketCondition = [[NSCondition alloc] init];
self.tickets = total;
self.threadBJ = [[NSThread alloc] initWithTarget:self selector:@selector(sale) object:nil];
[self.threadBJ setName:@"BJ"];
self.threadSH = [[NSThread alloc] initWithTarget:self selector:@selector(sale) object:nil];
[self.threadSH setName:@"SH"];
}
return self;
}
- (void)sale {
/**
多个线程同时操作一个数据时,可能会造成数据混乱,此时可通过加锁的方式对数据进行保护,使之每次只能有一个线程对数据进行操作,其他线程需等待操作数据的线程操作结束后才能对数据进行操作.
线程锁有多种方式,常见的有: @synchronized (self) {} 及 NSCondition 两种方式.
synchronized保护其内部的数据操作
NSCondition 在操作前通过 lock 方法加锁,在操作完成后通过 unlock 进行解锁. lock 与 unlock 必须一一对应.
*/
while (true) {
//@synchronized (self) { //加锁
[self.ticketCondition lock]; //加锁 --- NSCondition
if (self.tickets > 0) {
[NSThread sleepForTimeInterval:0.5];
self.tickets--;
self.saleCount = total - self.tickets;
NSLog(@"余票 === %d , 卖出票数 === %d ,线程名称 === %@",self.tickets,self.saleCount,[NSThread currentThread].name);
}
[self.ticketCondition unlock]; //解锁 --- NSCondition
//}
}
}
- (void)startToSaleTickets {
[self.threadBJ start];
[self.threadSH start];
}
@end