iOS 面试第九节 多线程

在这里插入图片描述
补充:
同步、全局对列 ABC 未开启子线程 任务同步有序
异步、全局对列 ABC 新开多个子线程 任务乱序执行(任务少有序,太多时也有可能会出现任务执行乱序。。。)

1.进程与线程

  • 进程
    1.进程是一个具有一定独立功能的程序关于某次数据集合的一次运行活动,它是操作系统分配资源的基本单元.
    2.进程是指在系统中正在运行的一个应用程序,就是一段程序的执行过程,我们可以理解为手机上的一个app.
    3.每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内,拥有独立运行所需的全部资源

  • 线程
    1.程序执行流的最小单元,线程是进程中的一个实体.
    2.一个进程要想执行任务,必须至少有一条线程.应用程序启动的时候,系统会默认开启一条线程,也就是主线程

  • 进程和线程的关系
    1.线程是进程的执行单元,进程的所有任务都在线程中执行
    2.线程是 CPU 分配资源和调度的最小单位
    3.一个进程中可有多个线程,但至少要有一条线程
    4.同一个进程内的线程共享进程资源

2.什么是多线程?

  • 多线程的实现原理:事实上,同一时间内单核的CPU只能执行一个线程,多线程是CPU快速的在多个线程之间进行切换(调度),造成了多个线程同时执行的假象。
  • 如果是多核CPU就真的可以同时处理多个线程了。
  • 多线程的目的是为了同步完成多项任务,通过提高系统的资源利用率来提高系统的效率。

3.多线程的优点和缺点

  • 优点
    能适当提高程序的执行效率
    能适当提高资源利用率(CPU、内存利用率)
  • 缺点:
  • 多线程编程实际上是一种易发生各种问题的编程技术。比如多个线程更新相同资源会导致数据的不一致(数据竞争)、停止等待事件的线程会导致多个线程相互等待(死锁)、使用太多线程会消耗大量内存等
    开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
    线程越多,CPU在调度线程上的开销就越大
    程序设计更加复杂:比如线程之间的通信、多线程的数据共享
    在这里插入图片描述

4.同步(Synchronous) 和 异步(Asynchronous)

同步和异步描述的其实就是函数什么时候返回. 比如用来下载图片的函数A: {download image}, 同步函数只有在image下载结束之后才返回, 下载的这段时间函数A只能搬个小板凳在那儿坐等… 而异步函数, 立即返回. 图片会去下载, 但函数A不会去等它完成. So, 异步函数不会堵塞当前线程去执行下一个函数!

5.串行(Serial)和 并行(Parallelism)

串行和并行描述的是任务和任务之间的执行方式. 串行是任务A执行完了任务B才能执行, 它们俩只能顺序执行. 并行则是任务A和任务B可以同时执行.

6.并行(Parallelism) 和 并发(Concurrency) 有什么区别?

并行就是同一时间做多个事
并发就是一个时间段内做多个事情。
所以并行的话,是需要多核cpu多个线程同时执行,如果单核cpu就只有并发了。

  • 并行:充分利用计算机的多核,在多个线程上同步进行
  • 并发:在一条线程上通过快速切换,让人感觉在同步进行

