线程和进程:
线程是指进程中的一个执行流程,程序执行流的最小单元,一个进程要想执行任务,必须至少有一条线程.应用程序启动的时候,系统会默认开启一条线程,也就是主线程
进程是一个内存中运行的应用程序,一个具有一定独立功能的程序关于某次数据集合的一次运行活动,它是操作系统分配资源的基本单元;每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内,拥有独立运行所需的全部资源
进程和线程的关系
1.线程是进程的执行单元,进程的所有任务都在线程中执行
2.线程是 CPU 分配资源和调度的最小单位
3.一个程序可以对应多个进程(多进程),一个进程中可有多个线程,但至少要有一条线程
4.同一个进程内的线程共享进程资源
多线程的实现原理:事实上,同一时间内单核的CPU只能执行一个线程,多线程是CPU快速的在多个线程之间进行切换(调度),造成了多个线程同时执行的假象。
优点:
能适当提高程序的执行效率
能适当提高资源利用率(CPU、内存利用率)
缺点:
开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
线程越多,CPU在调度线程上的开销就越大
NSThread
NSThread是封装程度最小最轻量级的,使用更灵活,但要手动管理线程的生命周期、线程同步和线程加锁等,开销较大;
/* 1 是否开启了多线程 */
BOOL isMultiThreaded = [NSThread isMultiThreaded];
/* 2 获取当前线程 */
NSThread *currentThread = [NSThread currentThread];
/* 3 获取主线程 */
NSThread *mainThread = [NSThread mainThread];
/* 1 线程实例对象创建与设置 */
NSThread *newThread= [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
/* 设置线程优先级threadPriority(0~1.0),即将被抛弃,将使用qualityOfService代替 */
newThread.threadPriority = 1.0;
newThread.qualityOfService = NSQualityOfServiceUserInteractive;
/* 开启线程 */
[newThread start];
启动流程:
start() ——> 创建pthread ——> main() ——> [target performSelector:] ——> exit()
/* 2 静态方法快速创建并开启新线程 */
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
[NSThread detachNewThreadWithBlock:^{
NSLog(@"block run...");
}];
/** NSObejct基类隐式创建线程的一些静态工具方法 **/
/* 1 在当前线程上执行方法,延迟2s */
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
/* 2 在指定线程上执行方法,不等待当前线程 */
[self performSelector:@selector(run) onThread:newThread withObject:nil waitUntilDone:NO];
/* 3 后台异步执行函数 */
[self performSelectorInBackground:@selector(run) withObject:nil];
/* 4 在主线程上执行函数 */
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];
GCD
https://juejin.im/post/58fd55c08d6d81005898df46#heading-5
GCD(Grand Central Dispatch),又叫大中央调度,对线程操作进行了封装,加入了很多新的特性,内部进行了效率优化,提供了简洁的C语言接口,使用更加简单高效,也是苹果推荐的方式
同步dispatch_sync与异步dispatch_async任务派发
队列
Dispatch Queue是执行处理的等待队列,按照任务(block)追加到队列里的顺序,先进先出执行处理。
而等待队列有两种
Serial Dispatch Queue:串行队列,等待当前执行任务处理结束的队列。
Concurrent Dispatch Queue:并发队列,不等待当前执行任务处理结束的队列。
dispatch_once_t只执行一次
dispatch_after延后执行
串行指的是队列内任务一个接一个的执行,任务之间要依次等待不可重合,且添加的任务按照先进先出FIFO的顺序执行
并行指的是同一个队列先后添加的多个任务可以同时并列执行,任务之间不会相互等待,且这些任务的执行顺序执行过程不可预测
GCD多线程编程时经常会使用dispatch_async和dispatch_sync函数往指定队列中添加任务块,区别就是同步和异步。同步指的是阻塞当前线程,要等添加的耗时任务块block完成后,函数才能返回,后面的代码才可以继续执行。如果在主线上,则会发生阻塞,用户会感觉应用不响应,这是要避免的。
/* 1. 提交异步任务 */
NSLog(@"开始提交异步任务:");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
/* 耗时任务... */
[NSThread sleepForTimeInterval:10];
});
NSLog(@"异步任务提交成功!");
/* 2. 提交同步任务 */
NSLog(@"开始提交同步任务:");
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
/* 耗时任务... */
[NSThread sleepForTimeInterval:10];
});
NSLog(@"同步任务提交成功!");
/* 创建一个并发队列 */
dispatch_queue_t concurrent_queue = dispatch_queue_create("demo.gcd.concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
/* 创建一个串行队列 */
dispatch_queue_t serial_queue = dispatch_queue_create("demo.gcd.serial_queue", DISPATCH_QUEUE_SERIAL);
dispatch_once_t 这个函数控制指定代码只会被执行一次,常用来实现单例模式,这里以单例模式实现的模板代码为例展示dispatch_once_t的用法,其中的实例化语句只会被执行一次:
+ (instancetype *)sharedInstance {
static dispatch_once_t once = 0;
static id sharedInstance = nil;
dispatch_once(&once, ^{
// 只实例化一次
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
dispatch_after 通过该函数可以让要提交的任务在从提交开始后的指定时间后执行,也就是定时延迟执行提交的任务,使用方法很简单:
// 定义延迟时间:3s
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
dispatch_after(delay, dispatch_get_main_queue(), ^{
// 要执行的任务...
});
dispatch_group_t
组调度可以实现等待一组操作都完成后执行后续操作,典型的例子是大图片的下载,例如可以将大图片分成几块同时下载,等各部分都下载完后再后续将图片拼接起来,提高下载的效率。使用方法如下:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /*操作1 */ });
dispatch_group_async(group, queue, ^{ /*操作2 */ });
dispatch_group_async(group, queue, ^{ /*操作3 */ });
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 后续操作... });
dispatch_ barrier_async:栅栏 解决多读单写问题
GCD 信号量:dispatch_semaphore :
Dispatch Semaphore提供了三个方法:
dispatch_semaphore_create:创建一个 Semaphore 并初始化信号的总量
dispatch_semaphore_signal:发送一个信号,让信号总量加 1
dispatch_semaphore_wait:可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
Dispatch Semaphore 在实际开发中主要用于:
保持线程同步,将异步执行任务转换为同步执行任务
保证线程安全,为线程加锁
NSOperation
NSOperation是基于GCD的一个抽象基类,将线程封装成要执行的操作,不需要管理线程的生命周期和同步,但比GCD可控性更强,例如可以加入操作依赖(addDependency)、设置操作队列最大可并发执行的操作个数(setMaxConcurrentOperationCount)、取消操作(cancel)任务状态的控制等等
NSOperation作为抽象基类不具备封装我们的操作的功能,需要使用两个它的实体子类:NSBlockOperation和NSInvocationOperation,或者继承NSOperation自定义子类。
NSInvocationOperation *invoOpertion = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
[invoOpertion start];
/* NSBlockOperation初始化 */
NSBlockOperation *blkOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"NSBlockOperation");
}];
[blkOperation start];
另外NSBlockOperation可以后续继续添加block执行块,操作启动后会在不同线程并发的执行这些执行快:
/* NSBlockOperation初始化 */
NSBlockOperation *blkOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"NSBlockOperationA");
}];
[blkOperation addExecutionBlock:^{
NSLog(@"NSBlockOperationB");
}];
[blkOperation addExecutionBlock:^{
NSLog(@"NSBlockOperationC");
}];
[blkOperation start];
另外说了NSOperation的可控性比GCD要强,其中一个非常重要的特性是可以设置各操作之间的依赖,即强行规定操作A要在操作B完成之后才能开始执行,成为操作A依赖于操作B:
/* 获取主队列(主线程) */
NSOperationQueue *queue = [NSOperationQueue mainQueue];
/* 创建a、b、c操作 */
NSOperation *c = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"OperationC");
}];
NSOperation *a = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"OperationA");
}];
NSOperation *b = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"OperationB");
}];
/* 添加操作依赖,c依赖于a和b,这样c一定会在a和b完成后才执行,即顺序为:A、B、C */
[c addDependency:a];
[c addDependency:b];
/* 添加操作a、b、c到操作队列queue(特意将c在a和b之前添加) */
[queue addOperation:c];
[queue addOperation:a];
[queue addOperation:b];
任务状态控制:
自定义任务状态控制:
如果只重写了main()方法,底层控制变更任务执行完成状态以及任务退出
如果重写了start方法, 自行控制任务状态
isReady
isExecuting
isFinished
isCancelled
多线程在iOS开发中的应用 :
显示\刷新UI界面
处理UI事件(比如点击事件、滚动事件、拖拽事件等)
别将比较耗时的操作放到主线程中
耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验
线程安全:
锁是最常用的同步工具。一段代码段在同一个时间只能允许被一个线程访问
13种加锁方案:
(1)OSSpinLock自旋锁
首先要提的是OSSpinLock已经出现了BUG,导致并不能完全保证是线程安全的。
(2) 条件信号量dispatch_semaphore_t
dispatch_semaphore_t GCD中信号量,也可以解决资源抢占问题,支持信号通知和信号等待。每当发送一个信号通知,则信号量+1;每当发送一个等待信号时信号量-1,;如果信号量为0则信号会处于等待状态,直到信号量大于0开始执行。
dispatch_semaphore_t semaphore;
* 创建一个信号量为1的信号
semaphore=dispatch_semaphore_create(1);
semaphore:等待信号 DISPATCH_TIME_FOREVER:等待时间 wait之后信号量-1,为0
dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);if(imageNames.count>0){imageName=[imageNames lastObject];[imageNames removeObject:imageName];}/**
* 发送一个信号通知,这时候信号量+1,为1
*/dispatch_semaphore_signal(semaphore);
(3) pthread_mutex条件锁
(4) NSLock
lock,加锁
unlock,解锁
tryLock,尝试加锁,如果失败了,并不会阻塞线程,只是立即返回
- (void)getIamgeName:(NSMutableArray *)imageNames{
NSString *imageName;
[lock lock];
if (imageNames.count>0) {
imageName = [imageNames lastObject];
[imageNames removeObject:imageName];
}
[lock unlock];
}
(5)条件锁NSCondition
NSCondition同样实现了NSLocking协议,所以它和NSLock一样,也有NSLocking协议的lock和unlock方法,可以当做NSLock来使用解决线程同步问题,用法完全一样。
同时,NSCondition提供更高级的用法。wait和signal,和条件信号量类似。
但是正是因为这种分别加锁的方式,NSCondition使用wait并使用加锁后并不能真正的解决资源的竞争。比如我们有个需求:不能让m<0。假设当前m=0,线程A要判断到m>0为假,执行等待;线程B执行了m=1操作,并唤醒线程A执行m-1操作的同时线程C判断到m>0,因为他们在不同的线程锁里面,同样判断为真也执行了m-1,这个时候线程A和线程C都会执行m-1,但是m=1,结果就会造成m=-1.
当我用数组做删除试验时,做增删操作并不是每次都会出现,大概3-4次后会出现。单纯的使用lock、unlock是没有问题的。
(6) pthread_mutex递归锁
(7) 递归锁NSRecursiveLock 可以重复加锁 解锁 不会造成死锁
有时候“加锁代码”中存在递归调用,递归开始前加锁,递归调用开始后会重复执行此方法以至于反复执行加锁代码最终造成死锁,这个时候可以使用递归锁来解决。使用递归锁可以在一个线程中反复获取锁而不造成死锁,这个过程中会记录获取锁和释放锁的次数,只有最后两者平衡锁才被最终释放。
- (void)getIamgeName:(NSMutableArray *)imageNames{
NSString *imageName;
[lock lock];
if (imageNames.count>0) {
imageName = [imageNames firstObject];
[imageNames removeObjectAtIndex:0];
[self getIamgeName:imageNames];
}
[lock unlock];
}
(8) 条件锁NSConditionLock
也有人说这是个互斥锁
NSConditionLock同样实现了NSLocking协议,试验过程中发现性能很低。
NSConditionLock也可以像NSCondition一样做多线程之间的任务等待调用,而且是线程安全的。
- (void)getIamgeName:(NSMutableArray *)imageNames{
NSString *imageName;
[lock lockWhenCondition:1]; //加锁
if (imageNames.count>0) {
imageName = [imageNames lastObject];
[imageNames removeObjectAtIndex:0];
}
[lock unlockWithCondition:0]; //解锁
}
- (void)createImageName:(NSMutableArray *)imageNames{
[lock lockWhenCondition:0];
[imageNames addObject:@"0"];
[lock unlockWithCondition:1];
}
(9) @synchronized代码块
- (void)getIamgeName:(int)index{
NSString *imageName;
@synchronized(self) {
if (imageNames.count>0) {
imageName = [imageNames lastObject];
[imageNames removeObject:imageName];
}
}
}
dispatch_queue(DISPATCH_QUEUE_SERIAL)
dispatch_barrier_async栅栏
dispatch_group调度组
os_unfair_lock互斥锁
面试题:
1. 线程是什么?进程又是什么?两者之间的区别?
2. 串行并行 同步异步的区别?zzzz
并行:充分利用计算机的多核,在多个线程上同步进行
并发:在一条线程上通过快速切换,让人感觉在同步进行
3. 子线程与子线程之间是如何进行通信的?
4.GCD的一些常用的函数?(group,barrier,信号量,线程同步)?zzzz
5.如何使用队列来避免资源抢夺?
当我们使用多线程来访问同一个数据的时候,就有可能造成数据的不准确性。这个时候我么可以使用线程锁的来来绑定。也是可以使用串行队列来完成。如:fmdb就是使用FMDatabaseQueue,来解决多线程抢夺资源。
6.线程同步的几个策略?知道哪几种锁及其它们之前区别?
7.GCD的队列(dispatch_queue_t)分哪两种类型?zzzz
串行串行调度队列
并发数量并行调度队列
8.如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)
使用调度组追加块到全局组队列,这些块如果全部执行完毕,就会执行主调度队列中的结束处理的块。
dispatch_queue_t队列= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);dispatch_group_t组= dispatch_group_create();dispatch_group_async(group,queue,^ { / *加载图片1 * / });dispatch_group_async(group,queue,^ { / *加载图片2 * / });dispatch_group_async(group,queue,^ { / *加载图片3 * / }); dispatch_group_notify(group,dispatch_get_main_queue(),^ {
//合并图片
});
9.dispatch_barrier_async的作用是什么?
可以解决多读单写
在并发等级中,为了保持某些任务的顺序,需要等待一些任务完成后才能继续进行,使用barrier来等待任务完成,避免数据dispatch_barrier_async竞争等问题。 函数会等待追加到并发调度队列全部执行完之后,然后再执行dispatch_barrier_async函数追加的处理,等dispatch_barrier_async追加的处理执行结束之后,并发调度队列才恢复之前的动作继续执行。
打个比方:某种你们公司周末跟团旅游,高速休息站上,司机说:大家都去上厕所,速战速决,上完厕所就上高速。超大的公共厕所,大家同时去,程序猿很快就结束了,但程序媛就可能会慢一些,即使你第一个回来,司机也不会出发,司机要等待所有人都回来后,才能出发。dispatch_barrier_async函数追加的内容就如同“上完厕所就上高速”这个动作。
10.苹果为什么要废弃dispatch_get_current_queue?
dispatch_get_current_queue由于派发模型是按层级来组织的,这意味着排在某条串联的中的块会在其上级串联里执行。队列是否为执行同步派发所用的队列这种方法并不总是奏效。dispatch_get_current_queue函数通常会被用于解决由不可以重入的代码所引发的死锁,然后能用此函数解决的问题,通常也可以用“特定特定数据”来解决。
11. 您是否做过异步的网络处理和通讯方面的工作?如果有,能具体介绍一些实现策略么?
12.有哪些场景是NSOperation比GCD更容易实现的?(或是NSOperation优于GCD的几点,知道多少说多少)
13. 说一说你对线程安全的理解?iOS上怎么保证线程安全?
14. 列举你知道的线程同步策略?
15. 有哪几种锁?各自的原理和优缺点?它们之间的区别是什么?最好可以结合使用场景来说,琐是毫秒级别还是微妙级别?
16. APP内通信的方式有什么?
17. GCD和Operation的比较,有用过其中的一种么?
18.GCD中的Block用到的属性是否需要__weak修饰
否
19.线程同步有哪些方式?
20.多个线程同时访问一个资源 需要注意什么?
21. 说一下多线程,你平常是怎么用的?
同时最多有几个线程: 根据cpu的能力,目测:50个
22. NSOperationQueue中的maxConcurrentOperationCount默认值
23. 异步一定会开启新的线程吗?
24. GCD如何取消线程?
dispatch_block_cancel类似NSOperation一样,可以取消还未执行的线程。但是没办法做到取消一个正在执行的线程。
使用临时变量+return 方式取消 正在执行的Block
25. 子线程默认不会开启 Runloop,那出现 Autorelease 对象如何处理?不手动处理会内存泄漏吗?
在子线程你创建了 Pool 的话,产生的 Autorelease 对象就会交给 pool 去管理。如果你没有创建 Pool ,但是产生了 Autorelease 对象,就会调用 autoreleaseNoPage 方法。在这个方法中,会自动帮你创建一个 hotpage(hotPage 可以理解为当前正在使用的 AutoreleasePoolPage,如果你还是不理解,可以先看看 Autoreleasepool 的源代码,再来看这个问题 ),并调用 page->add(obj)将对象添加到 AutoreleasePoolPage 的栈中,也就是说你不进行手动的内存管理,也不会内存泄漏啦!StackOverFlow 的作者也说道,这个是 OS X 10.9+和 iOS 7+ 才加入的特性
26.聊对于 GCD 的理解,和 GCD 底层是如何进行线程调度的?
首先,线程的生命周期包含:新建(create),就绪(ready),运行(run),阻塞(block), 死亡(dead) iOS的线程管理分为,pThread、NSThread、GCD、NSOperationpthread:POSIX标准,基于C语言的通用跨平台线程管理API。NSThread:最早期apple管理线程的apiNSOperation:基于GCD的面向对象线程管理ApiGCD:代替NSThread,C语言的线程管理Api
GCD有一个线程池底层,并不需要开发者额外编写,线程池中的线程有着良好的重用性,当一段时间后这个线程没有被调用胡话,这个线程就会被销毁。那么我们只需要向线程队列中添加任务,线程队列调度就行了。 如果队列中存放的是同步任务,则任务出队后,底层线程池中会提供一条线程供这个任务执行,任务执行完毕后这条线程再回到线程池。 如果队列中存放的是异步的任务,当任务出队后,底层线程池会提供一个线程供任务执行,因为是异步执行,队列中的任务不需等待当前任务执行完毕就可以调度下一个任务,这时底层线程池中会再次提供一个线程供第二个任务执行,执行完毕后再回到底层线程池中。 这样就对线程完成一个复用,而不需要每一个任务执行都开启新的线程,也就从而节约的系统的开销,提高了效率。
27.说@synchronized锁的实现原理,并说明其中可能存在的问题?
@synchronized是对mutex递归锁的封装,@synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作。最大的问题就是,效率低,传入对象必须等待之前的锁执行完成之后才能执行,无法达到异步的效果
28.多线程可以访问同一个对象吗,多进程呢?
29.有哪些场景是NSOperation比GCD更容易实现的?(或是NSOperation优于GCD的几点,知道多少说多少
GCD底层使用C语言编写高效、NSOperation是对GCD的面向对象的封装。对于特殊需求,如取消任务、设置任务优先级、任务状态监听,NSOperation使用起来更加方便。
NSOperation可以设置依赖关系,而GCD只能通过dispatch_barrier_async实现
NSOperation可以通过KVO观察当前operation执行状态(执行/取消)
NSOperation可以设置自身优先级(queuePriority)。GCD只能设置队列优先级(DISPATCH_QUEUE_PRIORITY_DEFAULT),无法在执行的block中设置优先级
NSOperation可以自定义operation如NSInvationOperation/NSBlockOperation,而GCD执行任务可以自定义封装但没有那么高的代码复用度
GCD高效,NSOperation开销相对高
30. 系统是怎么样移除一个isFinished=YES的NSOperation?
通过KVO;
31. 多个网络请求完成后如何执行下一步?
(1) 使用GCD的dispatch_group_t
NSString *str = @"http://xxxx.com/";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
dispatch_group_t downloadGroup = dispatch_group_create();
for (int i=0; i<10; i++) {
dispatch_group_enter(downloadGroup);
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%d---%d",i,i);
dispatch_group_leave(downloadGroup);
}];
[task resume];
}
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
NSLog(@"end");
});
(2) 使用GCD的信号量dispatch_semaphore_t
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);
count++;
if (count==10) {
dispatch_semaphore_signal(sem);
count = 0;
}
}];
[task resume];
}
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"end");
});
32. GCD执行原理?
GCD有一个底层线程池,这个池中存放的是一个个的线程。之所以称为“池”,很容易理解出这个“池”中的线程是可以重用的,当一段时间后这个线程没有被调用胡话,这个线程就会被销毁。注意:开多少条线程是由底层线程池决定的(线程建议控制再3~5条),池是系统自动来维护,不需要我们程序员来维护(看到这句话是不是很开心?) 而我们程序员需要关心的是什么呢?我们只关心的是向队列中添加任务,队列调度即可。
如果队列中存放的是同步任务,则任务出队后,底层线程池中会提供一条线程供这个任务执行,任务执行完毕后这条线程再回到线程池。这样队列中的任务反复调度,因为是同步的,所以当我们用currentThread打印的时候,就是同一条线程。
如果队列中存放的是异步的任务,(注意异步可以开线程),当任务出队后,底层线程池会提供一个线程供任务执行,因为是异步执行,队列中的任务不需等待当前任务执行完毕就可以调度下一个任务,这时底层线程池中会再次提供一个线程供第二个任务执行,执行完毕后再回到底层线程池中。
这样就对线程完成一个复用,而不需要每一个任务执行都开启新的线程,也就从而节约的系统的开销,提高了效率。在iOS7.0的时候,使用GCD系统通常只能开58条线程,iOS8.0以后,系统可以开启很多条线程,但是实在开发应用中,建议开启线程条数:35条最为合理。
33. 线程死锁的四个条件?
1、互斥:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
2、占有且等待:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
3、不可抢占:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
4、循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。
34.