GCD多线程问题整理
1.GCD队列有哪几种类型?有哪几种队列?
GCD队列分为串行队列、并行队列两种类型;队列有主串行
队列、全局并行
队列(在系统主线程空闲时才会执行)、自定义队列;
2.如何用GCD同步若干个异步调用(如根据若干个URL异步加载多张图片,然后在都下载完成后合成一张整图)?
使用Dispatch Group追加block到Global Group Queue,这些block全部执行完毕,回到主线程;
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_sync(queue, ^{
NSLog(@"加载图片1");
[self load1];
});
});
dispatch_group_async(group, queue, ^{
/**加载图片2*/
dispatch_sync(queue, ^{
NSLog(@"加载图片2");
[self load2];
});
});
dispatch_group_async(group, queue, ^{
/**加载图片3*/
dispatch_sync(queue, ^{
NSLog(@"加载图片3");
[self load3];
});
});
// //等待上面的任务全部完成后,会往下继续执行 (会阻塞当前线程)
// dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//等待上面的任务全部完成后,会收到通知执行block中的代码 (不会阻塞线程)
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
/**合并图片*/
NSLog(@"Method1-全部任务执行完成");
});
- (void)load1 {
sleep(2);
NSLog(@"加载图片1,沉睡2s");
}
- (void)load2 {
sleep(5);
NSLog(@"加载图片2,沉睡5s");
}
- (void)load3 {
sleep(8);
NSLog(@"加载图片3,沉睡8s");
}
dispatch_group_wait :在任务组完成时调用,或者任务组超时是调用(完成指的是enter和leave次数一样多,leave多崩溃,enter多未完成)
dispatch_group_notify:不管超不超时,只要任务组完成,会调用,不完成不会调用
dispatch_group_async(group, queue, block) 和 dispatch_group_notify(group, queue, block) 组合在执行同步任务时正常,在执行异步任务时不正常。
3.GCD怎么保证任务的按顺序执行,等待一些任务完成后才能继续进行?
使用dispatch_barrier_async;在并行队列中,为了保持某些任务的顺序,需要等待一些任务完成后才能继续进行,使用barrier来等待之前任务完成,避免数据竞争等问题;
4.苹果为什么废弃dispatch_get_current_queue?
dispatch_get_current_queue容易造成死锁,执行线程时,会产生一个与之绑定的队列,而执行dispatch_get_current_queue时,产生的队列可能与该线程没有关系,所以可能造成死锁;
5.dispatch_after的作用?
dispatch_after是来延迟执行的GCD方法,dispatch_after能让我们添加进队列的任务延迟执行,该函数并不是在指定时间后
执行处理,而只是在指定时间追加到自己需要的队列中
去执行。(在对时间要求非常精准的情况下才可能会出现问题。)dispatch_after参数:第一个参数是time,第二个参数是想加入那个队列,第三个参数需要执行的block。
6.GCD如何创建串行队列、并行队列?(同步、异步线程的创建略)
dispatch_queue_t queue = dispatch_queue_create("队列名"
,队列类型(DISPATCH_QUEUE_SERIAL -串行队列,DISPATCH_QUEUE_CONCURRENT -并行队列)
);
7.GCD之线程怎么形成死锁的?
参考自:https://www.cnblogs.com/LDSmallCat/p/4910080.html
正确代码如下:(同步:不会立即返回,即阻塞当前线程,等待block同步执行完成;异步:立即返回, block会在后台异步执行
)
-(void)testThreadDeadlock {
① NSLog(@"0 ---- %@", [NSThread currentThread]);
② // 创建一个串行队列
③ dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
④ dispatch_async(queue, ^{// block1开始
⑤ NSLog(@"1 ---- %@", [NSThread currentThread]);
⑥
⑦ dispatch_async(queue, ^{// block2开始
⑧ NSLog(@"2 ---- %@", [NSThread currentThread]);
⑨ });// block2结束
⑩
⑪ NSLog(@"3 ---- %@", [NSThread currentThread]);
⑫ });// block1结束
⑬ NSLog(@"4 ---- %@", [NSThread currentThread]);
}
案例分析:
1.
调用testThreadDeadlock方法执行到①时没有开启线程,在主线程中打印;
2.
执行到④时检测到异步函数async,此时async函数直接返回,不会等函数中的block1执行完毕才返回,执行到④时系统有两件事要做,第一跳至⑬行打印,第二开启一个新的线程1(是子线程而非主线程,假设是子线程1)执行block1当中的内容。所以看到主线程先打印NSLog(@"0 ---- %@", [NSThread currentThread]);、NSLog(@"4 ---- %@", [NSThread currentThread]);,打印结束就没有主线程的事情了,接下来子线程登场;
3.
子线程1执行block1的内容:执行⑤行打印,在(子线程1)打印;执行到⑦行检测到async异步函数,直接返回,不等待block2执行完毕,程序继续向下执行,即打印⑪行NSLog(@"3 ---- %@", [NSThread currentThread]);(子线程1打印),同时又创建一个 (子线程2)
4.
现在分析一下程序运行到这里一共经历了几个线程?3个,主线程1个,④行、⑦行分别创建了两个子线程1和2;(怎么看是否创建了新的线程?"一般"来说看函数,是async异步执行还是sync同步执行,为什么要用"一般"?往下看)
那么问题来了,⑤行和⑪行的打印(即1、3打印)都在一个线程上我们可以理解(看线程地址),但是为什么⑧行的打印也和这两个在同一个线程呢?因为④行创建了一个子线程打印了⑤行和⑪行之后,这个线程的任务结束了,正常来说就要销毁了,没他什么事了。但是我们在⑦行又需要创建一个线程,创建线程需要时间和空间成本(占用内存)所以在子线程1执行完任务进入线程池要销毁的时候,发现系统还要再创建一个线程,ok那我就不销毁了,你也不用再创建了,所以线程1就被重复利用了,所以他们在一个线程上打印。
5.
回到正题,在子线程1执行⑤行、⑪行的打印之后,被系统重复利用,来执行⑦行block2的内容,⑧行打印结束一个消息循环结束,进入休眠等待下次触发。
6.
总结:为什么程序的打印结果会是这样的?与两个因素有关:函数是async异步执行还是sync同步执行;队列我们创建的是串行队列这个串行队列里面有两个任务block1和block2,串行队列遵循先进先出原则,block1在④行加入,先执行,block2在⑦行加入后执行。block1中有三个任务⑤行、⑪行打印和block2,且block2在两个打印中间,按照程序自上到下执行…问题又来了,为什么block2不是在两个打印任务之间执行?而是在打印结束后执行?这时就要看函数啦~
async这是什么?异步执行!要开启新的线程(或是重复利用线程池中已经创建的线程,什么样的线程可以重复利用?这个线程已经执行完它的任务,进入线程池马上销毁的线程才可以重复利用)(开启线程或是获取线程池中重复利用的线程是需要时间成本的)检测到这个函数怎么办?直接返回,不用等这个函数的block执行完就返回。什么意思?执行到⑦行的时候兵分两路,一路向下执行⑪行打印,一路从线程池中获取重复利用的线程处理block2的任务。在兵分两路的时候子线程1还没有处理完⑪行的打印,它的任务没有结束啊!为什么处理block2没有创建新的线程?因为需要时间和空间成本。GCD为我们做了优化,(怎么优化的我们不用操心~~看不到源码这是个迷,不过看到了也不一定看得懂!哈哈所以不创建新的线程,等子线程1执行完他的任务,我们重复利用它!ok说完了;
综上:
如果⑦行改为dispatch_sync同步操作,将会造成死锁,理由:执行dispatch_sync同步线程时,不会创建新线程,那么在⑦行将调用线程1,同时block1向下执行⑪行代码也要调用线程1,造成死锁;
8.分析自旋锁、互斥锁、NSLock、信号量、条件锁、对象锁 、递归锁?
自旋锁OSSpinLock停止使用,线程的优先级反转
可能导致线程死锁,使用os_unfair_lock_t(实质为互斥锁)代替;
所有锁(包括NSLock)的接口实际上都是通过NSLocking协议
定义的,它定义了lock
和unlock
方法。你使用这些方法来获取
和释放
该锁。
锁类型 | 概念描述 | 优劣对比 |
---|---|---|
自旋锁 |
自旋锁(OSSpinLock)创建锁:_pinLock = OS_SPINLOCK_INIT; 加锁:OSSpinLockLock(&_pinLock); 解锁:OSSpinLockUnlock(&_pinLock); 为实现保护共享资源而提出的一种锁机制。跟 互斥锁 一样,一个执行单元要想访问被自旋锁保护的共享资源 ,必须先得到锁,在访问完共享资源后,必须释放锁。如果在获取自旋锁时,没有任何执行单元保持该锁,那么将立即得到锁;如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。 |
在任何时刻,最多只能有一个保持者,都能保证线程安全。调度机制上,如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁,则等待资源的线程会立即执行。 |
互斥锁 |
互斥锁(pthread_mutex C语言 )不能由多个线程同时执行pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); 定义锁的类型(属性):pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); pthread_mutex_t mutex; 创建锁:pthread_mutex_init(&mutex, &attr); 申请锁:pthread_mutex_lock(&mutex); { 临界区 } 释放锁:pthread_mutex_unlock(&mutex); 互斥锁的实现原理与信号量非常相似,不是使用忙等,而是阻塞线程并睡眠,需要进行上下文切换。锁的类型,可以有 PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE 等等;如果lock区域代码,再次进行加锁,就会变成递归,因此我们要把锁的属性变成PTHREAD_MUTEX_RECURSIVE来避免死锁 |
在任何时刻,最多只能有一个保持者,都能保证线程安全。调度机制上,如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁,则等待资源的线程会被唤醒。如果临界区很短,忙等的效率也许更高; 由于 pthread_mutex 有多种类型,可以支持递归锁等,因此在申请加锁时,需要对锁的类型加以判断,这也就是为什么它和信号量的实现类似,但效率略低的原因。 底层的api,复杂的多线程处理建议使用,并且可以封装自己的多线程
|
信号量 |
信号量(dispatch_semaphore)互斥类型 锁是服务于共享资源的;而semaphore是服务于多个线程间的执行的逻辑顺序的。 信号量的本质就是调度线程!dispatch_semaphore_create(value) value是初始信号持有量 dispatch_semaphore_signal 信号量+1 dispatch_semaphore_wait 信号量-1 如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。 |
semaphore是通过一个值来实现线程的调度的,因此借助这种机制,我们也可以实现对线程数量的限制。休眠,需要上下文切换,耗时,占用时间比较长时候使用 注意:不能在中断中休眠! |
条件锁 |
条件锁(NSConditionLock) 互斥锁pthread_mutex的一种封装 |
1 |
对象锁 |
对象锁(NSLock) 对pthread_mutex锁的属性为normal的封装; 互斥锁 不能多次调用lock方法,会造成死锁
|
1 |
递归锁 |
递归锁(NSRecursiveLock) 对pthread_mutex锁的属性为recursive的封装; |
递归锁的性能出奇的高,但是只能作为递归使用,所以限制了使用场景; |
上下文切换的概念:对线程的上下文进行保存和恢复的过程(当发生操作系统事件时,如唤起系统调用等情况,内核会切换执行路径;执行中路径的状态,如CPU寄存器等信息会保存到各自路径专用的内存块中;从切换目标路径专用的内存块中,复原CPU寄存器等信息,继续执行切换路径的CPU命令;)
各锁的性能对比图:
锁性能对比图.png
NSCondition:使用其做多线程之间的通信调用不是线程安全的
NSConditionLock:单纯加锁性能非常低,比NSLock低很多,但是可以用来做多线程处理不同任务的通信调用
9. GCD、NSOperatinQueue、NSThread对比
GCD | NSOperatinQueue | NSThread | |
---|---|---|---|
解析 | 基于C语言框架,充分利用多核,苹果推荐的多线程技术 | 面向对象多线程编程 本质是对GCD的封装 |
轻量级多线程编程,真正的多线程编程技术,每一个NSThread对象对应一个线程 |
抽象程度 | 高 | 中 | 低 |
优点 | 基于C语言,是替代前两者的高效强大的技术,执行效率比前两者高,代码比较集中易维护 | 不用担心线程管理、同步的事情,主要精力在对线程的操作(暂停、继续和取消 )上,可规定最大并发数;是面向对象的,建立在GCD上,在架构上、优先级管理上做的比GCD好,可以很好支持GCD没有或者不完美的操作,比如取消线程、KVO监控、指定操作间的优先级或相互依赖 |
量级轻,使用简单 |
缺点 | - | 相对GCD来说代码比较分散,不容易管理 | 需要自己管理线程的生命周期、线程同步、睡眠、唤醒等,对线程加锁需要耗费一定系统开销 |
10.进程与线程的关系
进程概念: 进程是程序在计算机的一次执行活动,打开一个app,就开启了一个进程,可包含多个线程;
线程概念: 独立执行的代码段,一个线程同时间只能执行一次,反之多线程并发可以同一时间执行多个任务;
线程与进程的区别和联系?
- 线程是进程的基本单位。
- 进程和线程都是由操作系统所产生的,程序运行的基本单元,系统利用该基本单元实现系统对于应用的并发性。
- 进程和线程的主要差别,在于它们是不同的
操作系统资源管理方式
。- 进程有独立的地址空间,一个进程崩溃后,在保护模式下,不会对其他进程产生影响。
- 线程只是一个进程中的不同执行路径。
- 线程有自己的堆栈和局部变量,但线程之间没有独立的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
- 但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
使用多线程的优势:
1.
使用多线程可以减少程序的响应时间。在单线程(单线程指的是程序执行过程中只有一个有效操作的序列,不同操作之间都有明确的执行先后顺序)的情况下,如果某个操作很耗时,或者陷入长时间的等待(如等待网络响应),此时程序将不会响应鼠标和键盘等操作,使用多线程后,可以把这个耗时的线程分配到一个单独的线程去执行,使得程序具备了更好的交互性。
2.
与进程相比,线程的创建和切换开销更小。由于启动一个新的线程必须给这个线程分配独立的地址空间,建立许多数据结构来维护线程的代码段、数据段等信息,而运行于同一进程内的线程共享代码段、数据段,线程的启动或切换的开销比进程要少很多。同时多线程在数据共享方面效率非常高。
3.
多CPU或多核计算机本身就具有执行多线程的能力,如果使用单个线程,将无法重复利用计算机资源,造成资源的巨大浪费。因此在多CPU计算机上使用多线程能提高CPU的利用率。
4.
使用多线程能简化程序的结构,使程序便于理解和维护。一个非常复杂的程序可以分成多个线程来执行。
11. dispatch_apply
dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API,该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束。可以实现对数组array[]的遍历。
NSArray *array =@[@"",@"",@""];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply([array count], queue, ^(size_t index){
NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
});
12. dispatch_set_target_queue
用dispatch_queue_create函数生成的Dispatch Queue都是默认优先级的线程,要改变线程的优先级,dispatch_set_target_queue的作用就凸显出来了,栗子如下:
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);
不仅如此,该函数还可以改变执行层次,最底层的Serial Dispatch Queue和Concurrent Dispatch Queue在按照顺序在上一层的Serial Dispatch Queue中执行,避免最底层线程并行执行。
13.利用NSOperation与NSOperationQueue处理多线程时,有3个NSOperation分别为A,B,C,要求A,B执行完之后,才执行C,如何做?
对于queue中的operations改变执行顺序的取决2点:
1.
由 NSOperation 对象的依赖关系和queue的最大并发operation数量决定;
2.
由 NSOperation 的优先级确定;优先级则是NSOperation 对象的一个属性。默认所有的“普通”优先级,通过setQueuePriority
方法提升或者降低NSOperation的优先级,优先级只能应用于相同的queue的NSOperation,如果应用有多个NSOperationqueue,每个queue优先级等级是相互独立的。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:1];
NSBlockOperation *A= [NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"执行第1次操作,线程:%@", [NSThread currentThread]);
}];
NSBlockOperation *B = [NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"执行第2次操作,线程:%@", [NSThread currentThread]);
}];
NSBlockOperation *C = [NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"执行第3次操作,线程:%@", [NSThread currentThread]);
}];
[C setQueuePriority:NSOperationQueuePriorityVeryLow];
// C依赖于A,B
[C addDependency:A];
[C addDependency:B];
[queue addOperation:A];
[queue addOperation:B];
[queue addOperation:C];
// AB并行执行,顺序随机,最后执行C
14.多线程的底层实现?
1.线程:Mach是第一个以多线程方式处理任务的系统,因此多线程的底层实现机制是基于Mach的线程;
2.开发中很少用Mach级的线程,因为Mach级的线程没有提供多线程的基本特征,线程之间是独立的;
3.开发中实现多线程的方案:
(1) C语言的POSIX接口(可移植操作系统接口):#include<pthread.h>
(2) OC的NSThread
(3) C语言的GCD接口(性能最好,代码更简洁)
(4) OC的NSOperation和NSOperationQueue(基于GCD)
15.多线程的三大特性:
1.原子性:线程的一个或者多个操作要么全部执行,且执行过程不会被打断,要么全部都不执行;
2.可见性:可见性是指多个线程访问同一个变量的时候,一个线程修改了这个变量的值,其他线程也可以立刻看到这个修改后的值。Java提供关键字volatile来保证可见性;锁也能保证可见性;
3.有序性:即程序的执行顺序按照代码的先后顺序执行。
16.volatile关键字的作用?
volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次
需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。
17.dispatch_suspend、dispatchp_resume线程挂起与恢复
简单来说,就是可以暂停、恢复队列上的任务。但是这里的“挂起”,并不能保证可以立即停止队列上正在运行的block;dispatch_suspend并不会立即暂停正在运行的block,而是在当前block执行完成后,暂停后续的block执行。
18.列举Cocoa中常见的几种多线程的实现,并谈谈多线程安全的几种解决办法?
GCD、NSOperation、NSThread;自旋锁、互斥锁、信号量;
19.如果让你实现 GCD 的线程池,讲一下思路
线程池包含如下7个部分:1、线程池管理器(用于创建并管理线程池,单例);2、工作线程(线程池中的线程);3、任务接口(每个任务必须实现的接口,以供工作线程调度任务的执行);4、任务队列(用于存放没有处理的任务,提供一种缓冲机制);5、corePoolSize核心池的大小(默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数量达到corePoolSize后,就会把达到的任务放到缓存队列当中);6、maximumPoolSize线程池最大线程数(它表示在线程池中最多能创建多少个线程);7、keepAliveTime存活时间(表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,这时如果一个线程空闲的时间达到keepAliveTime,则会终止直到线程池中的线程数不大于corePoolSize);
具体流程如下:
1、
当通过任务接口向线程池管理器中添加任务时,如果当前线程池管理器中的线程数目小于corePoolSize,则每来一个任务,就会通过线程池管理器创建一个线程去执行这个任务;
2、
如果当前线程池中的线程数目大于等于corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;
3、
如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
4、
如果线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;
20.Global Dispatch Queue全局队列的四种执行优先级?
高优先级(High Priority,参数:DISPATCH_QUEUE_PRIORITY_HIGH)、默认优先级(Default Priority,参数:DISPATCH_QUEUE_PRIORITY_DEFAULT)、低优先级(Low Priority,参数:DISPATCH_QUEUE_PRIORITY_LOW)、后台优先级(Background Priority,参数:DISPATCH_QUEUE_PRIORITY_BACKGROUND)
21.使用GCD以及block时要注意些什么?它们两是一回事儿么?block在ARC中和传统的MRC中的行为和用法有没有什么区别,需要注意些什么?
block的使用需注意:1、block的内存管理;2、防止循环强引用;
(1、block 在实现时就会对它引用到的,它所在方法中定义的栈变量进行一次只读拷贝,然后在 block 块内使用该只读拷贝;2、非内联(inline) block 不能直接访问 self,只能通过将 self 当作参数传递到 block 中才能使用,并且此时的 self 只能通过 setter 或 getter 方法访问其属性,不能使用句点式方法。但内联 block 不受此限制;)
不是一回事;
block避免循环强引用需要注意:1、非ARC(MRC):__block修饰的变量的引用计算是不变的
(还有修改外部临时变量);2、ARC:__weak/__unsafe_unretained
22.线程之间是如何通信的?
NSThread类提供了两个比较常用的方法,用于实现线程间的通信,这两个方法的定义格式如下:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
NSMachPort:(基本机制:A线程父线程
创建NSMachPort对象,并加入A线程的runloop。当创建B线程辅助线程
时,将创建的NSMachPort对象传递到主体入口点,B线程辅助线程
就可以使用相同的端口对象将消息传回A线程父线程
。)
23.GCD内部怎么实现的?
1.iOS和OS X的核心是XNU内核,GCD是基于XNU内核实现的;
2.GCD的API全部在libdispatch库中;
3.GCD的底层实现主要有Dispatch Queue管理block(操作)
和Dispatch Source处理事件
(MACH端口发送,MACH端口接收,检测与进程相关事件等10种事件)。
24.NSBlockOperation和NSInvocationOperation用法的区别?
NSBlockOperation执行代码块,NSInvocationOperation执行指定的方法,相对来说后者更加灵活易用。
25.UIKit类要在哪一个应用线程上使用?
UIKit的界面类只能在主线程上使用,对界面进行更新,多线程环境中要对界面进行更新必须要切换到主线程上。
26.GCD中栅栏机制?
栅栏函数,只能用在调度并发队列中,不能使用在全局并发队列中。
1、实现高效率的数据库访问和文件访问。
2、避免数据竞争。
dispatch_barrier_async函数会等待追加到并行队列上的并行执行的处理
全部结束之后,再将制定的处理追加到该并行队列中。然后再由dispatch_barrier_async函数追加的处理执行完毕后,并行队列才恢复为一般的动作,追加到该并行队列的处理又开始执行。
27.线程共享进程资源的实例?
火车票抢票案例。
28.unix上进程怎么通信?
UNIX主要支持三种通信方式:
1.
基本通信:主要用来协调进程间的同步和互斥;(1.锁文件通信
:通信的双方通过查找特定目录下特定类型的文件(称锁文件
)来完成进程间,对临界资源访问时的互斥;例如进程p1访问一个临界资源,首先查看是否有一个特定类型文件,若有,则等待一段时间再查找锁文件。2.记录锁文件
)
2.
管道通信:适应大批量的数据传递;
3.
IPC:适应大批量的数据传递;
29.列举几种进程的同步机制、进程的通信途径、死锁及死锁的处理方法。
进程的同步机制:原子操作、信号量机制、自旋锁、管程、会合、分布式系统(具体解释可参照:https://blog.csdn.net/opah521/article/details/6546410)
进程之间通信的途径:共享存储、系统消息传递系统、管道(以文件系统为基础)
进程死锁的原因:资源竞争及进程推进顺序非法(进程在运行过程中,请求和释放资源的顺序不当
)
死锁的4个必要条件:互斥、请求保持、不可剥夺、环路;
死锁的处理:鸵鸟策略、预防策略、避免策略、检测与解除死锁;
30.什么是线程同步,如何实现的?子线程回到主线程的方法是什么,有什么作用?
线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,实现线程同步的方法有很多,临界区对象就是其中一种。
回到主线程的方法:dispatch_async(dispatch_get_main_queue(), ^{});
作用:刷新UI界面。
31.使用atomic一定是线程安全的吗?
不一定是线程安全的;atomic和nonatomic修饰的OC对象,系统都会自动生成setter/getter方法,区别在于atomic会进行加锁操作,nonatomic不会。系统默认使用atomic(在多线程环境下,解析的访问器提供一个对属性的安全访问,从获取器得到的返回值或者通过设置器设置的值可以一次完成,即便是别的线程也正在对其进行访问。)。
atomic的本意是指属性的存取方法
是线程安全的(thread safe),并不保证整个对象是线程安全的。
A、B、C等多个线程都要操作同一个对象setter,D线程要getter这个对象的值,那么每个线程都成保证各自数据的完整性,但是D线程最后get到的值并不能确定。
所以atomic能够保证数据的完成性,也就是说他只是读写安全,并不能准确定义说他是线程安全的。因为线程可以对数据做很多操作,包括读写,还有release、retain,假如说对一个已经释放的对象进行release,就会导致crash。
32.OC中异步使用的哪种事件模型,iOS中异步实现机制?
异步非阻塞I/O(AIO)模型。不是很确定,自我理解为,当需要异步调用时,开启一个异步线程,在这个子线程中进行操作;
33.GCD的queue、main queue中执行的代码一定是在main thread么?
不一定。对于queue中所执行的代码,不一定在main thread中;如果queue是在主线程中创建的,那么在主线程中执行;如果在子线程中创建,那么不在main thread中执行。对于main queue就是在主线程中的,因此一定会在主线程中执行。
34.有a、b、c、d 4个异步请求,如何判断a、b、c、d都完成执行?如果需要a、b、c、d顺序执行,该如何实现?
活学活用,dispatch_group_async判断4个同步请求都执行完成,然后执行dispatch_group_notify来回调;或者加锁、信号量、栅栏。顺序执行,NSOperation通过添加依赖关系、dispatch_barrier_async栅栏、加锁、信号量、串行队列等。
35.如何实现dispatch_once?
参考:http://www.dreamingwish.com/article/gcd-guide-dispatch-once-2.html
dispatch_once的主要处理的情况如下:(通过使用信号量来实现)
1.线程A执行Block时,任何其它线程都需要等待。
2.线程A执行完Block应该立即标记任务完成状态,然后遍历信号量链来唤醒所有等待线程。
3.线程A遍历信号量链来signal时,任何其他新进入函数的线程都应该直接返回而无需等待。
4.线程A遍历信号量链来signal时,若有其它等待线程B仍在更新或试图更新信号量链,应该保证此线程B能正确完成其任务:a.直接返回 b.等待在信号量上并很快又被唤醒。
5.线程B构造信号量时,应该考虑线程A随时可能改变状态(“等待”、“完成”、“遍历信号量链”)。
6.线程B构造信号量时,应该考虑到另一个线程C也可能正在更新或试图更新信号量链,应该保证B、C都能正常完成其任务:a.增加链节并等待在信号量上 b.发现线程A已经标记“完成”然后直接销毁信号量并退出函数。
总结:无锁的线程同步编程非常精巧,为了提升效率,每一处线程竞争都必须被考虑到并妥善处理。
36.关于NSOperation
- NSOperation:抽象类,不能直接使用,需要使用其子类(类似的类还有核心动画)。
- 两个常用子类:NSInvocationOperation(调用方法)和NSBlockOperation(代码块)。
- 两者没有本质区别,后者使用Block的形式组织代码,使用相对方便。
- NSInvocationOperation在调用start方法后,不会开启新的线程只会在当前线程中执行。
- NSBlockOperation在调用start方法后,如果封装的操作数>1会开辟多条线程执行,=1只会在当前线程执行。
- NSOperationQueue创建的操作队列默认为全局队列,队列中的操作执行顺序是无序的,如果需要让它有序执行需要添加依赖关系。
// 操作op3依赖于操作op2
[op3 addDependency:op2];
// 操作op2依赖于操作op1
[op2 addDependency:op1];
- 同时可以设置最大并发数。
- NSOperationQueue、NSOperation支持取消、暂停的操作,但是正在进行的操作并不能取消,这一旦取消不可恢复。
- NSOperationQueue支持KVO,可以监测Operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCancelled)。
37.NSOperationQueue是队列吗?
存放NSOperation的集合类,不能说是队列,因为不是严格的先进先出。
38.为什么要取消/恢复队列呢?
1.一般在内存警告后取消队列中的操作;2.为了保证scrollView在滚动的时候流程,通常在滚动开始时,暂停队列中的所有操作,滚动结束后,恢复操作。
39.下面关于线程管理错误的是?
A.GCD所用的开销要比NSThread大
B.可以在子线程中修改UI元素
C.NSOperationQueue是比NSThread更高层的封装
D.GCD可以根据不同优先级分配线程
- 参考答案:B
- 解析:首先,UI元素的更新必须在主线程。GCD与Block配合使用,block需要自动捕获上下文变量信息等,因此需要更多的资源,故比NSThread开销要大一些。NSOperationQueue与NSOperation配合使用,比NSThread更易于操作线程。GCD提供了多个优先级,我们可以根据设置优先级,让其自动为我们分配线程。