多线程概述
程序 进程 线程
程序
- 由源代码生成的可执行应用。
进程
- 一个正在运行的程序可以看做一个进程,进程拥有独立运行所需的全部资源。
线程
- 程序中独立运行的代码段。
- 除了主线程以外的 都叫子线程
- 子线程可以有很多个 但是线程是耗费资源的
- 一般最多不超过五条 注:3条最佳
- 程序退出后 会清空线程的任务
三者之间的关系
一个进程是由一个或多个线程组成。进程只负责资源的调度和分配,线程才是程序真正的执行单元,负责代码的执行。
单、多线程的区别
- 单线程程序:只有一个线程,代码顺序执行,容易出现代码阻塞(页面假死)。
- 多线程程序:有多个线程,线程之间独立运行,能有效的避免代码阻塞,并且提高程序的运行性能。
注意:iOS中关于UI的添加和刷新必须在主线程中操作。
主线程操作什么样的任务?
- UI界面 按钮点击 屏幕的滚动 (一切用户看得见的 都要在主线程当中去操作)
- 比较大的耗时操作 又或者 用户看不到的操作 可以放到子线程当中去做 比如:下载 解压缩 读取大型数据等 可以在子线程中去操作
多线程的原理:
- CPU在工作时 同一时间只能执行一个任务 之所以可以造成多条线程一起执行任务的假象 是CPU在进行高速的切换(调度) 在线程之间切换 来达到多个任务一起执行的效果
说说多线程的优点:
- 可以大大提高 执行任务的效率
- 可以让用户 有更好的用户体验
缺点:
- 如果大量开辟线程 会造成程序的卡顿(耗费过量的资源)
线程休眠
[NSThread sleepForTimeInterval:10];
判断当前线程是否为主线程
NSLog(@"%d", [NSThread isMainThread]);
- (void)viewDidLoad {
[super viewDidLoad];
// 创建Button感受卡顿
UIButton *button = [UIButton buttonWithType:(UIButtonTypeCustom)];
button.frame = CGRectMake(100, 100, 100, 100);
button.backgroundColor = [UIColor orangeColor];
[button addTarget:self action:@selector(buttonClick:) forControlEvents:(UIControlEventTouchUpInside)];
[self.view addSubview:button];
// 查看当前线程的方法
NSLog(@"%@", [NSThread currentThread]);
}
- (void)buttonClick:(UIButton *)button
{
NSLog(@"%@", [NSThread currentThread]);
NSLog(@"你点我了");
}
// 触摸控制器的view 会触发
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// for (int i = 0; i < 10000; i++) {
// NSLog(@"%@", [NSThread currentThread]);
// NSLog(@"---%d", i);
// }
// 该方法在哪个线程里调用 该方法的线程就是那个线程
// [self performSelector:@selector(download:) withObject:@"123"];
// 开辟了一个子线程
// 方法是基类提供的 只要是对象都能调用
// [self performSelectorInBackground:@selector(download:) withObject:@"123"];
#pragma mark ------ NSThread
// // 初始化 开辟一个子线程
// NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download:) object:@"123"];
// // 开始执行
// [thread start];
// 2.开辟子线程 并且这个方法不用开始 自动执行
[NSThread detachNewThreadSelector:@selector(download:) toTarget:self withObject:@"123"];
}
- (void)download:(NSString *)str
{
// 线程休眠 (延迟执行效果)
[NSThread sleepForTimeInterval:10];
// 通过在子线程中执行耗时操作 解决卡顿问题
NSLog(@"download---%@", [NSThread currentThread]);
// 判断当前线程是否为主线程
NSLog(@"%d", [NSThread isMainThread]);
for (int i = 0; i < 10000; i++) {
// NSLog(@"%@", [NSThread currentThread]);
NSLog(@"---%d", i);
if (i == 300) {
// 立即结束当前线程
[NSThread exit];
// 相当于 你在这里
// 只要退出当前的线程 后面的一切方法都不执行了
return;
}
}
NSLog(@"你猜还执行么?");
}
NSOperation
- NSOperation 是一个抽象类 (本身无主线程、子线程之分)实际任务由它的子类
NSBlockOperation或NSInvocationOperation执行 通常与NSOperationQueue结合使用
NSInvocationOperation
- 封装了执行操作的target和要执行的action
NSBlockOperation
- 封装了需要执行的代码块
线程队列(任务中心 可以执行很多任务)
- 串行队列 主线程相当于一个串行队列 队列中的任务需要一个执行完毕后 再执行下一个
并行队列 队列中的任务 进行并发执行 同时开始未必同时结束
同步:没有开启子线程的能力
- 异步:拥有开启子线程的能力
任务是什么?
- 比如 下载 打印 都是个任务
线程之间的通信
在子线程中 完成耗时的操作 完成后 需要回到主线程进行UI刷新
常用的开启多线程的方法
优势:不用程序员 管理 线程的生命周期
- NSOperation 封装一下GCD的方法 提供OC的语法来使用GCD
- GCD 是苹果推荐的 可以最大化地发挥多核CPU 是C语言的函数
NSOperationQueue
- 是操作队列,用来管理一组Operation对象的执行,会根据需要自动为Operation开辟合适数量的线程,以完成任务的并行执行。
- NSOperation可以调剂为在队列中的优先级。
- 当最大并发数设置为1时,能实现线程同步
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// 需要把 NSInvocationOperation 对象 放进一个队列里 才能开启子线程
// 创建一个队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 给队列提供一个 可以设置 最大并发数
// 一次最多并发执行两个任务
queue.maxConcurrentOperationCount = 2;
// 开启一个线程 (相当于一个任务)
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downLoad:) object:@"123"];
// 把任务添加到队列中
// 只需要把任务添加进队列中 不需要程序员操作 会自动执行
// 不是添加一个任务 就开启一个线程
// 这个操作是系统给咱们做的 有的线程会被系统重复利用(优化)
[queue addOperation:operation];
// 往队列中添加任务
[queue addOperationWithBlock:^{
// block 就是 你要添加的任务
NSLog(@"---3%@", [NSThread currentThread]);
}];
// 初始化一个任务
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"----4%@", [NSThread currentThread]);
}];
// 添加进队列
[queue addOperation:blockOperation];
}
- (void)downLoad:(NSString *)str
{
NSLog(@"%@", [NSThread currentThread]);
}
GCD
任务
- 具有一定功能的代码段,一般是一个block或者函数
- 分发队列:GCD以队列的方式进行工作,FIFO。
- 根据分发队列的类型,创建合适数量的线程执行队列中的任务。
队列类型
- SerialQueue:一次只执行一个任务,用于同步访问特定的资源或数据。创建多个时,虽然是同步执行的,但两两之间是并发执行的,实现线程同步
- Concurrent:可以并发第执行多个任务,但是遵守FIFO。
功能
- dispatch_async() 往对列中添加任务,任务排队执行
- dispatch_after() 往对列中添加任务,任务不但会排队,还会在延迟的时间点执行
- dispatch_apply() 往对列中添加任务,任务会重复执行n次
- dispatch_group_async() 将任务添加到队列中,并添加分组标记
- dispatch_group_notify() 将任务添加到队列中,当某个分组的所有任务执行完成之后,此任务才会执行
- dispatch_barrier_async() 将任务添加到队列中,此任务执行的时候,其他任务停止执行
- dispatch_once() 任务添加到队列中,但任务在程序运行过程中,只执行一次
- dispatch_sync() 将任务添加到队列中,block不执行完,下面代码不会执行
队列:
- 并行队列 系统提供了一个 全局并行队列(整个应用都可以使用)如果不想用 也可以创建一个出来
- 串行队列 需要创建一个出来
任务:
- 同步:不具有开启线程的能力
- 异步:具有开启线程的能力
分四种情况:
- 并行 — 异步任务
- 并行 — 同步任务
- 串行 — 异步任务
- 串行 — 同步任务
- (void)viewDidLoad {
[super viewDidLoad];
// [self asyncGlobleQueue];
// [self syncGlobalQueue];
// [self asyncSerialQueue];
// [self sycnSerialQueue];
// 100张票 4个同时卖票 抢夺资源的问题
// 多个线程 同时对一个数据进行操作
// 解决方案:访问数据 加一个锁 只能有一个线程访问 等这个线程访问后 才能让下一个线程再访问 保证数据被访问的安全
// 添加同步锁 (互斥锁)
self.lock = [[NSLock alloc] init];
// [lock lock]; // 上锁
// // 中间就是被上锁的部分
// [lock unlock]; // 开锁
// // 初始化票的总数
// self.ticketsCount = 100;
// // 调用一下 买票
// [self tickets];
#pragma mark --- GCD开启子线程加载图片 回到主线程刷新UI
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
// 做请求 耗时操作(同步请求)
// 在子线程中的 同步请求 就相当于 异步请求
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:kUrl2]];
UIImage *image = [UIImage imageWithData:data];
// 回到主线程 刷新界面
// 取出主线程 dispatch_get_main_queue();
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主线程 要执行的任务
// 刷新UI
});
});
}
// 模拟卖票
- (void)tickets
{
// 获取并行队列 添加异步任务
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
// 添加异步任务
[self setTickets];
});
dispatch_async(queue, ^{
// 添加异步任务
[self setTickets];
});
}
// 卖票方法
- (void)setTickets
{
// 互斥锁
@synchronized(self){
while (1) {
if (self.ticketsCount > 0) {
self.ticketsCount--;
NSLog(@"剩余%ld张 ------%@", self.ticketsCount, [NSThread currentThread]);
} else {
// 票卖没了 停止循环
return;
}
}
}
}
// 1.并行 --- 异步任务
// 开启子线程 并且 任务并发
- (void)asyncGlobleQueue
{
// 获取 全局的并发队列
// dispatch_queue_t 队列的类型
// 参数1 是cpu切换的优先级(频率高度)
// 参数2 是个预留参数 可以填0
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 添加异步任务
// 参数1 给哪一个队列添加任务
dispatch_async(queue, ^{
// 添加任务
NSLog(@"---任务1---%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
// 添加任务
NSLog(@"---任务2---%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
// 添加任务
NSLog(@"---任务3---%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
// 添加任务
NSLog(@"---任务4---%@", [NSThread currentThread]);
});
// 创建的队列 是需要释放的(ARC 不用释放)
dispatch_release(queue);
}
// 2.并行 --- 同步任务
// 没有开启子线程 相当于变成了一个串行的队列
- (void)syncGlobalQueue
{
// 创建一个 并行队列
// 参数1 队列的标识符 反向域名 com.lanou3g.www
// 参数2 填 你要创建的队列的类型(串/ 并)
dispatch_queue_t queue = dispatch_queue_create("com.lanou3g.www", DISPATCH_QUEUE_CONCURRENT);
// 添加同步任务
dispatch_sync(queue, ^{
// 添加同步任务
NSLog(@"---任务1---%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
// 添加任务
NSLog(@"---任务2---%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
// 添加任务
NSLog(@"---任务3---%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
// 添加任务
NSLog(@"---任务4---%@", [NSThread currentThread]);
});
}
// 3.串行 --- 异步任务
// 开启了子线程 串行
- (void)asyncSerialQueue
{
// 创建串行队列
dispatch_queue_t queue = dispatch_queue_create("哈哈", DISPATCH_QUEUE_SERIAL);
// 添加异步任务
dispatch_async(queue, ^{
// 添加任务
NSLog(@"---任务1---%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
// 添加任务
NSLog(@"---任务2---%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
// 添加任务
NSLog(@"---任务3---%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
// 添加任务
NSLog(@"---任务4---%@", [NSThread currentThread]);
});
dispatch_release(queue);
}
// 4.串行 --- 同步任务
// 没有开启线程 都在主线程 顺序执行
- (void)sycnSerialQueue
{
dispatch_queue_t queue = dispatch_queue_create("haha", DISPATCH_QUEUE_SERIAL);
// 添加同步任务
dispatch_sync(queue, ^{
// 添加任务
NSLog(@"---任务1---%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
// 添加任务
NSLog(@"---任务2---%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
// 添加任务
NSLog(@"---任务3--%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
// 添加任务
NSLog(@"---任务4---%@", [NSThread currentThread]);
});
dispatch_release(queue);
}
结果
- 并行 — 异步任务
- 并行 — 同步任务
- 串行 — 异步任务
- 串行 — 同步任务
多线程管理
线程间通信
- 主线程进入子线程
- 子线程回到主线程
线程互斥
- 线程互斥是指某一资源同时只允许一个访问者对齐进行访问,具有唯一性和排他性。
- 访问是无序的。因此要加上互斥锁来进行顺序访问。
- NSLock类可以协助完成互斥操作。
单例类的创建
+ (MyHandle *)shareHandle
{
// 如果同时有多个线程 访问这个对象
// 如果创建该对象
static MyHandle *handle = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 执行的任务 在整个程序运行期间 只执行一次
// 并且只允许 一个线程访问(自带 线程保护)
handle = [[MyHandle alloc] init];
});
return handle;
}