简介
什么是多线程?多线程有什么作用?
有了多线程我们可以同时做多个事情,而不是一个一个任务的完成.比如:前段和后台的交互 大任务(学要消耗一定时间和资源)等等,也就是说 我们可以使用线程把占据时间长的任务放到后台中处理.而不影响用户的使用.
线程间通讯
有一个非常重要的队列 就是主队列.在这个主队列中处理多点触控及所有与UI有关的操作等等.他非常特殊,原因有两点:(1) 我们绝对不想他阻塞,我们不会将需要执行很长时间的任务放在主队列上执行.(2)我们将其用于所有与UI相关的同步,也就是线程间通讯要注意的地方.所有有可能会是屏幕UI发生变化的.都应放在主队列上执行
线程的定义:
每个正在系统上运行的程序都是一个进程,每个进程包含一个到多个线程.进程也可能是整个程序或者是部分程序的动态执行.线程是一组指令的集合,或者是程序的特殊段,他可以在程序里独立执行,也可以把它理解为代码运行的上下文.所以县城基本上轻量级的进程,他负责再单个程序里执行多任务.通常有操作系统负责多个线程的调度和执行.
iOS支持的多线程技术
一 Thread
1)显示创建线程:NSThreed
2)隐式创建多线程:NSObject
二 Cocoa opreations
NSOpreation 类是一个抽象类,因此我们必须使用他的两个子类
1)NSInvocationOpreation
2)NSBlockOpreations
3)NSOpreationQueue(继承于NSObject)
三 Grand Central Dispatch (GCD)
1)GCD的创建
2)重复执行线程及一次性执行:dispath_apply & dispatch_once
3)操作(串行)队列:dispatch_queue_create
4)GCD群组通知: dispatch_group_t
5)GCD实现计时器
6)后台运行
7)延迟执行
四比较多线程技术
一 Thread
我们可以使用NSthread 和NSobject类去调用
1)显示创建线程:NSthread
创建NSthread有两中办法
//第一种:创建之后需要使用start方法,才会执行方法
NSThread *threadAlloc = [[NSThread alloc]initWithTarget:self selector:@selector(thread:) object:nil];
[threadAlloc start];
//第二种:创建并马上执行方法
[NSThread detachNewThreadSelector:@selector(thread) toTarget:self withObject:nil];
2)隐式创建线程:NSobject
我们也可以使用NSobject类直接调用方法
[self performSelectorInBackground:@selector(thread) withObject:nil];
取消线程的方法
实际上并没有真正提供取消线程的API.苹果提供了一个cancel的API.蛋挞不能作用于取消线程,它只能改变运行状态,我们可以使用它来进行条件判断
- (void)threadCancel
{
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadCancelNow) object:nil];
[thread start];
}
- (void)threadCancelNow
{
int a = 0;
while (![[NSThread currentThread] isCancelled]) {
NSLog(@"a - %d", a);
a++;
if (a == 5000) {
NSLog(@"终止循环");
[[NSThread currentThread] cancel];
break;
}
}
}
NSThread线程间通讯 -- 调用主线程修改UI:
只需要传递一个selector和它的参数.withObject参数可以为nil waitUntilDone代表是否要等待调用他的这个线程执行之后再将它从主队列调出,并在主队列上运行,通常设为NO,不需要等待
[self performSelectorOnMainThread:@selector(reload) withObject:nil waitUntilDone:NO];
g *name NS_AVAILABLE(10_5, 2_0);
/**
* 获取当前线程的线程对象
*
* 通过这个属性可以查看当前线程是第几条线程,主线程为1。
* 可以看到当前线程的序号及名字,主线程的序号为1,依次叠加。
*/
+ (NSThread *)currentThread;
// 线程休眠(秒)
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 线程休眠,指定具体什么时间休眠
+ (void)sleepUntilDate:(NSDate *)date;
// 退出线程
// 注意:这里会把线程对象销毁!销毁后就不能再次启动线程,否则程序会崩溃。
+ (void)exit;
二Cocoa operations
1)NSInvocationOperation
创建NSInvocationOperation线程,附带一个NSString参数
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationAction:) object:@"abc"];
//需要启动线程,默认是不启动的
[operation start];
如果在创建时定义了参数,那么接受的时候,可以对sender进行转换,如字符串 数组等
-(void)invocationAction:(NSInvocationOperation *)sender{
NSLog(@"sender - %@",sender); //输出params
NSString *str = (NSString *)sender;
NSLog(@"str - %@e", str); // params
}
输出结果:
2015-10-30 21:11:22.283多线程[43996:509231] sender - abc
2015-10-30 21:11:22.284多线程[43996:509231] str - abce
附带一提,线程的普通创建一般为并发执行的,因为串行队列是需要显示创建的,如没看见此类代码,那么就是并发队列线程,因此 上述代码也就是并发线程,关于并发和串行队列(线程),我将会在下面详细说明,我们继续往下看你也可以用NSOperationQueue来创建一个线程队列,用来添加子线程:
// 获取/设置线程的名字
@property (copy) NSStrin
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
//线程A
NSInvocationOperation *operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationAction:) object:@"operation1"];
//线程B
NSInvocationOperation *operation2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationAction:) object:@"operation2"];
//向队列中添加子线程
[queue addOperations:@[operation1,operation2] waitUntilFinished:YES];
2)NSBlockOperation
创建NSBlockOperation
//创建线程任务
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"one _ %@",[NSThread currentThread]);
}];//执行线程任务
[blockOperation start];
2015-10-31 11:12:22.401多线程[7704:81963] one _
比如说你在主线程中调用它.那么他就在主线程中执行.如果你在子线程中调用它.那么他就在子线程中执行
做个简单的实验,我们新建一条子线程,然后在子线程中调用NSBlockOperation
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"one _ %@",[NSThread currentThread]);
}];//执行线程任务
[blockOperation start];
});
2015-10-31 11:20:29.167多线程[8206:87771] one _
我们也可以使它并发执行,通过使用addExecutionBlock方法添加多个Block,这样就能使它在主线程和其他线程中工作
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"one _ %@",[NSThread currentThread]);
}];//执行线程任务
[blockOperation addExecutionBlock:^{
NSLog(@"two_%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"three_%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"four_%@",[NSThread currentThread]);
}];
[blockOperation start];
2015-10-31 11:28:58.493多线程[8760:94808] two_
2015-10-31 11:28:58.493多线程[8760:94832] three_
2015-10-31 11:28:58.493多线程[8760:94833] four_
2015-10-31 11:29:00.497多线程[8760:94831] one _
3)NSOperationQueue
这里介绍一下NSOperation的依赖关系,依赖关系会影响现成的执行顺序
//创建操作队列
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
//线程A
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"one_%@",[NSThread currentThread]);
}];
//线程B
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"two_%@",[NSThread currentThread]);
}];
//线程C
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"three_%@",[NSThread currentThread]);
}];
//线程B依赖线程C 也就是等线程C执行完之后才会执行线程B
[op2 addDependency:op3];
//线程C依赖线程A,同上只不过依赖对象改成了线程A
[op3 addDependency:op1];
//为队列添加线程
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
2015-10-31 11:41:20.021多线程[9491:103289] one_
2015-10-31 11:41:20.022多线程[9491:103289] three_
2015-10-31 11:41:20.022多线程[9491:103289] two_
当你没有添加依赖关系时,队列是并行执行的.注意:依赖关系可以多重依赖,但不要建立循环依赖
不添加依赖关系运行结果(并行执行)
2015-10-31 11:45:28.088多线程[9762:106647] two_
2015-10-31 11:45:28.088多线程[9762:106649] three_
2015-10-31 11:45:28.088多线程[9762:106646] one_
Cocoa operations线程间通信 --调用主线程修改UI
// 创建线程对象(并发)
NSBlockOperation *blockOperation = [[NSBlockOperation alloc] init];
// 添加新的操作
[blockOperation addExecutionBlock:^{
NSLog(@"two - %@", [NSThread currentThread]);
}];
// 添加新的操作
[blockOperation addExecutionBlock:^{
NSLog(@"three - %@", [NSThread currentThread]);
// 在主线程修改UI
NSOperationQueue *queue = [NSOperationQueue mainQueue];
[queue addOperationWithBlock:^{
//在这里修改UI
NSLog(@"%@",[NSThread currentThread]);
}];
}];
[blockOperation start];
2015-10-31 11:51:04.167多线程[10109:110581] two -
2015-10-31 11:51:04.167多线程[10109:110669] three -
2015-10-31 11:51:04.174多线程[10109:110581]
NSOperation方法及属性
// 设置线程的最大并发数
@property NSInteger maxConcurrentOperationCount;
// 线程完成后调用的Block
@property (copy) void (^completionBlock)(void);
// 取消线程
- (void)cancel;
只列举上面那些,其它的方法就不全列出来了。
注意:在NSOperationQueue类中,我们可以使用cancelAllOperations方法取消所有的线程。这里需要说明一下,不是执行cancelAllOperations方法时就会马上取消,是等当前队列执行完,下面的队列不会再执行
在一个Operation Queue中是可以同时执行多个Operation的,Operation Queue会动态的创建多个线程来完成相应Operation。具体的线程数是由Operation Queue来优化配置的,这一般取决与系统CPU的性能,比如CPU的核心数,和CPU的负载。但我们还是可以设置一个最大并发数的,那么Operation Queue就不会创建超过最大并发数量的线程。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
如果我们将
maxConcurrentOperationCount
设置为
1
,那么在队列中每次只能执行一个任务。这就是一个串行的执行队列了。
Grand Central Dispatch (GCD)
1)GCD异步线程的创建:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程 -- %@",[NSThread currentThread]);
});
GCD也可以创建同步线程,只需要把async改成sync即可
2)重复执行线程:dispatch_apply
以下代码会执行4次
dispatch_apply(4, DISPATCH_QUEUE_PRIORITY_DEFAULT, ^(size_t index) {
//index则为执行的次数 0开始递增
NSLog(@"one--%ld",index);
NSLog(@"%ld---%@",index,[NSThread currentThread]);
});
其中需要注意的是 每次执行都会开辟一条子线程 因为是异步的原因 他们不会顺序的
2015-10-31 15:20:08.307 多线程[22413:262136] one--0 --
2015-10-31 15:20:08.307 多线程[22413:262224] one--3 --
2015-10-31 15:20:08.307 多线程[22413:262222] one--2 --
2015-10-31 15:20:08.307 多线程[22413:262223] one--1 --
然而,GCD还有一次性执行的方法,如下
dispatch_once_t once;
dispatch_once(&once, ^{
NSLog(@"once -- %@",[NSThread currentThread]);
});
3)操作队列:dispatch_queue_create
使用GCD也能创建串行队列,具体代码如下
dispatch_queue_t queue = dispatch_queue_create("com.GarveyCalvin.queue", DISPATCH_QUEUE_CONCURRENT);
// 线程A
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"sleep async - %@", [NSThread currentThread]);
});
// 线程B
dispatch_barrier_async(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"sleep barrier2 - %@", [NSThread currentThread]);
});
// 线程C
dispatch_async(queue, ^{
NSLog(@"async");
});
以上会先执行 线程A-》线程B-》线程C,它是一个串行队列。
dispatch_queue_create的第二个参数:
1)DISPATCH_QUEUE_SERIAL(串行)
2)DISPATCH_QUEUE_CONCURRENT(并发)
4)GCD群组通知:dispatch_group_t
GCD的高级用法,等所有线程都完成工作后,再作通知。
// 创建群组
dispatch_group_t group = dispatch_group_create();
// 线程A
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"group1");
[NSThread sleepForTimeInterval:2];
});
// 线程B
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"group2");
});
// 待群组里的线程都完成之后调用的通知
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"group success");
});
群组里的线程也是并行队列。线程A和线程B都执行完之后,会调用通知打印group success
5)GCD实现计时器
__block int time = 30;
CGFloat reSecond = 1.0;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, reSecond * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
time--;
NSLog(@"%d", time);
if (time == 0) {
dispatch_source_cancel(timer);
}
});
dispatch_resume(timer);
GCD线程间通信-调用主线程修改UI:
有时候我们请求后台作数据处理,数据处理是异步的,数据处理完成后需要更新UI,这时候我们需要切换到主线程修改UI,例子如下
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"异步数据处理 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"数据处理完成");
// 调用主线程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"更新UI - %@", [NSThread currentThread]);
[self editUINow];
});
});