多线程
多线程
1. 进程和线程
一个应用程序可以对应多个进程
每个进程中至少有一个线程
进程中的线程共享该进程的资源
2. 线程的串行
线程执行任务的方式 -- 串行(任务和任务之间有执行顺序,即多个任务一个一个地按顺序执行,一个线程同时只能执行一个任务)
3. 多线程的优缺点
单个进程中的每条线程可以并行执行任务
单CPU
同一时间CPU只能处理一条线程,即只有一条线程在工作
多线程并发执行,实则是CPU快速地在线程之间调度切换
优点
适当提高程序的执行效率
适当提高资源的利用率
缺点
空间开销:内核数据结构,栈空间
时间开销:约90ms的创建时间
性能降低:在开启大量线程时降低程序性能,同时CPU调度线程时开销更大
程序设计:线程之间通信,多线程数据共享(同一数据被多个线程共享导致数据安全问题)更加复杂
4. 多线程的安全隐患
资源共享问题
一块资源可能会同时被多个线程共享,从而引发数据错乱和数据安全问题。
安全隐患解决
互斥锁
注意:互斥锁必须是全局唯一的,且只能用同一把锁为一块区域加锁,用多把锁则无效
优点:有效防止因多线程抢夺资源造成的数据安全问题,实现“线程同步”
缺点:需要消耗大量CPU资源
代码演示
// ViewController.m
//售票员卖票demo
@interface ViewController()
@property(nonatomic, strong) NSThread *threadA;
@property(nonatomic, strong) NSThread *threadB;
@property(nonatomic, strong) NSThread *threadC;
@end
@implementation ViewController
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.ticketCount = 100;
//开启三个售票员线程
self.threadA = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.threadB = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.threadC = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
//线程属性设置
self.threadA.name = @"售票员A";
self.threadB.name = @"售票员B";
self.threadC.name = @"售票员C";
//启动线程
[self.threadA start];
[self.threadB start];
[self.threadC start];
}
- (void)saleTicket {
while(1) {
@synchronized (self) {
//对于每一次卖票的模块上锁,保证同一时间只有一位售票员在卖票
if(self.ticketCount > 0) {
//卖出去一张票
for(int i = 1; i <= 100000; i++); //增加程序执行时间,方便观察结果
NSLog(@"%@卖出去一张,还剩下%zd张", [NSThread currentThread].name, self.ticketCount - 1);
self.ticketCount--;
} else {
NSLog(@"票已售罄");
break;
}
}
}
}
@end
6.多线程在iOS开发中的应用
1). 主线程与子线程
主线程
一个iOS程序运行后,默认开启一条“主线程”或“UI线程”
主线程的作用
显示/刷新UI界面
处理UI事件(点击,滚动,拖拽等)
主线程的使用注意
凡是和UI相关的操作必须在主线程中执行
不将耗时操作放在主线程中 -- 会卡住主线程,严重影响UI流畅度,降低用户体验
子线程
用来执行耗时操作
代码演示
// ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
//1.获得主线程
NSThread *mainThread = [NSThread mainThread];
NSLog(@"%@", mainThread);
//2.获得当前线程
NSThread *currentThread = [NSThread currentThread];
NSLog(@"%@", currentThread);
//3.判断主线程
//3.1 打印该线程,number == 1 则为主线程
//3.2 类方法
BOOL isMainThreadA = [NSThread isMainThread];
//3.3 对象方法
BOOL isMainThreadB = [currentThread isMainThread];
NSLog(@"%zd---%zd", isMainThreadA, isMainThreadB);
}
2). 原子性与非原子性
atomic 原子属性
为setter方法加锁(默认为atomic)
线程安全,消耗大量资源
nonatomic 非原子属性
不会为setter方法加锁
非线程安全,适合小内存移动设备
5.iOS的多线程实现方案
| 技术方案 | 简介 | 语言 | 线程生命周期 | 使用频率 |
|---|---|---|---|---|
| pthread | 1.通用的多线程API 2.适用于Unix/Linux/Windows等系统 3.跨平台/可移植 4.使用难度较大 |
C语言 | 程序员管理 | 少 |
| NSThread | 1.更加面向对象 2.简单易用,可直接操作线程对象 |
OC | 程序员管理 | 正常 |
| GCD | 1.旨在代替NSThread等线程技术 2.充分利用设备的多核资源 |
C | 自动管理 | 经常使用 |
| NSOperation | 1.基于GCD,增加了一些简单功能 2.更加面向对象 |
OC | 自动管理 | 经常使用 |
1.pthread
// ViewController.m
#import <pthread.h>
- (void)viewDidLoad {
[super viewDidLoad];
//1.创建线程对象
pthread_t thread;
//2.线程创建函数
//1).线程对象 -- 地址传递
//2).线程的属性
//3).指向函数的指针
//4).前面函数要接收的参数 -- NULL
pthread_create(&thread, NULL, func, NULL);
//3.判断两条线程是否相等
pthread_equal();
}
void *func(*void) {
NSLog(@"%@-----", [NSThread currentThread]);
return NULL;
}
2.NSThread
1). 线程的创建
// ViewController.m
//线程创建方法1 -- allo + init
- (void)createThread1 {
/*
第一个参数:目标对象 self
第二个参数:方法选择器 调用的方法
第三个参数:前面调用方法需要传递的参数 nil
*/
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"test"];
//启动线程
[thread start];
//设置线程属性
thread.name = @"线程A";
[thread setName:@"线程A"];
//设置线程优先级 -- 优先级大小(0.0 ~ 1.0), 默认为0.5
thread.threadPriority = 1.0;
//线程生命周期 -- 当任务执行完成之后被释放
}
//线程创建方法2 -- 无法获取创建的线程对象
- (void)createThread2 {
//分离子线程方法
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"通过分离子线程创建新线程"];
}
//线程创建方法3-- 无法获取创建的线程对象
- (void)createThread3 {
//开启后台线程方法
[self performSelectorInBackground:@selector(run:) withObject:@"通过开启后台线程创建新线程"];
}
- (void)test:(NSString *)para {
NSLog(@"-----test-----%@", [NSThread currentThread]);
}
2). 线程的状态
// ViewController.m
- (void)createThread1 {
//新建状态
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"test"];
//就绪或运行状态
[thread start];
}
- (void)test:(NSString *)para {
NSLog(@"-----test-----%@", [NSThread currentThread]);
//阻塞状态
[NSThread sleepFortimeinterval:2.0];
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3.0]];
//死亡状态
[NSThread exit];
}
3. GCD -- Grand Central Dispatch
强大的中枢调度器
纯C语言实现
解决多核的并行运算,能够自动利用更多的CPU内核,自动管理线程的生命周期
1).核心概念
任务 -- 执行什么操作
队列 -- 用来存放,安排任务
2).GCD的使用
定制任务
将任务添加到队列 -- 先进先出地自动取出任务放到对应线程中执行
3).执行任务
同步执行 -- 只能在当前线程执行任务,不能开启新线程
异步执行 -- 可以在新线程中执行任务,可以开启新线程
4). 队列类型
并发队列 -- 可以让多个任务并发执行 (自动开启多个线程同时执行任务) -- 并发功能只在异步函数下生效
//并发队列
/*
参数1:C语言字符串 -- 队列标签
参数2:队列类型 DISPATCH_QUEUE_CONCURRENT -- 并发
*/
dispatch_queue_t queue = dispatch_queue_create("label1", DISPATCH_QUEUE_CONCURRENT);
//全局并发队列
/*
参数1:队列优先级
参数2:默认为0
*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
串行队列 -- 让任务一个接一个地执行
//串行队列
dispatch_queue_t queue = dispatch_queue_create("label2", DISPATCH_QUEUE_SERIAL);
//主队列(主线程执行任务)
dispatch_queue_t queue = dispatch_get_main_queue();
5). 代码演示
//同步函数 异步函数 并发队列 串行队列
#import "ViewController.h"
@implementation ViewController
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self asyncConcurrent];
[self asyncSerial];
[self syncConcurrent];
[self syncSerial];
}
//异步函数 + 并发队列 -- 会开启多条线程,队列中的任务异步执行
- (void)asyncConcurrent {
//1.创建队列 -- 并发队列
dispatch_queue_t queue = dispatch_queue_create("label1", DISPATCH_QUEUE_CONCURRENT);
//2.封装任务并添加到队列中 -- 异步函数
/*
参数1:队列名
参数2:要执行的任务
*/
//测试是否开启了多条线程
dispatch_async(queue, ^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
});
}
//异步函数 + 串行队列 -- 会开启一条线程,队列中的任务串行执行
- (void)asyncSerial {
//创建队列 -- 串行队列
dispatch_queue_t queue = dispatch_queue_create("label2", DISPATCH_QUEUE_SERIAL);
//异步函数
dispatch_async(queue, ^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
});
}
//异步函数 + 主队列 -- 不会开启线程,队列中任务都在主线程中执行
- (void)asyncMain {
//创建主队列
dispatch_queue_t queue = dispatch_get_main_queue();
//异步函数
dispatch_async(queue, ^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
});
dispatch_async(queuaaa
NSLog(@"download1 --- %@", [NSThread currentThread]);
});
}
//同步函数 + 并发队列 -- 不会开启线程
- (void)syncConcurrent {
//创建并发队列
dispatch_queue_t queue = dispatch_queue_create("label3", DISPATCH_QUEUE_CONCURRENT);
//同步函数
dispatch_sync(queue, ^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
});
}
//同步函数 + 串行队列 -- 不会开启线程
- (void)syncSerial {
//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("label4", DISPATCH_QUEUE_SERIAL);
//同步函数
dispatch_sync(queue, ^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
});
}
//同步函数 + 主队列 -- 产生死锁
//避免死锁:用子线程调用该方法
- (void)syncMain {
//创建主队列
dispatch_queue_t queue = dispatch_get_main_queue();
//同步函数
dispatch_sync(queue, ^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
});
}
@end
6). 总结
| 并发队列 | 手动创建的串行队列 | 主队列 | |
|---|---|---|---|
| 同步(sync) | 没有开启新线程 串行执行任务 |
没有开启新线程 串行执行任务 |
没有开启新线程 串行执行任务(在子线程中执行避免死锁) |
| 异步(async) | 有开启新线程 并发执行任务 |
有开启新线程 串行执行任务 |
没有开启新线程 串行执行任务 |
5.线程间通信
A线程专递数据给B线程
A线程执行完特定任务后,转到B线程继续执行任务
1). NSThread实现
//网络图片下载Demo -- NSThread实现
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic, strong) UIImageView *imageView;
@property(nonatomic, strong) NSThread *threadA;
@end
@implementation ViewController
//耗时操作要用子线程进行
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//分离一条子线程去执行download方法
[NSThread detachNewThreadSelector:@selector(download) toTarget:self withObject:nil];
}
- (void)download {
//确定图片URL
NSURL *url = [NSURL URLWithString:@"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fa1.att.hudong.com%2F62%2F02%2F01300542526392139955025309984.jpg&refer=http%3A%2F%2Fa1.att.hudong.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1613890070&t=9b041b83f4ddfe8c165547120f5aa589"];
//根据URL下载图片二进制数据到本地
NSData *imageData = [NSData dataWithContentsOfURL:url];
//图片格式转换
UIImage *image = [UIImage imageWithData:imageData];
//回到主线程显示UI的三种方法
//方法1
/*
参数1:回到主线程要调用的方法
参数2:前面方法需要的参数
参数3:当前方法剩余部分的执行是否等待参数1方法执行完毕
*/
[self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];
//方法2 -- 主动选择执行线程为主线程
[self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
//方法3 -- 直接用self.imageView调用主线程方法
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
}
- (void)showImage:(UIImage *)image {
self.imageView.image = image;
}
@end
2). GCD实现
//网络图片下载Demo -- GCD实现
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic, strong) UIImageView *imageView;
@end
@implementation ViewController
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//1.使用GCD开子线程 -- 使用异步函数
dispatch_async(dispatch_get_global_queue(0, 0), ^{ //获取全局并发队列
//封装任务
//确定图片URL
NSURL *url = [NSURL URLWithString:@"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fa1.att.hudong.com%2F62%2F02%2F01300542526392139955025309984.jpg&refer=http%3A%2F%2Fa1.att.hudong.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1613890070&t=9b041b83f4ddfe8c165547120f5aa589"];
//根据URL下载图片二进制数据到本地
NSData *imageData = [NSData dataWithContentsOfURL:url];
//图片格式转换
UIImage *image = [UIImage imageWithData:imageData];
//更新UI
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
NSLog(@"UI--- %@", [NSThread currentThread]);
});
//同步函数更新UI
dispatch_sync(dispatch_get_main_queue(), ^{ //此时任务在子线程中执行,因此使用主队列不会造成死锁
self.imageView.image = image;
NSLog(@"UI--- %@", [NSThread currentThread]);
});
});
}
@end
延迟执行
//ViewController.m
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self delay];
}
//延迟执行
- (void)delay {
NSLog(@"start---- delay running");
//1.延迟执行的第一种方法
[self performSelector:@selector(task) withObject:nil afterDelay:2.0]; //2.0秒延迟之后执行
//2.延迟执行的第二种方法
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(task) userInfo:nil repeats:YES]; //在2.0秒延迟之后每2.0秒执行一次task方法
//3.GCD实现延迟执行
/*
参数1:DISPATCH_TIME_NOW 从现在开始计算时间
参数2:延迟的时间 2.0 GCD时间单位:纳秒
参数3:队列
*/
dispatch_queue_t queue = dispatch_get_main_queue(); //使用主队列
//dispatch_queue_t queue = dispatch_get_global_queue(0, 0); //使用全局并发队列
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), queue, ^{ //从现在开始计时,2.0*10e9纳秒延迟后执行block
NSLog(@"GCD --- %@", [NSThread currentThread]);
});
}
- (void)task {
NSLog(@"task --- %@", [NSThread currentThread]);
}
一次性代码 -- 不可放入懒加载中
//一次性代码 -- 整个应用程序运行期间只执行一次
- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"---once---");
});
}
栅栏函数 -- 控制并发队列任务执行顺序(栅栏后的任务等待栅栏执行后才会执行)
//ViewController.m
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//0.创建并发队列
dispatch_queue_t que = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);
//1.异步函数开线程
dispatch_async(que, ^{ //任务1
NSLog(@"download1---%@", [NSThread currentThread]);
});
dispatch_async(que, ^{ //任务2
NSLog(@"download2---%@", [NSThread currentThread]);
});
//栅栏函数 -- 不可使用全局并发队列
dispatch_barrier_async(que, ^{
NSLog(@"++++++++++++++++");
});
dispatch_async(que, ^{ //任务3
NSLog(@"download3---%@", [NSThread currentThread]);
});
dispatch_async(que, ^{ //任务3
NSLog(@"download4---%@", [NSThread currentThread]);
});
}
快速迭代
//文件剪切Demo -- 快速迭代并发遍历并剪切文件
//开子线程和主线程一起完成并发任务,任务并发执行
- (void)apply {
/*
参数1:遍历次数
参数2:并发队列
参数3:遍历索引
*/
dispatch_apply(100, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"%zd --- %@", index, [NSThread currentThread]);
});
}
- (void)moveFile {
//1.拿到文件路径
NSString *from = @"/Users/qiaoyibo/Downloads/from";
//2.获得目标文件路径
NSString *to = @"/Users/qiaoyibo/Downloads/to";
//3.得到目录下面的所有文件(名)
NSArray *subPaths = [[NSFileManager defaultManager] subpathsAtPath:from];
//4.遍历所有文件,执行剪切操作
NSInteger count = subPaths.count;
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
//4.1 拼接文件全路径
//拼接时自动添加路径间的'/'
NSString *fullPath = [from stringByAppendingPathComponent:subPaths[i]];
NSString *tofullPath = [to stringByAppendingPathComponent:subPaths[i]];
//4.2 执行剪切操作
/*
参数1:要剪切的文件在哪
参数2:文件应该被存放到哪里
参数3:默认为nil
*/
[[NSFileManager defaultManager] moveItemAtPath:fullPath toPath:tofullPath error:nil];
});
}
队列组
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//1.创建队列
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
//2.创建队列组
dispatch_group_t group = dispatch_group_create();
//异步函数
/*
1)封装任务
2)把任务添加到队列中
3)会监听任务的执行情况,通知group
*/
dispatch_group_async(group, queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
});
//拦截通知,当队列组中所有的任务都执行完毕的时候会进入到下面的方法
dispatch_group_notify(group, queue, ^{
NSLog(@"-----拦截任务组-----");
});
}

浙公网安备 33010602011771号