7.线程从开始到结束的状态

  • 新建状态

    • 通过上面3中方法实 例化线程对象
    • 程序还没有开始运行线程中的代码
  • 就绪状态
    oc的NSThread对于的几种开线程方式

    • 向线程对象发送 start 消息,线程对象被加入 可调度线程池 等待 CPU 调度
    • detachNewThreadSelector 方法
      detachNewThreadWithBlock和
      performSelectorInBackground 方法
      会直接实例化一个线程对象并加入 可调度线程池
    • 处于就绪状态的线程并不一定立即执行线程里的代码,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。
  • 运行状态

    • CPU 负责调度可调度线程池中线程的执行
    • 线程执行完成之前(死亡之前),状态可能会在就绪和运行之间来回切换
    • 就绪和运行之间的状态变化由 CPU 负责,程序员不能干预
  • 阻塞状态

    • 所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。
    • 线程通过调用sleep方法进入睡眠状态
    • 线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者
    • 线程试图得到一个锁,而该锁正被其他线程持有;
    • 线程在等待某个触发条件
  + (void)sleepUntilDate:(NSDate *)date;//休眠到指定日期
  + (void)sleepForTimeInterval:(NSTimeInterval)ti;//休眠指定时长
  @synchronized(self):互斥锁
  sleep(unsigned int) __DARWIN_ALIAS_C(sleep);
  • 死亡状态
    • 正常死亡
      • 线程执行完毕
    • 非正常死亡
      • 当满足某个条件后,在线程内部自己中止执行(自杀)
      • 当满足某个条件后,在主线程给其它线程打个死亡标记(下圣旨),让子线程自行了断.(被逼着死亡)

在这里插入图片描述
上面就是线程生命周期
当线程处于就绪状态时线程会被移到可调度线程池里面(CPU只调度此线程池里面的线程),当处于阻塞状态时,线程会被移出可调度线程池,当处于死亡状态时 先移出线程池,再从内存中释放。

线程通信
线程在运行过程中,可能需要与其它线程进行通信,如在主线程中修改界面等等,可以使用如下接口:

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait

线程的同步与锁
关于锁这一块,需要单独列一块,上锁方式很多种,性能较优的有 dispatch_semaphore_t (信号量上锁)

线程的同步与锁什么时候会遇到?就是我们在线程公用资源的时候,导致的资源竞争。举个例子:多个窗口同时售票的售票系统!

- (void)viewDidLoad {
    [super viewDidLoad];
    
    tickets = 100;
    count = 0;
    
    //锁对象
    self.ticketsLock = [[NSLock alloc] init];

    self.ticketsThreadOne = [[NSThread alloc] initWithTarget:self selector:@selector(sellAction) object:nil];
    self.ticketsThreadOne.name = @"thread-1";
    [self.ticketsThreadOne start];
    
    self.ticketsThreadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(sellAction) object:nil];
    self.ticketsThreadTwo.name = @"thread-2";
    [self.ticketsThreadTwo start];
    
}

- (void)sellAction{
    while (true) {
        //上锁
        [self.ticketsLock lock];
        if (tickets > 0) {
            [NSThread sleepForTimeInterval:0.5];
            count = 100 - tickets;
            NSLog(@"当前总票数是:%ld----->卖出:%ld----->线程名:%@",tickets,count,[NSThread currentThread]);
            tickets--;
        }else{
            break;
        }
        //解锁
        [self.ticketsLock unlock];
    }
}

@end

通过上面的Demo应该理解线程的同步以及锁的使用问题

[myLock lock]

资源处理....

[myLock unLock];

8.iOS中实现多线程的几种方案,各自有什么特点?

  • NSThread 面向对象的,需要程序员手动创建线程,但不需要手动销毁。子线程间通信很难。
- (void)testThread {
    
//    [NSThread detachNewThreadSelector:<#(nonnull SEL)#> toTarget:<#(nonnull id)#> withObject:<#(nullable id)#>];
    NSLog(@"A:%@",[NSThread currentThread]);
    
    //异步、同步 的区别就是函数什么时候返回,异步直接返回不影响继续执行下面代码,同步是等待子线程执行结束再返回然后再执行下面代码
    
    //NSThread 的对象方法开线程  异步 ACB
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadDownloadImage) object:nil];
    [thread start];// 需要手动开启
    
    //使用NSThread 类方法开启子线程 异步
//    [NSThread detachNewThreadWithBlock:^{
//        [self threadDownloadImage];
//    }];
    
    //使用NSThread 类方法开子线程 异步
