1.线程与进程
1.什么是进程
进程是指在系统中正在运行的一个应用程序,每个进程之间都是相互独立的,各个进程都运行在其专用的且受保护的内存中。
2.什么是线程
线程是进程的基本执行单元。一个进程中的所有任务都在线程中执行
程序中默认会开一条线程,这条线程就是主线程。
3.线程与进程的区别
1.地址空间:同一进程之间的线程共享本进程的地址空间,而进程之间是相互独立的
2.资源拥有:同一进程内的线程共享本进程的资源,而进程之间是互相独立的。
3.一个进程崩溃后,不会对其他造成影响。但是一个线程崩溃了,这个进程就崩掉了。
4.进程切换时,消耗的资源大,效率低。而合理利用多线程能帮我们提高应用的执行效率。
5.线程是处理器调度的基本单元,但进程不是。
4.什么是多线程,多线程的优缺点
同一时间,CPU只能处理一条线程,多线程并发执行只是CPU在多条线程之间快速的切换。
优点:能适当提高CPU的占用率 提高程序执行效率
缺点:过多的开启线程会占用大量的CPU资源,降低程序的性能。
程序设计更加复杂。比如线程间的通信,多数据的安全共享等问题。
2.同步异步,串行并发
同步异步:同步任务不具备开启多线程的能力,异步任务具备开启多线程的能力。
串行并行:串行队列里面的任务是按照添加的顺序执行的,并行队列的任务在异步的情况是是并行执行的。
3.多线程的几种方式
多线程有四种方式实现:
1.pthread
纯C语言的多线程API,需要我们手动管理线程的生命周期
它具有跨平台和可移植的特性。支持windows,linux,unix等操作系统。这是其他三种不具有的优点
使用起来难度大,使用的频率也不高。
2.NSThread
对pthread的封装,他是一种面向对象的多线程实现方式。
需要手动管理线程的生命周期
简单易用,直接操作多线程对象
3.GCD
C语言写的多线程API,旨在替代NSThread
线程的生命周期完全由系统来管理,开发者只需要关注自己需要做的任务就行
GCD会自动利用CPU的多核来进行多线程之间的切换
它有多个封装好的函数方便我们使用,对开发极其方便
4.NSOperation
针对于GCD的封装,他比GCD更加面向对象,我们都是通过操作对象进行多线程操作,使用起来更加方便
我们也不需要去关心他的生命周期,系统为我们管理,我们也可以通过KVO来实时查看操作(NSOPeration)的生命周期
4.NSThread
由于我们平时基本用不到pthread,这里就去掉了pthread的描述,感兴趣的可以自行百度
NSThread的创建
NSThread *t1 = [[NSThread alloc]initWithTarget:self selector:@selector(test) object:nil];
t1.name = @"测试线程";
[t1 start];
[NSThread detachNewThreadSelector:@selector(detachNewThread) toTarget:self withObject:nil];
[self performSelectorInBackground:@selector(backgroundThread) withObject:nil];
这是三种通过NSThread创建新线程的方式,第一种可以自行设置新线程的属性,而第二第三种无法直接设置新线程的属性,但第二第三种方式的创建方式更加快捷。
线程的优先级 通过设置优先级的参数来控制
- +(double)threadPriority;
- +(BOOL)setThreadPriority:(double)p;
- -(double)threadPriority;
- -(BOOL)setThreadPriority:(double)p;
优先级的取值范围为0-1,默认0.5,值越大,优先级越高。
线程的生命周期的管理
1.启动线程
[t start];
2.阻塞暂停线程
- +(void)sleepUntilDate:(NSDate *)date;
- +(void)sleepForTimeInterval:(NSTimeInterval)time;
3.结束线程
+(void)exit;
5.GCD
队列和任务的组合图
如果当前队列是串行队列,一定要注意是给当前队列的同步任务中添加同步任务会造成队列中死锁。
主队列
系统默认开启的队列,我们直接可以使用,它是一个串行的队列。
tips:如果当前队列是主队列,我们给主队列添加同步任务会造成死锁
全局队列(全局并发队列)
没有名称直接使用
全局队列可以设置优先级
dispatch_queue_t golbal = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0);
队列的创建
dispatch_queue_t t2 = dispatch_queue_create("www.test1.com", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t t3 = dispatch_queue_create("www.test2.com", DISPATCH_QUEUE_CONCURRENT);
任务的创建
任务的创建非常简单,我们直接调用同步异步的block函数就行
dispatch_sync(t2, ^{
NSLog(@"t2---%@",[NSThread currentThread]);
});
dispatch_async(t3, ^{
NSLog(@"t3---%@",[NSThread currentThread]);
});
GCD的高级函数
1.一次函数 dispatch_once
一般用于保证区块代码只需要执行一次的时候使用,例如单例
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//这里面进行具体的代码逻辑
});
2.栅栏函数 dispatch_barrier_async
栅栏函数的作用是保证异步函数之前的操作都执行完了,才去执行栅栏函数里面的操作,然后栅栏函数里面的任务执行完才能接着执行后面的操作。
dispatch_queue_t t3 = dispatch_queue_create("www.test2.com", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(t3, ^{
NSLog(@"我在进行请求A");
});
dispatch_async(t3, ^{
NSLog(@"我在进行请求B");
});
dispatch_async(t3, ^{
NSLog(@"我在进行请求C");
});
dispatch_barrier_async(t3, ^{
NSLog(@"我在进行请求E");
});
dispatch_async(t3, ^{
NSLog(@"我在进行请求D");
});
3.队列组 dispatch_group_async
队列组的作用:多个队列都在队列组里面,每个队列里面都有很多任务,在所有的任务完成之后,需要通知所有的任务已完成
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t t4 = dispatch_queue_create("haha", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t t5 = dispatch_queue_create("haha2", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, t4, ^{
for(int i=0;i<10;i++){
NSLog(@"%i----t4",i);
}
});
dispatch_group_async(group, t5, ^{
for(int i=0;i<10;i++){
NSLog(@"%i---t5",i);
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有的任务已执行,我通知一下你");
});
4.dispatch_group_enter、dispatch_group_leve
dispatch_group_enter 通知group,有任务要被添加到group中执行了
dispatch_group_leve 任务完成后,通知group,要把这个任务移除掉
dispatch_group_enter、dispatch_group_leve是一一对应的
他不会对任务的执行顺序有任何影响
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t t4 = dispatch_queue_create("haha", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_enter(group);
dispatch_group_async(group, t4, ^{
for(int i=0;i<10;i++){
NSLog(@"%i----t4",i);
}
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, t4, ^{
for(int i=0;i<10;i++){
NSLog(@"%i---t5",i);
}
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有的任务已执行,我通知一下你");
});
5.信号量
信号量类似于锁的作用,当我们需要对某块代码进行加锁的时候调用
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER),这个时候线程会等待,当我们调用dispatch_semaphore_signal(sem)就会对当前被锁定的线程解锁。
一个直接的例子,我们对网络请求按照顺序请求,所有的请求之后才去刷新UI
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
for(int i=0;i<10;i++){
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%d---%d",i,i);
dispatch_semaphore_signal(sem);
}];
[task resume];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
};
6.NSOperation
一图值千金😄 感谢博客园大佬雅之上善若水 超级详细的NSOperation分析
NSOperation的使用
使用子类NSBlockOperation或者NSInvocationOperation进行多线程操作
重写NSOperation
NSOperation的使用
在没有使用 NSOperationQueue、在主线程中单独使用使用子类 NSInvocationOperation执行一个操作的情况下,操作是在当前线程执行的,并没有开启新线程。NSBlockOPeration如果也没有使用 NSOperationQueue,只有单个操作的话也不会开启新线程。只有添加新的操作之后才会开启新线程。
NSOperation配合NSOperationQueue实现多线程操作
设置线程的优先级 改变操作在队列中的优先级,可以改变操作的执行顺序
NSOperationQueue *q = [[NSOperationQueue alloc]init];[op setQueuePriority:NSOperationQueuePriorityHigh];
[op1 setQueuePriority:NSOperationQueuePriorityLow];
[op2 setQueuePriority:NSOperationQueuePriorityNormal];
设置线程之间的依赖
NSOperationQueue *q = [[NSOperationQueue alloc]init];
[op2 addDependency:op];
[op2 addDependency:op1];
[q addOperation:op];
[q addOperation:op1];
[q addOperation:op2];
查看线程的生命周期
NSOperation的几种生命周期
执行中:executing
完成:finished
完成的时候可以通过completionBlock来调用
就绪:isReady
tips:如果已经执行的操作,使用cancel是无法取消的
通过KVO可以获取操作的状态
如何实现一个串行队列 通过控制最大并发量
队列的取消,暂停,恢复
取消队列的所有操作
- (void)cancelAllOperations;
提示:也可以调用NSOperation的- (void)cancel方法取消单个操作
暂停和恢复队列 - (void)setSuspended:(BOOL)b; // YES代表暂停队列,NO代表恢复队列
- (BOOL)isSuspended;
NSOperation的重写
如果只重写了main方法,底层控制变更任务执行完成状态,以及任务退出。
如果重写了start方法,自行控制任务状态。
7.GCD和NSOperation的对比
1.GCD和NSOperation都是自动管理线程的生命周期的,开发者只关注具体的业务逻辑,不需要编写任何的线程管理的相关代码
2.GCD会自动利用苹果的多核进行多线程的调度处理,NSOperation是基于GCD的,他也拥有GCD的这个特性
3.GCD是将任务添加到队列,然后调用同步异步函数来执行任务。而NSOperation是将操作添加到队列中,就会执行。
4.GCD提供了NSOperation不具备的功能
1.一次函数
2.栅栏函数
3.调度组
4.信号量
5.NSOperation基于GCD也封装了一些独有的功能
1.最大并发数
2.线程的优先级
3.线程之间的依赖
4.队列的暂停/继续
5.取消队列的操作
6.操作的生命周期的状态
什么时候使用GCD,什么时候使用NSOperation
1.如果两个都能轻松实现,根据个人喜好选型
2.如果是需要写一次函数,栅栏函数,信号量这些的话使用GCD他有更简单的函数
3.如果想要对操作有更多的限制,比如设置依赖,优先级,暂停,取消,恢复,查询操作状态这些功能的话NSOperation更合适
9.多线程常见面试题
并行与并发
并行:两个或者多个事件在同一时间内发生 通过栅栏函数和队列组通知都能实现这个效果
并发:两个或者多个事件在同一时间间隔内发生 比如我们需要完成A请求才能去完成B请求,针对GCD我们可以使用信号量来限制,NSOperation通过线程间的依赖来实现
并发和串行的区别
并发的关键是你有处理多个任务的能力,不一定要同时
并发是异步的串行
1.多读单写
多个人可以读,但是写的时候只有一个人能写
读读并发,读写互斥,写写互斥
2.系统是怎样移除一个isFinished=YES的NSOperation的?
当一个任务完成后,系统通过KVO的方式来移除NSOperationQueue中对应的NSOperation,以达到能正常退出或者说销毁NSOperation的作用。