1.什么是线程?
2.什么是进程?
3.什么又是多线程?
进程就是指在系统中正在运行的应用程序.
每个进程之间是独立的, 每个进程均运行在其专用且受保护的内存内.
线程是进程的一条执行路径.
线程注意点:
1>线程执行任务是串行的. (所以才有了子线程的概念, 如果都放在一个线程中执行任务, 当下载大图片的时候, 就什么操作也做不了了. .....)
关于多线程:
一个进程中可以开启多个线程, (一个应用程序中能够开启多个通道, 让不同的任务进行执行, 而不会导致 阻塞).
这样在同一时间内, 既不影响用户的体验, 也将大数据/耗时操作 得以执行.
关于多线程的原理:
1.同一时间,CPU只能处理一条线程, 只有一条线程在工作(执行)
2.多线程并发(同时)执行,其实是CPU快速的在多条线程之间调度(切换).
3.如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象.
思考:如果线程很多时,会出现什么情况?
1>CPU会在多个线程之间调度, 累死了,消耗了大量的CPU资源
2>每条线程被调度执行的频率降低( 会很卡的).
多线程的优缺点:
优点:
1>能适当提高程序的执行效率.
2>适当的提高资源利用率
缺点:
1>创建线程是有开销的. 创建时间大约 90毫秒, 占用一定内存资源
2>大量的开启线程, 会降低程序的性能.
3>线程越多, CPU在调度线程上的开销就越大(无故消耗资源)
4>程序设计更加复杂,线程之间的通信, 以及资源共享等问题
多线程在iOS开发中的应用:
1>主线程: 和程序的启动有关, 程序已启动,默认开启一个主运行循环, 内部有一个主线程.(UI线程)
2>主线程的主要作用:
显示/刷新UI界面 (一般都是一部请求数据, 会主线程刷新UI)
处理UI事件(比如 点击, 拖拽,滚动等)
3>使用主线程需要注意的
不要将耗时操作放置在主线程执行. (阻塞主线程 , 严重影响UI的流畅度)
pthread(纯C语言, 比较难使用,这里只进行简单方法的说明)
// 点击屏幕增加子线程 ,来执行耗时的 方法.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// 将耗时操作放在子线程中执行
pthread_t thread;
/*
第一个的参数:线程的代号(就是代指线程)
第二个参数:线程的属性
第三个参数:只想指向函数的指针,就是线程需要执行的方法
第四个参数: 就是指向函数的指针 传递的参数。
*/
pthread_create(&thread, NULL,&demo , "lxl");
// 用于验证 ,主线程继续执行操作 (没有阻塞主线程)
NSLog(@"刘小龙,你好,%@",[NSThread currentThread]);
}
// 设置耗时的方法 ( 只想函数的指针)
void *demo(void *parma){
// 打印传入的参数, 以及当前的线程, 是1则是主线程,其他都不是
NSLog(@"%s,%@",parma,[NSThread currentThread]);
// 一些耗时的操作
for (int i = 0; i < 99999; i++) {
// NSLog是一个非常耗时的操作
// 上架之前 全部清掉.
NSLog(@"%i",i);
}
return NULL;
}
NSThread(不长使用, 需要自己手动管理它的生命周期) [现在使用它只有一个目的 就是获取当先所处的线程 [NSThread currentThread] ]
创建和启动线程
1>一个NSThread对象就代表一个线程.
2>创建.启动线程 (直接设置线程的执行者, 和线程执行的方法, 传入的参数)
- initWithTarget:<#(nonnull id)#> selector:<#(nonnull SEL)#> object:<#(nullable id)#>
- start
开启线程
3>常用的与主线程相关的方法:
+ mainThread
获得主线程
- isMainThread
是否是主线程
+ isMainThread
是否为主线程
+ currentThread
获取当前的线程
- setName:
设置线程的名字,方便管理
4> 创建一个直接启动的线程(不能设置名字)
+ detachNewThreadSelector: toTarget: withObject:
5>隐式创建并启动线程(一般我都不会这么用,不好管理)
[self performSelectorInBackground:@selector(run) withObject:nil];
线程的状态
1>启动线程
- start
进入就绪状态--->运行状态. 线程任务执行完毕,自动进入死亡状态2>阻塞(暂停)线程
+ sleepUntilDate:
// 线程休眠到什么时候+ sleepForTimerInterval:
// 休眠几秒阻塞线程
3>强制停止线程
+ exit
进入死亡状态 ---一旦线程停止(死亡), 就不能再次开启任务, 需要重新定义.#############
1.创建子线程:新建
2.就绪:启动子线程
3.运行:调度子线程
4.阻塞:调用了sleep/等待同步锁 @synchronized(id){}
5.死亡:线程执行完毕,或者异常退出/强制退出
注意: 对于线程对象,创建的时候被加到了 可调度线程池中, 待调度使用。 如果调用了sleep/同步锁,就会被移除调度池,当sleep/同步锁到时后,会再被引入调度池,做就绪准备
互斥锁,与线程之间的抢夺, 已经在上文讲出
线程之间的通信
1>线程之间不是孤立存在的, 多个线程之间需要经常进行通信.
2> 数据传递, 任务传递
3>常用方法[一般都是控制器自己调用的]
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)obj waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)the withObject:(id)arg waitUntilDone:(BOOL)wait
GCD (Grand Central Dispatch),"牛逼的中枢调度器"
1>纯C语言,提供了很强的函数
2>优势:
GCD是苹果公司为多核的并行运算提出的解决方案
GCD会自动利用更多的CPU内核
GCD会自动管理线程的生命关系(创建线程,调度任务,销毁线程)
程序员只需要告诉GCD想要执行什么任务,不需要管理线程
3>任务与队列
任务:执行什么操作
队列:用来存放任务
4>GCD的使用就2个步骤
定制任务
确定想做的事情
5>将任务添加到队列中
线程---->队列----->任务
任务加入队列, 队列放置在线程中执行
GCD会自动
将队列中的任务取出,放到对应的线程中执行
任务的取出遵循队列的FIFO原则:先进先出,后进后出.
########任务的执行
GCD常用的2个用来执行任务的常用函数
1>
1.同步的方式执行任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:队列 block:任务
2.异步的方式执行任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
2>同步和异步的区别
同步:只能在当前的线程中执行任务,不具备开启新线程的能力
异步:可以在新的线程中执行任务,具备开启新线程的能力
3>执行任务
GCD中还有个来执行任务的函数:(添加依赖)
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
前面的队列执行完任务后,才能执行后面的任务 ,这里的队列不能传全局并发队列, 必须传一个固定的队列.
4>GCD的队列可以分为2个类型:
并发队列(Concurrent Dispatch Queue)
系统创建多条线程来并发(同时)执行队列中的任务, 但开启几条,由系统决定, 这个队列只有在异步函数中有效果
串行队列(Serial Dispatch Queue)
让任务一个个执行 (一个任务执行完毕后,在执行下一个任务)
########同步/异步;并发/串行
同步/异步:能不能开启新的线程
同步:只是在当前线程
(一般都是指主线程)中执行任务,不具备开启新线程的能力.
异步:可以在新的线程中执行任务,具备开启新线程的能力
并发/串行:任务的执行方式
并发:允许多个任务并发(同时)执行
串行:一个任务执行完毕后,再执行下一个任务
5>并发队列
5.1dispatch_queue_create
函数创建队列
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
lable: 队列名称
attr: 队列的类型
简单的创建并发队列:
dispatch_queue_t queue = dispatch_queue_create("123",DISPATCH_QUEUE_CONCURRENT)
5.2全局并发队列
dispatch_get_global_queue
函数获得全局的并发队列
函数的书写
dispatch_queue_t dispatch_get_global_queue( dispatch_queue_priority_t priority ,// 队列的优先级 unsigned long flags //这个只是一个标志, 一般不用 传0)
优先级默认也是0
6>串行队列
6.1 dispatch_queue_create
函数创建串行队列
创建串行队列(队列类型传递NULL 或者 DISPATCH_QUEUE_SERIAL)
简单创建串行队列
dispatch_queue_t queue = dispatch_queue_create("123",NULL)
6.2 使用主队列 (跟主线程相关联的队列)
主队列是GCD自带的一种特殊的串行队列
放在主队列的任务,都会放在主线程中执行
使用dispatch_get_main_queue()
获得主队列
7>线程之间的通信
子线程执行耗时操纵, 会主线程刷新UI控件
dispatch_async(
dispatch_get_global_queue(0,0),^{
// 耗时的异步操作
.....
#warning 必须使用异步调用主队列. 同步的话,会造成阻塞主线程(因为有矛盾, 主线程不想让执行block, 主队列又想, 这样很矛盾的)
dispatch_async(dispatch_get_main_queue(),^{
// 返回主线程,执行UI刷新操作
});
});
8>延迟执行
常见的几种延迟执行的方法:
8.1 调用NSObject的方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0]; // 延时两秒调用self的run方法
8.2使用GCD函数
dispatch_after(dispatch_time(DISPATCH_TIME_NOW , (int64_t)(2.0 * NSEC_PER_SEC)),dispatch_get_main_queue(),^{
// 2秒后执行这里的代码......
});
8.3使用NSTimer
[NSTimer scheduledTimerWithTimeInterval: 2.0 target:self selector:@selector(test) useInfo:nil repeats:NO];
/##############特殊应用##############/
8.4一次性代码
使用dispatch_once函数能保证某段代码在程序运行过程中只被执行一次
static dispatch_once_t oneToken;
dispatch_once(&onceToken,^{
// 只执行一次的代码(在这里默认是线程安全的)
});
8.5快速迭代
使用dispatch_apply函数能进行快速迭代遍历
dispatch_apply(10,dispatch_get_global_queue(0,0),^(size_t index){
// 执行10次代码, index顺序不确定
.....
})
8.6队列组
有点等同于依赖的关系,
但是对于某些特殊要求还是需要建立队列组来完成
比如有这么一个需求
1>首先:分别异步执行2个耗时操作
2>其次:等2个异步操作都执行完毕后,再回到主线程执行操作
dispatch_group_t group = dispatch_group_creat();
dispatch_group_async(group,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0),
^{
//
执行1个耗时的异步操作
});
dispatch_group_async(group,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0),
^{
//
执行1个耗时的异步操作
});
dispatch_group_notify(group,
dispatch_get_main_queue(),
^{
//
等前面的异步操作都执行完毕后,回到主线程...
});,,