//    [NSThread detachNewThreadSelector:@selector(threadDownloadImage) toTarget:self withObject:nil];
    
    //使用NSObject的分类方式开子线程 异步
//    [self performSelectorInBackground:@selector(threadDownloadImage) withObject:nil];
    
    //使用NSObject的分类方式开子线程 同步,也就是打印 ABC
//    [self performSelector:@selector(threadDownloadImage)];
    
    NSLog(@"C:%@",[NSThread currentThread]);
}

- (void)threadDownloadImage {
    sleep(5);
    NSLog(@"B:%@",[NSThread currentThread]);
    NSLog(@"下载大量图片数据");
}
  • GCD c语言,充分利用了设备的多核,自动管理线程生命周期。比NSOperation效率更高。

    GCD(Grand Central Dispatch)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。

任务和队列

  • 任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在GCD中是放在block中的。执行任务有两种方式:同步执行和异步执行。两者的主要区别是:是否具备开启新线程的能力。

    • 同步执行(sync):只能在当前线程中执行任务,不具备开启新线程的能力
      • 必须等待当前语句执行完毕,才会执行下一条语句
      • 不会开启线程
      • 在当前主线程执行 block 的任务
      • dispatch_sync(queue, block);
    • 异步执行(async):可以在新的线程中执行任务,具备开启新线程的能力
      • 不用等待当前语句执行完毕,就可以执行下一条语句
      • 会开启线程执行 block 的任务
      • 异步是多线程的代名词
      • dispatch_async(queue, block);
  • 队列:这里的队列指任务队列,即用来存放任务的队列。队列是一种特殊的线性表,采用FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。在GCD中有四种队列:串行队列、并发队列、主队列、全局队列。

    1. 串行队列(Serial Dispatch Queue):让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

      • 一次只能"调度"一个任务
      • dispatch_queue_create("queue", NULL);或者或者dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    2. 并发队列(Concurrent Dispatch Queue):可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务),

      • 一次可以"调度"多个任务
      • 并发功能只有在异步(dispatch_async)函数下才有效
      • dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    3. 主队列:

      • 专门用来在主线程上调度任务的队列
      • 不会开启线程
      • 在主线程空闲时才会调度队列中的任务在主线程执行
      • dispatch_get_main_queue();
    4. 全局队列:

      • 执行过程和并发队列一致,参考并发队列
      • dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

小结: 在以后的使用中,记住下面的就可以了!

1.开不开线程由执行任务的函数决定

  • 异步开,异步是多线程的代名词
  • 同步不开

2.开几条线程由队列决定

  • 串行队列开一条线程(GCD会开一条,NSOperation Queue最大并发数为1时也可能开多条)
  • 并发队列开多条线程,具体能开的线程数量由底层线程池决定

下面代码实践

//前提 testThread 主线程调用的
- (void)testThread {
    NSLog(@"A:%@",[NSThread currentThread]);
//    [self threadMainUse]; //主线程开启的
    [self gcdMainUse]; //主线程中开启的
    NSLog(@"C:%@",[NSThread currentThread]);
}

//模拟网络下载
- (void)threadDownloadImage {
    sleep(5);
    NSLog(@"B 下载大量图片数据:%@",[NSThread currentThread]);
}

