1. 概念:
1.1 进程和线程的比较:
线程是 cpu 调用的最小单位
进程是 cpu 分配资源的最小单位
一个进程中至少有一个线程
进程内线程 资源共享
windows\mac 进程管理: 通过进程管理器, 可以直接杀死
linux 进程管理器: 通过ps | top | pstree | kill
1.2. 多线程认识:
多线程 执行原理: 单核, cpu 多条线程切换, 多线程并发假像
多核 ,
多线程的缺点:
创建 线程有开销,主要是 内核数据结构(大约1KB), 栈空间(子线程512KB,主线程1KB,
可以使用 setStackSize设置,但是必须是4K倍数,最下是16K), 创建线程大概需要花费
90毫秒,
建议开闭3个线程就可以了,线程越多,开销越大
1.3. UI线程:主线程
功能: 刷新UI
处理UI事件(比如点击、滚动、拖拽事件)
不能做耗时操作
线程id 是1
1.4. IOS线程实现方案:
pthread: C的api 移植过来的, 需要手动管理声明周期
NSTrhead: OC 面向对象, 手动管理生命周期
GCD: 基于c语言的封装,自动管理声明周期
NSOperation: 基于gcd封装
1.5. pthread 创建线程
#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
- (IBAction)btnClick:(id)sender;
@end
@implementation ViewController
- (IBAction)btnClick:(id)sender {
NSLog(@"%@",[NSThread mainThread]);
//创建线程
pthread_t thread;
/*
第一个参数:线程对象
第二个参数:线程属性
第三个参数:void *(*)(void *) 指向函数的指针
第四个参数:函数的参数
*/
pthread_create(&thread, NULL, run, NULL);
pthread_t thread1;
pthread_create(&thread1, NULL, run, NULL);
}
//void *(*)(void *)
void *run(void *param)
{
// NSLog(@"---%@-",[NSThread currentThread]);
for (NSInteger i =0 ; i<10000; i++) {
NSLog(@"%zd--%@-",i,[NSThread currentThread]);
}
return NULL;
}
@end
1.6. NSThread 使用
-(void)touchesBegan1:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 1. 线程创建方式1
// 第一个参数: 目标对象
// 第二个参数: 选择器,调用哪个方法
// 第三方参数: 前面方法需要传递的参数
NSThread* thread = [[NSThread alloc] initWithTarget:self
selector:@selector(show) object:nil];
// 线程名称
thread.name=@"线程1";
// 线程优先级 默认0.5 最高1.0 ,从 0.0 - 1.0 之间
[thread setThreadPriority:1.0];
// 启动线程
[thread start];
// 2. 线程创建方式2 不用start 但会不能获取线程thread
[NSThread detachNewThreadSelector:@selector(show) toTarget:self withObject:nil];
// 3. 线程创建方式3 不能获取线thread
[self performSelectorInBackground:@selector(show) withObject:@"后台线程"];
}
-(void)show{
NSLog(@"run.....%@",[NSThread currentThread]);
}
2. 线程状态
3. 线程之间通信
-(void)touchesBegan2:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[NSThread detachNewThreadSelector:@selector(download) toTarget:self
withObject:nil];
}
-(void) download{
// 1. 获取下载地址
NSURL* url= [NSURL URLWithString:@"http://wimg.spriteapp.cn/cropx/852x480/cutbimage/5ed5b653d19fd.jpg"];
// 2. 下载图片
NSData* data= [NSData dataWithContentsOfURL:url];
NSDate *start= [NSDate date];
CFAbsoluteTime start1= CFAbsoluteTimeGetCurrent();
// 3. 把 二进制图片 转化为图片
UIImage* image= [UIImage imageWithData:data];
NSDate* end =[ NSDate date];
CFAbsoluteTime end1 =CFAbsoluteTimeGetCurrent();
// 计算图片下载时间
NSLog(@"++++++%f",[end timeIntervalSinceDate:start]);
NSLog(@"++++++%f",(end1-start1));
// 4. 回到 主线程 刷新UI
// 1. 方式1
// YES表示 表示等 showImage执行 完毕,在走下面的方法
// NO 表示 直接往下走
// [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone: YES];
// 2. 直接设置 图片
// [self.myImageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
// 3. 通过 performSelector
[self.myImageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES ];
}
-(void) showImage:(UIImage*) image{
self.myImageView.image = image;
}
4. 线程安全 设置 @synchronized
#import "ViewController.h"
@interface ViewController ()
//售票员01
@property (nonatomic, strong) NSThread *thread01;
//售票员02
@property (nonatomic, strong) NSThread *thread02;
//售票员03
@property (nonatomic, strong) NSThread *thread03;
//总票数
@property(nonatomic, assign) NSInteger totalticket;
//@property (nonatomic, strong) NSObject *obj;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//假设有100张票
self.totalticket = 100;
// self.obj = [[NSObject alloc]init];
//创建线程
self.thread01 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread01.name = @"售票员01";
self.thread02 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread02.name = @"售票员02";
self.thread03 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread03.name = @"售票员03";
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//启动线程
[self.thread01 start];
[self.thread02 start];
[self.thread03 start];
}
//售票
-(void)saleTicket
{
while (1) {
//2.加互斥锁
@synchronized(self) {
[NSThread sleepForTimeInterval:0.03];
//1.先查看余票数量
NSInteger count = self.totalticket;
if (count >0) {
self.totalticket = count - 1;
NSLog(@"%@卖出去了一张票,还剩下%zd张票",[NSThread currentThread].name,self.totalticket);
}else
{
NSLog(@"%@发现当前票已经买完了--",[NSThread currentThread].name);
break;
}
}
}
}
@end
5. GCD的使用
5.1. gcd
GCD 为苹果的多核运行提供方案,可以充分使用CPU内核,内部实现线程管理
gcd 核心 任务 、队列 , 把任务加入队列中即可
gcd 中所有函数都是 dispatch_ 开头
5.2. gcd中有2 个用来执行任务的常用 函数
dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
同步: 只能在当前线程中执行任务, 不具备开线程的能力,只有一个线程

dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
异步: 可以在新的线程中执行任务,具备开新线程的能力
5.3. 队列类型
并发队列: 可以让多个任务并发执行同时,必须要有多个线程,执行在异步函数中
获取并发队列方式1: dispatch_queue_t queue= dispatch_queue_create("com.xiaozhi.baidu", DISPATCH_QUEUE_CONCURRENT);
方式2: dispatch_queue_t queue = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
串行队列: 一个任务做完以后, 接着下一个任务运行
dispatch_queue_t que= dispatch_queue_create("com.xiaozhi.baidu", DISPATCH_QUEUE_SERIAL);
主队列: dispatch_queue_t que= dispatch_get_main_queue();
5.4.1 // 并发队列+ 异步函数
// 并发队列+ 异步函数
/**
输出结果: 开了3个线程
{number = 8, name = (null)} 新线程
{number = 7, name = (null)} 新线程
{number = 9, name = (null)} 新线程
*/
-(void)touchesBegan3:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// gcd 中所有函数 dispatch开头
// 获取并发队列的第一种方法:
//第一个参数 c 语言字符串
// 第二个参数:
// DISPATCH_QUEUE_CONCURRENT: 并发队列
// DISPATCH_QUEUE_SERIAL: 串行队列
// dispatch_queue_t queue= dispatch_queue_create("com.xiaozhi.baidu", DISPATCH_QUEUE_CONCURRENT);
// 获取并发队列的第二种方式:
// 系统提供给了一个获取并发 队列的 方法, 获取全局并发队列
// 第一个参数: 队列的优先级
// 第二个参数: 传0
/*
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
*/
dispatch_queue_t queue = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 第一个参数 队列 第二个参数 任务
dispatch_async(queue, ^{
NSLog(@"任务1--%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务2--%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务3--%@",[NSThread currentThread]);
});
}
5.4.2 // 异步函数 + 串行 队列
/**
输出结果:
{number = 6, name = (null)}
{number = 6, name = (null)}
{number = 6, name = (null)}
开了一个线程, 串行执行
*/
-(void)touchesBegan4:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
dispatch_queue_t queue= dispatch_queue_create("com.xiaozhi.baidu", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"任务1--%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务2--%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务3--%@",[NSThread currentThread]);
});
}
5.4.3 // 同步函数+ 并发队列 ,不会开线程,在main 线程中执行
/**
{number = 1, name = main}
{number = 1, name = main}
{number = 1, name = main}
*/
-(void)touchesBegan6:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
dispatch_queue_t que = dispatch_get_global_queue(0, 0);
dispatch_sync(que, ^{
NSLog(@"任务1--%@",[NSThread currentThread]);
});
dispatch_sync(que, ^{
NSLog(@"任务1--%@",[NSThread currentThread]);
});
dispatch_sync(que, ^{
NSLog(@"任务1--%@",[NSThread currentThread]);
});
}
5.4.4 // 同步函数+ 串行队列 ,不会开线程,在main 线程中执行
// 同步函数+ 串行队列 ,不会开线程,在main 线程中执行
// 结果和上面 相同
-(void)touchesBegan7:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
dispatch_queue_t que= dispatch_queue_create("com.xiaozhi.baidu", DISPATCH_QUEUE_SERIAL);
dispatch_sync(que, ^{
NSLog(@"任务1--%@",[NSThread currentThread]);
});
dispatch_sync(que, ^{
NSLog(@"任务1--%@",[NSThread currentThread]);
});
dispatch_sync(que, ^{
NSLog(@"任务1--%@",[NSThread currentThread]);
});
}
5.4.5 获取主队列 就是一个运行UI 线程的 串行队列
// 异步 函数 + 主队列
// 同步函数 + 主队列:卡死
// 为什么会卡死: 加入同步函数以后会立刻执行4个任务,那么会卡死UI任务,死锁
/**
{number = 1, name = main}
{number = 1, name = main}
{number = 1, name = main}
*/
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 获取主队列
dispatch_queue_t que= dispatch_get_main_queue();
dispatch_async(que, ^{
NSLog(@"任务1--%@",[NSThread currentThread]);
});
dispatch_async(que, ^{
NSLog(@"任务1--%@",[NSThread currentThread]);
});
dispatch_async(que, ^{
NSLog(@"任务1--%@",[NSThread currentThread]);
});
}
5.4.6 队列在实际开发中使用
// 实际开发中 应用
-(void)touchesBegan9:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 1. 获取下载地址
NSURL* url= [NSURL URLWithString:@"http://wimg.spriteapp.cn/cropx/852x480/cutbimage/5ed5b653d19fd.jpg"];
// 2. 下载图片
NSData* data= [NSData dataWithContentsOfURL:url];
// 3. 把 二进制图片 转化为图片
UIImage* image= [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
self.myImageView.image= image;
});
});
}
6. gcd 其他函数功能
6.1. 栅栏函数
首先把并发队列中 任务1 和 任务 2 执行完毕 以后, 在执行栅栏 函数
之后再执行 任务3 和 任务
// 栅栏函数
// 首先把并发队列中 任务1 和 任务 2 执行完毕 以后, 在执行栅栏 函数
// 之后再执行 任务3 和 任务4
-(void)touchesBegan10:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 并发队列
dispatch_queue_t queue= dispatch_queue_create("com.xiaozhi.baidu", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for(NSInteger i=0;i<10;i++){
NSLog(@"%zd---download1--%@",i,[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for(NSInteger i=0;i<10;i++){
NSLog(@"%zd---download2--%@",i,[NSThread currentThread]);
}
});
dispatch_barrier_sync(queue, ^{
NSLog(@"----------我是一个栅栏函数-----------");
});
dispatch_async(queue, ^{
for(NSInteger i=0;i<10;i++){
NSLog(@"%zd---download3--%@",i,[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for(NSInteger i=0;i<10;i++){
NSLog(@"%zd---download4--%@",i,[NSThread currentThread]);
}
});
}
6.2. 延迟 执行 , 一次性质函数
// 延迟 执行 , 一次性质函数
-(void)touchesBegan11:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 1. 在2 s 以后调用 run
// [self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// 2. 方式2
// [NSTimer scheduledTimerWithTimeInterval:2.0 target:self
// selector:@selector(run) userInfo:nil repeats:NO];
// 3. 方式3
// 在主线中执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"main===%@", [NSThread currentThread]);
});
// 在子线程中执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
NSLog(@"child===%@", [NSThread currentThread]);
});
// 程序启动过程中执行一次,以后都不会执行了,一次性代码
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
}
-(void)run{
NSLog(@"%@",@"run");
}
6.3. 快速 迭代函数
案例1 :比较 for循环和 快速迭代函数
-(void)touchesBegan12:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 在 main 线程中 串行 执行 从 0 -10
// 缺点,在main 线程中做耗时操作如果图片比较大,单线程串行执行
// for (NSInteger i=0; i<10; i++) {
// NSLog(@"%zd--%@",i,[NSThread currentThread]);
// }
// 快速迭代函数, 多个线程并发执行
//创建队列(并发队列),穿件多个队列线程并发执行
dispatch_queue_t queue = dispatch_queue_create("com.downloadqueue", DISPATCH_QUEUE_CONCURRENT);
/*
第一个参数:迭代的次数
第二个参数:在哪个队列中执行
第三个参数:block要执行的任务
*/
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zd--%@",index,[NSThread currentThread]);
});
}
案例2: 实际应用,快速迭代函数, 文件拷贝
// from 文件夹 到 to 文件夹
// 缺点: 在主线程中比较 耗时
-(void)moveFile
{
//文件在哪个地方(文件夹)
NSString *form = @"/Users/denganzhi/Desktop/from";
//要剪切到什么地方
NSString *to = @"/Users/denganzhi/Desktop/to";
NSFileManager *manager = [NSFileManager defaultManager];
NSArray *subpaths = [manager subpathsAtPath:form];
// NSLog(@"%@",subpaths);
NSInteger count = [subpaths count];
for (NSInteger i = 0; i<count; i++) {
//拼接文件全路径
// NSString *fullPath = [form stringByAppendingString:c]
NSString *subpath = subpaths[i];
NSString *fullPath = [form stringByAppendingPathComponent:subpath];
//拼接目标文件全路径
NSString *fileName = [to stringByAppendingPathComponent:subpath];
//剪切操作
[manager moveItemAtPath:fullPath toPath:fileName error:nil];
NSLog(@"%@--%@",fullPath,fileName);
}
}
/// 快速 迭代函数 ,开启多个子线程 ,实现快速拷贝
/**
输出的线程:
{number = 7, name = (null)}
{number = 6, name = (null)}
{number = 4, name = (null)}
{number = 1, name = main}
*/
-(void)moveFile1
{
//文件在哪个地方(文件夹)
NSString *form = @"/Users/denganzhi/Desktop/from";
//要剪切到什么地方
NSString *to = @"/Users/denganzhi/Desktop/to";
NSFileManager *manager = [NSFileManager defaultManager];
NSArray *subpaths = [manager subpathsAtPath:form];
//创建队列(并发队列)
dispatch_queue_t queue = dispatch_queue_create("com.downloadqueue", DISPATCH_QUEUE_CONCURRENT);
NSInteger count = [subpaths count];
dispatch_apply(count, queue, ^(size_t index) {
NSString *subpath = subpaths[index];
NSString *fullPath = [form stringByAppendingPathComponent:subpath];
//拼接目标文件全路径
NSString *fileName = [to stringByAppendingPathComponent:subpath];
//剪切操作
[manager moveItemAtPath:fullPath toPath:fileName error:nil];
NSLog(@"%@",[NSThread currentThread]);
});
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self moveFile1];
}
6.4. 队列组: 类似RxJava任务合并
-(void)group
{
//下载图片1
//创建队列组
dispatch_group_t group = dispatch_group_create();
//1.开子线程下载图片
//创建队列(并发)
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
//1.获取url地址
NSURL *url = [NSURL URLWithString:@"http://img5.mtime.cn/mg/2019/06/27/231348.59732586_120X90X4.jpg"];
//2.下载图片
NSData *data = [NSData dataWithContentsOfURL:url];
//3.把二进制数据转换成图片
self.image1 = [UIImage imageWithData:data];
NSLog(@"1---%@",self.image1);
});
//下载图片2
dispatch_group_async(group, queue, ^{
//1.获取url地址
NSURL *url = [NSURL URLWithString:@"http://img5.mtime.cn/mg/2019/06/27/224744.68512147_120X90X4.jpg"];
//2.下载图片
NSData *data = [NSData dataWithContentsOfURL:url];
//3.把二进制数据转换成图片
self.image2 = [UIImage imageWithData:data];
NSLog(@"2---%@",self.image2);
});
//合成, 2 张图片 都下载完毕
dispatch_group_notify(group, queue, ^{
//创建一个基于位图的上下文 默认透明,一个画布
//1. 开启图形上下文
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
//画1,把图1 画上去
[self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
//画2,把图2 画上去
[self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
//2. 根据图形上下文拿到图片,得到合成的图片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
//3. 关闭上下文
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
self.myImageView.image = image;
NSLog(@"%@--刷新UI",[NSThread currentThread]);
});
});
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self group];
}
合成图片效果: