一、基本概念
在分析问题之前,我们先区分两个概念:进程和线程
进程:一个正在运行的程序可以看成是一个进程。 例如正在运行的QQ,进程拥有独立运行所需要的全部资源。
线程:程序中独立运行的代码段是线程。例如:接收QQ消息的代码段。
一个进程是由一个线程或者多个线程组成的,进程负责资源的调度和分配,线程才是程序真正的执行单元,负责代码的执行。
我们知道,在ios里面 会自动为UI创建一个主线程,默认主线程分配的内存空间是 1MB。我们新开辟的子线程默认是512k,分配的大小必须是4的倍数,分配给子线程的空间是用来存储变量的,所以512k是充分够用的。
iOS运行的时候 程序在主入口自动添加自动释放池 而我们自己创建的释放池是没有自动释放池的 所以要我们手动添加,添加释放吃的原因是子线程共用堆内存 如果不及时回收随着子线程数量的上升 以及大量使用便利构造器 会对系统本身造成很大的负担。
了解以上之后,我们来定义一个方法 求0-63000000的和
-(void)test{
NSLog(@"%@,%d",[NSThread currentThread],[[NSThread mainThread]isMainThread]);
@autoreleasepool {
long int sum = 0;
long int num = 63550000;
for (long int i = 1; i <= num; i++) {
sum +=i;
}
NSLog(@"%ld",sum);
}
调用以上方法我们发现返回计算结果的时间我们还能接受 但是在63000000后面加上一个或者更多0 再次试一次 就会发现 这个时间计算机的计算速度非常慢,这时候我们就可以考虑开辟多条线程来加快程序运行效率。
二、开辟多线程有哪些方式?
1、在后台执行 开辟多线程最简单的一个方法
[self performSelectorInBackground:@selector(test) withObject:nil];
2、NSThread
NSThread是一个轻量级的线程 创建方式有两种:
/* 第一种*/
//1 由init来创建并且需要手动来开启
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(test) object:nil];
//2 开启线程
[thread start];
//3 取消线程
[thread cancel];//这个很少有人在用 一般也不会写在这个地方
/* 第二种 */
//初始化的时候自动开启线程
[NSThread detachNewThreadSelector:@selector(test) toTarget:self withObject:nil];
3、NSOperation
NSOperation是一个抽象类 他有两个子类NSInvocationOperation 和NSBlockOperation 在MVC里面属于M层 封装了单个任务的数据和相关的代码 一般我们使用它的子类
NSInvocationOperation *incocation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
__weak typeof(self)temp = self;
NSBlockOperation *block =[NSBlockOperation blockOperationWithBlock:^{
[temp test];
NSLog(@"这个是BlockOprationQueue执行的");
}];
//NSOperationQueue是线程操作队列 用来管理一组Operation对象 会根据实际需求创建出合适数量的子线程 完成任务的并发执行
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//当最大并发执行数为1的时候程序是串行执行的
[queue setMaxConcurrentOperationCount:1];
//设置依赖关系[A addDependency B]若A依赖B则B先执行A在执行 若B依赖A则A先执行B在执行
[invocation addDependency:blockOperation];
#pragma mark ---上面两部要写在添加之前---
[queue addOperation:invocation];
[queue addOperation:blockOperation];
我们运行一下程序结果:
由于我们做了[invocation addDependency:blockOperation];只有当blockOperation执行完成之后,invocation才能执行。结果中的number数量就是线程个数 ,0表示非主线程,1则相反。很显然 我们开辟子线程了
4、GCD
GCD 三种队列:1 、 主队列 2、 全局队列 3 、 自定义队列(串行队列还是并行队列)
(1)主队列:获取朱队列生成一个串行的对列 队列里面的Block快按照先进先出的顺序执行实际上就是一个单线程队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 网队列里面添加任务
dispatch_async(queue, ^{
[temp test];
NSLog(@"1");
});
dispatch_async(queue, ^{
[temp test];
NSLog(@"2");
});
dispatch_async(queue, ^{
[temp test];
NSLog(@"3");
});
结果显示:
当然根据需要我们也可以设置程序的延迟执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"今晚在如家402一起敲代码,好么?");
});
(2)全局队列 并发执行
dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(global, ^{
[temp test];
NSLog(@"11");
});
dispatch_async(global, ^{
[temp test];
NSLog(@"22");
});
dispatch_async(global, ^{
[temp test];NSLog(@"33");
});
重复执行问题
重复执行的任务第一个参数size_t iterations是重复执行的次数dispatch_queue_t queue 是指定的队列
dispatch_apply(5, global, ^(size_t t) {//相当于int t
NSLog(@"执行到第%ld次",t);
});
(3)自定义队列
//串行队列
dispatch_queue_t queue = dispatch_queue_create("AAA", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
[temp test];
NSLog(@"111");
});
dispatch_async(queue, ^{
[temp test];
NSLog(@"222");
});
dispatch_async(queue, ^{
[temp test];
NSLog(@"333");
});
//分组任务 返回结果的先后顺序就不是确定的了
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
[temp test];
NSLog(@"1111");
});
dispatch_group_async(group, queue, ^{
[temp test];
NSLog(@"2222");
});
dispatch_group_async(group, queue, ^{
[temp test];
NSLog(@"3333");
});
//这句话就是一个谦虚的狗做的 拒绝拿第一
dispatch_group_notify(group, queue, ^{
NSLog(@"昭哥变成了数学家");
});
//障碍执行 执行任务的时候 要是前面还有任务的话就会等其他任务执行完之后在再执行
该方法创建的任务在执行的时候 会先检查是不是有其他任务正在执行
要是有等待其执行完毕 再进行执行 否则其他任务要等待其执行完毕之后再执行
dispatch_barrier_sync(queue, ^{
[temp test];
NSLog(@"这个叫做障碍执行");
});
//不争不抢 最后一步执行
dispatch_group_notify(queue3, queue2, ^{
NSLog(@"昭哥变成了数学家");
});
三、子线程回到主线程
简单介绍几种常用子线程回到主线程的方式
// GCD
dispatch_async(dispatch_get_main_queue(), ^{
//在这里面进行主线程任务的操作(UI的刷新)
});
// NSObject
// (1)
[self performSelectorOnMainThread:@selector(backMainThread) withObject:nil waitUntilDone:YES];
// (2)
[self performSelector:@selector(backMainThread) onThread:[NSThread mainThread] withObject:nil waitUntilDone:YES];
4、模拟购票系统
声明一个全局变量 int count = 10000; 假设10000张票
声明锁 @property(nonatomic,strong)NSLock *lock;并进行初始化
__weak typeof(self)temp = self;
dispatch_queue_t queue3 = dispatch_queue_create("CCC", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue3, ^{
for (int i = 0; i < 100; i++) {
[temp buyTickets];
}
});
dispatch_async(queue3, ^{
for (int i = 0; i < 100; i++) {
[temp buyTickets];
}
})
防止发生购票冲突 我们使用了互斥锁
-(void)buyTickets{
//线程互斥
@autoreleasepool {
[_lock lock];
count--;
NSLog(@"剩余票数%d",count);
[_lock unlock];
}
}