//GCD 试验田
- (void)gcdMainUse {
    //再次重申 异步 同步 跟开不开线程没有关系,只是让你当前代码要不要立刻跳过去往下继续执行
    
    
    //串行队列之探索  SERIAL
//    dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
//    dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", NULL);

    //同步 串行
    // 同步串行 ABC 未开子线程  任务有序执行 for模拟downImage多任务是否顺序执行(按次序执行)
//    for (int i = 0; i < 6; i++) {
//        dispatch_sync(serialQueue, ^{
//            NSLog(@"b_%d",i);
//            [self threadDownloadImage];
//        });
//    }

    
    // 异步 串行
    // 异步、串行  ACB  新开多个子线程  任务乱序执行(任务少有序,太多时也有可能会出现任务执行乱序。。。)
//    for (int i = 0; i < 6; i++) {
//        dispatch_async(serialQueue, ^{
//            NSLog(@"b_%d",i);
//            [self threadDownloadImage];
//        });
//    }

    
    
    //并发队列之探索  Concurrent
//    dispatch_queue_t concurrentQueue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    
    // 同步、并发  ABC  未开子线程  任务有序执行
//    for (int i = 0; i < 6; i++) {
//        dispatch_sync(concurrentQueue, ^{
//            NSLog(@"b_%d",i);
//            [self threadDownloadImage];
//        });
//    }
    
    //异步、并发  ACB b_0 b_2 b_5 b_4   新开多个子线程 任务随机执行 (任务是没有顺序随机执行)
//    for (int i = 0; i < 6; i++) {
//        dispatch_async(concurrentQueue, ^{
//            NSLog(@"b_%d",i);
//            [self threadDownloadImage];
//        });
//    }
    
    
    //全局队列之探索  global_queue
//    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //同步、全局对列 ABC 未开启子线程 任务同步有序
//    for (int i = 0; i < 6; i++) {
//        dispatch_sync(globalQueue, ^{
//            NSLog(@"b_%d",i);
//            [self threadDownloadImage];
//        });
//    }
    
    //异步、全局对列 ABC 新开多个子线程 任务乱序执行(任务少有序,太多时也有可能会出现任务执行乱序。。。)
//    for (int i = 0; i < 6; i++) {
//        dispatch_async(globalQueue, ^{
//            NSLog(@"b_%d",i);
//            [self threadDownloadImage];
//        });
//    }
    
    //主队列队列之探索  main_queue
//    dispatch_queue_t mainQueue = dispatch_get_main_queue();
  
    //同步主队列 死锁 (原因:源代码在Main Dispatch Queue 即主队列中执行指定的block任务,并等待其结束。而其实在主线程中正在执行这些源代码,所以无法执行追加到Main Dispatch Queue 的block任务。 解释一下就是:gcd代码同步执行, 会阻塞当前线程, 也就是任务B执行完成以后才会执行任务C. 主队列是串行队列, 当一个任务执行完成以后才会执行另一个任务, 也就是任务C执行完成以后才会执行任务B. 所以任务B和任务C就会一直相互等待, 形成死锁.)
//    for (int i = 0; i < 6; i++) {
//        dispatch_sync(mainQueue, ^{
//            NSLog(@"b_%d",i);
//            [self threadDownloadImage];
//        });
//    }
    
    //异步主队列 ACB 未开启子线程  任务有序执行
//    for (int i = 0; i < 6; i++) {
//        dispatch_async(mainQueue, ^{
//            NSLog(@"b_%d",i);
//            [self threadDownloadImage];
//        });
//    }

    // 延迟函数
    //  延时函数并非是在指定的时间后开始执行任务block, 而是在指定的时间后将任务block异步添加到指定的队列中, 然后再等待从队列中取出任务block放入线程中去执行, 如果block分配的线程被sleep了一段时间, 那么这个延迟函数就不太准确了, 所以延迟函数dispatch_after存在一定的时间误差.

//    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
//    dispatch_after(time, dispatch_get_main_queue(), ^{
//        NSLog(@"B");
//    });


    
    // 栅栏函数。哈哈哈哈哈 顾名思义,就是个栅栏隔离 ,比如任务ABC执行完后执行F
    //执行顺序 ACB(F前面的执行顺序)FD
    //白话翻译:当栅栏函数中的任务block到达并发队列queue的最前边时, 任务block并不会立即执行, 它会等待并发队列中正在执行的任务执行完成以后才执行栅栏函数的任务. 任何在栅栏函数以后添加到并发队列中的任务都会等待栅栏函数中的任务执行完成以后才执行.

//    dispatch_queue_t conQueue = dispatch_queue_create("conQueue", DISPATCH_QUEUE_CONCURRENT);
//    
//    dispatch_async(conQueue, ^{
//        NSLog(@"任务A");
//    });
//    
//    dispatch_async(conQueue, ^{
//        NSLog(@"任务B");
//    });
//    
//    dispatch_async(conQueue, ^{
//        NSLog(@"任务C");
//    });
//    
//    dispatch_barrier_async(conQueue, ^{
//        NSLog(@"任务F");
//    });
//    
//    dispatch_async(conQueue, ^{
//        NSLog(@"任务D");
//    });

    //队列组。 任务场景在n个耗时并发任务都完成后,再去执行接下来的任务。比如,在n个网络请求完成后去刷新UI页面。
    //流程是 先创建组,把队列加入组中,组通知任务完成后回主线程刷新
//    dispatch_queue_t concurrentQueue = dispatch_queue_create("test1", DISPATCH_QUEUE_CONCURRENT);
//    dispatch_group_t group = dispatch_group_create();
//
//    for (NSInteger i = 0; i < 10; i++) {
//        dispatch_group_async(group, concurrentQueue, ^{
//            sleep(1);
//            NSLog(@"%zd:网络请求",i);
//        });
//    }
//
//    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//        NSLog(@"刷新页面");
//    });
}

