什么是进程
进程是指在系统中正在运行的一个应用程序
每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内
比如同时打开迅雷、Xcode,系统就会分别启动2个进程
什么是线程
1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程)
一个进程(程序)的所有任务都在线程中执行
比如使用酷狗播放音乐、使用迅雷下载电影,都需要在线程中执行
1个线程中任务的执行是串行的
如果要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务
也就是说,在同一时间内,1个线程只能执行1个任务
什么是多线程
- 1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务
- 多线程技术可以提高程序的执行效率
- 比如同时开启3条线程分别下载3个文件(分别是文件A、文件B、文件C
多线程的原理
- 同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)
- 多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)
- 如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
如果线程非常非常多,会发生什么情况?
CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源
每条线程被调度执行的频次会降低(线程的执行效率降低)
多线程的优点
- 能适当提高程序的执行效率
- 能适当提高资源利用率(CPU、内存利用率)
多线程的缺点
- 创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,也可以使用-setStackSize:设置,但必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的创建时间
- 如果开启大量的线程,会降低程序的性能
- 线程越多,CPU在调度线程上的开销就越大
- 程序设计更加复杂:比如线程之间的通信、多线程的数据共享
什么是主线程
一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”
-
主线程的主要作用
- 显示\刷新UI界面
- 处理UI事件(比如点击事件、滚动事件、拖拽事件等)
-
主线程的使用注意
- 别将比较耗时的操作放到主线程中
- 耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验
在主线程放一个需要耗时10秒的操作;用户在第五秒的时候点击屏幕按钮,在第十秒的时候才会做出响应;造成卡顿现象;
- 在ios领域里面真正的多线程技术只有这两个pthread和NSthread;
GCD不能直接操作多线程;属于并发技术;NSOperation也是一样,是操作队列的,与线程无关,线程部分GCD已经帮你封装好了!
- GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 我们只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
- 苹果不建议开发者使用多线程技术;
鼓励使用并发技术;
NSThread
- 创建线程的几种方法
// 创建一个NSThread
NSThread * thread = [[NSThread alloc]initWithTarget:self selector:@selector(demo:) object:@"Thread"];
//线程就绪-之后等着cpu调度;
[thread start];
//demo函数在子线程执行
-(void)demo:(id)obj{
for (int i = 0; i < 2; i++) {
//number !=1
NSLog(@"%@",[NSThread currentThread]);
//[NSThread currentThread] 打印当前线程
//返回一个Thread对象,里面有number和name属性
//number == 1 说明是主线程 number != 1 就是其他线程
}
}
//detach ==> 分离
//分离出一条子线程
[NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"Detach"];
//InBackground 就是在后台(子线程)运行!!
//是NSObject的分类 意味着所有的继承NSObject的都可以使用这个方法
//非常方便.不用NSThread对象
[self performSelectorInBackground:@selector(demo:) withObject:@"background"];
线程的状态
- 新建一条线程,线程在可调度线程池里 (由cpu进行调度的)
- 当 [thread start] 的时候,在可调度线程池里的线程都处于就绪状态
- cpu调度处于就绪状态的线程
- 运行状态
- 线程执行完毕之后线程在可调度线程池取出,干掉;
线程阻塞
- 当运行满足某个条件,会让线程"睡一会
提示:sleep 方法是类方法,会直接休眠当前线程!!
//当前线程睡两秒
[NSThread sleepForTimeInterval:2.0];
//一旦强行终止线程,后续的所有代码都不会被执行
//注意:在终止线程之前,应该要释放之前分配的对象!!
[NSThread exit];
//创建线程
NSThread * t = [[NSThread alloc]initWithTarget:self selector:@selector(theadStatus) object:nil];
//线程就绪(CPU翻牌)
[t start];
-(void)theadStatus{
for (int i = 0; i < 20;i++) {
//阻塞,当运行满足某个条件,会让线程"睡一会"
//提示:sleep 方法是类方法,会直接休眠当前线程!!
if (i == 8) {
NSLog(@"睡一会");
//睡的是子线程
//注意!!! exit会杀掉主线程!但是APP不会挂掉!!
[NSThread sleepForTimeInterval:2.0];
}
NSLog(@"%@ %d",[NSThread currentThread],i);
//当线程满足某一个条件时,可以强行终止的
//exit 类方法,哥么终止当前线程!!!!
if (i == 15) {
[NSThread exit]; //线程就就处于死亡状态了
}
}
NSLog(@"能来吗???"); //来不了了
}
threadPriority //线程优先级
- 优先级只是保证cpu调度的可能性会高!
用优先级来控制线程得执行顺序是不理性的!
- 建议:再开发的时候不要修改优先级;
在多线程的开发中,不要相信一次的运行结果!
多线程的目的
- 将耗时操作放在后台,不阻塞UI线程
多线程的安全隐患
-
1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
- 比如多个线程访问同一个对象、同一个变量、同一个文件
当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题
安全隐患的解决 - 互斥锁
- 一个线程访问一个资源时,先给他上锁,访问完成之后再解锁让其他线程访问;
- 互斥锁 -- 保证锁内的代码,同一时间,只有一条线程执行!
- 互斥锁 的范围 应该尽量小,范围大了 效率就差!!
//互斥锁
@synchronized (self) {
//括号里是 同一时间只能有一条线程执行的代码
};
好比你去上厕所,你把门锁上了,别人只能在外面等着,等你操作完厕所,别人才能进来;如果你不锁门,两个人在厕所后果不堪设想!😏
原子属性
- atomic-用来保护线程安全(多线程执行写入操作的时候,保证同一时间只有一个线程执行写入 只对写入操作上锁,读不上锁)
- 单写多读的原子属性(只能单个线程写,但可以多个线程读)
- 实际上原子属性内部有一把锁叫 - 自旋锁
- 自旋锁&互斥锁异同
-
共同点
- 都能保证线程安全
-
不同点
- 互斥锁:被锁在外面的线程,处于休眠状态;等待锁打开,然后被唤醒;
- 自旋锁:被锁在外面的线程,用死循环的方式,等待锁打开;
-
- 自旋锁&互斥锁异同
- 不管什么锁,都很消耗性能,效率不高;
想要模拟原子属性的时候就在set方法里加了一把锁;
@synchronized
如果不写nonatomic | atomic
默认是atomic
线程间的通信方法
- 就这五个方法
- 把SEL丢到某一个线程去执行
/**
waitUntilDone : 当前线程是否等待SEL执行完再继续
*/
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
// equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
// equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);
GCD(重点)
GCD并不是多线程技术;属于并发解决技术;
- GCD是苹果为了适配多核的并行运算提出的解决方案
- GCD会自动利用更多的CPU内核(比如双核、四核)
- GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
GCD的使用就2个步骤
定制任务
将任务添加到队列中
GCD中有2个核心概念
- 任务:执行什么操作
- 执行任务方式有两种; 同步/异步
- 同步:不会到线程池里面去获取子线程!
- 异步:只要有任务,就会到线程池取子线程!(主队列除外!)
- 执行任务方式有两种; 同步/异步
- 队列:用来存放任务
- 串行:一个接一个的调度任务
- 并行:可以同时调度多个任务
GCD会自动将队列中的任务取出,放到对应的线程中执行
任务的取出遵循队列的FIFO原则:先进先出,后进后出
/**
同步执行方法,这句话不执行完,就不会执行下一个任务,同步执行不会开启线程
*/
//1.创建队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
//2.任务添加到队列中
//2.1 定义任务 -- block
void(^task)() = ^{
NSLog(@"%@",[NSThread currentThread]);
};
//2.2 添加任务到队列,并且会执行
dispatch_sync(q, task);
/**
异步执行任务 哥么如果任务没有执行完毕,可以不用等待,异步执行下一个任务
具备开启线程的能力! 异步通常又是多线程的代名词!!
*/
-(void)gcdDemo2{
//1.创建队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
//2 定义任务 -- block
void(^task)() = ^{
NSLog(@"%@",[NSThread currentThread]);
};
//3. 添加任务到队列
dispatch_async(q, task);
}
//线程间通信-切换子线程到主线程更新UI
//指定任务执行方法 -- 异步
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//耗时操作
NSLog(@"%@",[NSThread currentThread]);
//更新UI 主队列,就是专门负责在主线程上调度任务的队列!
//dispatch_get_main_queue 主队列 住能操作主线程
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"更新UI%@",[NSThread currentThread]);
});
});
//MARK:串行队列,同步任务
/**
* 不会开启线程,会顺序执行
*/
-(void)gcdDemo1{
//1.队列 - 串行
/**
1."ios" 队列名称:
2. NULL 队列的属性: DISPATCH_QUEUE_SERIAL 表示串行!
*/
dispatch_queue_t q = dispatch_queue_create("ios", NULL);
//2.同步执行任务
for (int i = 0; i < 10; i++) {
dispatch_sync(q, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
}
//MARK: 串行队列,异步任务
-(void)gcdDemo2{
/**
会开几条线程?会顺序执行吗?
开一条线程,顺序执行
*/
//1.队列 - 串行
dispatch_queue_t q = dispatch_queue_create("tanzhouios", NULL);
//2.异步执行任务
for (int i = 0; i < 10; i++) {
NSLog(@"%d------------",i);
dispatch_async(q, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
//哥么在主线程!
NSLog(@"come here");
}
//MARK : 并发队列,异步执行
-(void)gcdDemo3{
//1.队列 - 并发 DISPATCH_QUEUE_CONCURRENT
dispatch_queue_t q = dispatch_queue_create("tanzhouios", DISPATCH_QUEUE_CONCURRENT);
//2.异步执行任务
for (int i = 0; i < 10; i++) {
dispatch_async(q, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
//哥么在主线程!
NSLog(@"come here");
}
//MARK : 并发队列,同步执行 和 串行队列,同步执行 效果一样!
-(void)gcdDemo4{
// 会开线程吗? 顺序执行? come here?
// 不会 顺序 最后
//1.队列 - 并发 DISPATCH_QUEUE_CONCURRENT
dispatch_queue_t q = dispatch_queue_create("tanzhouios", DISPATCH_QUEUE_CONCURRENT);
//2.同步执行任务
for (int i = 0; i < 10; i++) {
dispatch_sync(q, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
//哥么在主线程!
NSLog(@"come here");
}
小结:
只有异步才会开启子线程!同步不开启!
开几条线程,取决于队列,串行开一条,并发可以开多条(异步)
个人理解锁这个问题
线程锁:是多条线程同时访问一个资源时需要一个互斥锁;
GDC-死锁:主队列是一个串行队列;在主队列里添加一个同步任务会造成死锁;互相等待的局面;
//主队列是专门负责在主线程上调度任务的队列 --> 不会开线程
//1.队列 --> 已启动主线程,就可以获取主队列
dispatch_queue_t q = dispatch_get_main_queue();
//2.同步任务 ==> 死锁
//走到这里添加一个同步任务,主队列的任务是不是要等他执行完了在执行;
dispatch_sync(q, ^{
NSLog(@"能来吗? ");
//主队列里的任务没执行完呢你想执行这同步任务,你是不是要等主队列执行完了才能执行;
});
NSLog(@"come here");
注意:
主队列不是全局队列
全局队列:dispatch_get_global_queue
全局队列本质上是并发队列
主队列:dispatch_get_main_queue()
主队列是一个串行队列
主队列是串行的 主队列里的任务默认只有一个(不手动添加的话) 这个任务是同步的 主队列里直接添加一个异步任务 不开开启子线程
主队列只负责主线程
//主队列是专门负责在主线程上调度任务的队列 --> 不会开线程
//1.队列 --> 一启动主线程,就可以获取主队列
dispatch_queue_t q = dispatch_get_main_queue();
//2.异步任务
dispatch_async(q, ^{
NSLog(@"%@",[NSThread currentThread]);
});
NSLog(@"come here");
//全局队列
/* 参数
1. 涉及到系统适配
iOS 8 服务质量
QOS_CLASS_USER_INTERACTIVE 用户交互(希望线程快速被执行,不要用好使的操作)
QOS_CLASS_USER_INITIATED 用户需要的(同样不要使用耗时操作)
QOS_CLASS_DEFAULT 默认的(给系统来重置队列的)
QOS_CLASS_UTILITY 使用工具(用来做耗时操作)
QOS_CLASS_BACKGROUND 后台
QOS_CLASS_UNSPECIFIED 没有指定优先级
iOS 7 调度的优先级
- DISPATCH_QUEUE_PRIORITY_HIGH 2 高优先级
- DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认优先级
- DISPATCH_QUEUE_PRIORITY_LOW (-2) 低优先级
- DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台优先级
提示:尤其不要选择BACKGROUND 优先级,服务质量,线程执行会慢到令人发指!!!
2. 为未来使用的一个保留,现在始终给0.
老项目中,一般还是没有淘汰iOS 7 ,没法使用服务质量
*/
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
//以第一个0用来设置优先级
//第二个0还没有定义
/*
全局队列 & 并发队列 区别
1> 名称,并发队列取名字,适合于企业开发跟踪错误
2> release,在MRC 并发队列 需要使用的
dispatch_release(q);//ARC 情况下不需要release !
全局队列 & 串行队列
全局队列: 并发,能够调度多个线程,执行效率高
- 费电
串行队列:一个一个执行,执行效率低
- 省点
判断依据:用户上网方式
- WIFI : 可以多开线程
- 流量 : 尽量少开线程
*/
//1.队列
dispatch_queue_t q = dispatch_queue_create("tanzhou", DISPATCH_QUEUE_CONCURRENT);
//2.全局队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
延迟执行
/**
从现在开始,进过多少纳秒之后,让 queue队列,调度 block 任务,异步执行!
参数:
1.when
2.queue
3.block
*/
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.00003 * NSEC_PER_SEC));
dispatch_after(when, dispatch_queue_create("tanzhou", NULL), ^{
NSLog(@"%@",[NSThread currentThread]);
});
GCD:执行一次 常用于单利
//苹果提供的 一次执行机制,不仅能够保证一次执行!而且是线程安全的!!
static dispatch_once_t onceToken;
NSLog(@"%ld",onceToken);
//苹果推荐使用 gcd 一次执行,效率高
//不要使用互斥锁,效率低!
dispatch_once(&onceToken, ^{
//只会执行一次!!
NSLog(@"执行了%@",[NSThread currentThread]);
});
GCD调度组
//1.队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
//2.调度组
dispatch_group_t g = dispatch_group_create();
//3.添加任务,让队列调度,任务执行情况,最后通知群组
dispatch_group_async(g, q, ^{
NSLog(@"download A%@",[NSThread currentThread]);
});
dispatch_group_async(g, q, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"download B%@",[NSThread currentThread]);
});
dispatch_group_async(g, q, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"download C%@",[NSThread currentThread]);
});
//4.所有任务执行完毕后,通知
//用一个调度组,可以监听全局队列的任务,主队列去执行最后的任务
//dispatch_group_notify 本身也是异步的!
dispatch_group_notify(g, dispatch_get_main_queue(), ^{
//更新UI,通知用户
NSLog(@"OK %@",[NSThread currentThread]);
});