同步串行,如果同步线程与串行执行方法线程在同一个线程会造成死锁。
比如在主线程,进行同步主队列会死锁。
在子线程,进行同步串行会死锁。

  • NSOperation 基于gcd封装,更加面向对象,比gcd多了一些功能。
- (void)operationThreadUse {
    //operation 开启线程同 NSThread相似,也是三种方式
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务B1-%@",[NSThread currentThread]);
        }
    }];

    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务B2-%@",[NSThread currentThread]);
        }
    }];
    
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务3-%@",[NSThread currentThread]);
        }
    }];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//    //设置任务依赖,意思是任务2在任务3后面执行,所以也就是任务B1 B3无序先执行
//    [operation2 addDependency:operation3];
//    [queue addOperation:operation1];
//    [queue addOperation:operation2];
//    [queue addOperation:operation3];

    //线程间通讯
    [queue addOperationWithBlock:^{
        NSLog(@"在子线程中执行--任务1--%@", [NSThread currentThread]);
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                NSLog(@"在主线程中执行--任务2--%@", [NSThread currentThread]);
        }];
    }];
    
//    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operation1Selector) object:nil];
//    NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operation2Selector) object:nil];

        // 取消某一个操作
    //    [operation1 cancel];
        
        // 暂停队列
//        [queue setSuspended:YES];

        // 取消队列中的操作
    //    [queue cancelAllOperations];
//        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//            [queue setSuspended:NO];
//        });
        
//        [queue addOperation:operation1];
//        [queue addOperation:operation2];

    
    
    //A B1 B2 C 新开子线程产生 有序执行
//    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//    queue.maxConcurrentOperationCount = 1;
//    [queue addOperation:operation1];
//    [queue addOperation:operation2];
    
    
    //A B1 B2 C 新开子线程产生 有序执行
//    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//    [queue addOperationWithBlock:^{
//        for (int i = 0; i < 3; i++) {
//            NSLog(@"任务1-%@",[NSThread currentThread]);
//        }
//    }];
//
//    [queue addOperationWithBlock:^{
//        for (int i = 0; i < 3; i++) {
//            NSLog(@"任务2-%@",[NSThread currentThread]);
//        }
//    }];
}

iOS多线程方案
iOS开发之多线程编程总结

9.多个网络请求完成后一起执行下一步

  • 使用GCD的dispatch_group_t

创建一个dispatch_group_t
每次网络请求前先dispatch_group_enter,请求回调后再dispatch_group_leave,enter和leave必须配合使用,有几次enter就要有几次leave,否则group会一直存在。
当所有enter的block都leave后,会执行dispatch_group_notify的block。

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");
});
  • 使用GCD的信号量dispatch_semaphore_t
    dispatch_semaphore信号量为基于计数器的一种多线程同步机制。如果semaphore计数大于等于1,计数-1,返回,程序继续运行。如果计数为0,则等待。dispatch_semaphore_signal(semaphore)为计数+1操作,dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)为设置等待时间,这里设置的等待时间是一直等待。

创建semaphore为0,等待,等10个网络请求都完成了,dispatch_semaphore_signal(semaphore)为计数+1,然后计数-1返回

NSString *str = @"http://xxxx.com/";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];

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");
});

10.多个网络请求顺序执行后执行下一步

  • 使用信号量semaphore
    每一次遍历,都让其dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER),这个时候线程会等待,阻塞当前线程,直到dispatch_semaphore_signal(sem)调用之后
NSString *str = @"http://www.jianshu.com/p/6930f335adba";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];

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);
}

dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"end");
});

11.异步操作两组数据时, 执行完第一组之后, 才能执行第二组

  • 这里使用dispatch_barrier_async栅栏方法即可实现
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
    NSLog(@"第一次任务的主线程为: %@", [NSThread currentThread]);
});

dispatch_async(queue, ^{
    NSLog(@"第二次任务的主线程为: %@", [NSThread currentThread]);
});

dispatch_barrier_async(queue, ^{
    NSLog(@"第一次任务, 第二次任务执行完毕, 继续执行");
});

dispatch_async(queue, ^{
    NSLog(@"第三次任务的主线程为: %@", [NSThread currentThread]);
});

dispatch_async(queue, ^{
    NSLog(@"第四次任务的主线程为: %@", [NSThread currentThread]);
});
  • 通过队列组 也可以(个人认为)
    -将之前任务1任务2放到group1,任务3任务4放到一组。group1执行完成后执行group2

12.多线程中的死锁?

死锁是由于多个线程(进程)在执行过程中,因为争夺资源而造成的互相等待现象,你可以理解为卡主了。产生死锁的必要条件有四个:

互斥条件 : 指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。

请求和保持条件 : 指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。

不可剥夺条件 : 指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

环路等待条件 : 指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

最常见的就是 同步函数 + 主队列 的组合,本质是队列阻塞。

dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"2");
});
NSLog(@"1");
// 什么也不会打印,直接报错

13.GCD执行原理?

  • GCD有一个底层线程池,这个池中存放的是一个个的线程。之所以称为“池”,很容易理解出这个“池”中的线程是可以重用的,当一段时间后这个线程没有被调用胡话,这个线程就会被销毁。注意:开多少条线程是由底层线程池决定的(线程建议控制再3~5条),池是系统自动来维护,不需要我们程序员来维护(看到这句话是不是很开心?)
    而我们程序员需要关心的是什么呢?我们只关心的是向队列中添加任务,队列调度即可。

  • 如果队列中存放的是同步任务,则任务出队后,底层线程池中会提供一条线程供这个任务执行,任务执行完毕后这条线程再回到线程池。这样队列中的任务反复调度,因为是同步的,所以当我们用currentThread打印的时候,就是同一条线程。

  • 如果队列中存放的是异步的任务,(注意异步可以开线程),当任务出队后,底层线程池会提供一个线程供任务执行,因为是异步执行,队列中的任务不需等待当前任务执行完毕就可以调度下一个任务,这时底层线程池中会再次提供一个线程供第二个任务执行,执行完毕后再回到底层线程池中。

  • 这样就对线程完成一个复用,而不需要每一个任务执行都开启新的线程,也就从而节约的系统的开销,提高了效率。在iOS7.0的时候,使用GCD系统通常只能开58条线程,iOS8.0以后,系统可以开启很多条线程,但是实在开发应用中,建议开启线程条数:35条最为合理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值