iOS多线程之GCD

作者: Rxiaobing | 来源:发表于2017-11-10 15:31 被阅读644次

    多线程相关概念

    进程与线程

    进程概念: 进程是程序在计算机上的一次执行活动,打开一个app,就开启了一个进程,可包含多个线程。
    线程概念: 独立执行的代码段,一个线程同时间只能执行一个任务,反之多线程并发就可以在同一时间执行多个任务。
    iOS程序中,主线程(又叫作UI线程)主要任务是处理UI事件,显示和刷新UI,(只有主线程有直接修改UI的能力)耗时的操作放在子线程(又叫作后台线程、异步线程)。在iOS中开子线程去处理耗时的操作,可以有效提高程序的执行效率,提高资源利用率。但是开启线程会占用一定的内存,(主线程的堆栈大小是1M,第二个线程开始都是512KB,并且该值不能通过编译器开关或线程API函数来更改)降低程序的性能。所以一般不要同时开很多线程。

    线程相关

    同步线程:同步线程会阻塞当前线程去执行线程内的任务,执行完之后才会反回当前线程。
    异步线程:异步线程不会阻塞当前线程,会开启其他线程去执行线程内的任务。
    串行队列:线程任务按先后顺序逐个执行(需要等待队列里面前面的任务执行完之后再执行新的任务)。
    并发队列:多个任务按添加顺序一起开始执行(不用等待前面的任务执行完再执行新的任务),但是添加间隔往往忽略不计,所以看着像是一起执行的。
    并发VS并行:并行是基于多核设备的,并行一定是并发,并发不一定是并行。

    多线程中会出现的问题

    Critical Section(临界代码段)
    指的是不能同时被两个线程访问的代码段,比如一个变量,被并发进程访问后可能会改变变量值,造成数据污染(数据共享问题)。
    Race Condition (竞态条件)
    当多个线程同时访问共享的数据时,会发生争用情形,第一个线程读取改变了一个变量的值,第二个线程也读取改变了这个变量的值,两个线程同时操作了该变量,此时他们会发生竞争来看哪个线程会最后写入这个变量,最后被写入的值将会被保留下来。
    Deadlock (死锁)
    两个(多个)线程都要等待对方完成某个操作才能进行下一步,这时就会发生死锁。
    Thread Safe(线程安全)
    一段线程安全的代码(对象),可以同时被多个线程或并发的任务调度,不会产生问题,非线程安全的只能按次序被访问。
    所有Mutable对象都是非线程安全的,所有Immutable对象都是线程安全的,使用Mutable对象,一定要用同步锁来同步访问(@synchronized)。
    互斥锁:能够防止多线程抢夺造成的数据安全问题,但是需要消耗大量的资源
    原子属性(atomic)加锁
    atomic: 原子属性,为setter方法加锁,将属性以atomic的形式来声明,该属性变量就能支持互斥锁了。
    nonatomic: 非原子属性,不会为setter方法加锁,声明为该属性的变量,客户端应尽量避免多线程争夺同一资源。
    Context Switch (上下文切换)
    当一个进程中有多个线程来回切换时,context switch用来记录执行状态,这样的进程和一般的多线程进程没有太大差别,但会产生一些额外的开销。

    多线程编程技术的优缺点比较

    NSThread (抽象层次:低)
    优点:轻量级,简单易用,可以直接操作线程对象
    缺点: 需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销。
    Cocoa NSOperation (抽象层次:中)
    优点:不需要关心线程管理,数据同步的事情,可以把精力放在学要执行的操作上。基于GCD,是对GCD 的封装,比GCD更加面向对象
    缺点: NSOperation是个抽象类,使用它必须使用它的子类,可以实现它或者使用它定义好的两个子类NSInvocationOperation、NSBlockOperation.
    GCD 全称Grand Center Dispatch (抽象层次:高)

    优点:是 Apple 开发的一个多核编程的解决方法,简单易用,效率高,速度快,基于C语言,更底层更高效,并且不是Cocoa框架的一部分,自动管理线程生命周期(创建线程、调度任务、销毁线程)。
    缺点: 使用GCD的场景如果很复杂,就有非常大的可能遇到死锁问题。

    GCD抽象层次最高,使用也简单,因此,苹果也推荐使用GCD

    GCD中的队列类型

    GCD编程的核心就是dispatch队列,dispatch block的执行最终都会放进某个队列中去进行。

    The main queue(主线程串行队列): 与主线程功能相同,提交至Main queue的任务会在主线程中执行,
    Main queue 可以通过dispatch_get_main_queue()来获取。
    Global queue(全局并发队列): 全局并发队列由整个进程共享,有高、中(默认)、低、后台四个优先级别。
    Global queue 可以通过调用dispatch_get_global_queue函数来获取(可以设置优先级)
    Custom queue (自定义队列): 可以为串行,也可以为并发。
    Custom queue 可以通过dispatch_queue_create()来获取;
    Group queue (队列组):将多线程进行分组,最大的好处是可获知所有线程的完成情况。
    Group queue 可以通过调用dispatch_group_create()来获取,通过dispatch_group_notify,可以直接监听组里所有线程完成情况。

    重点来了哈

    GCD在项目中的实际应用

    GCD在项目中的实际应用

    GCD在项目中的实际应用(一定要说够三遍!!!)

    以下请求以afn为例

    场景一

    个页面多个网络请求,并且多个请求同时执行,都执行完成以后进行数据操作
    实现方式1:dipatch_group

    AFHTTPSessionManager *MANAGER = [AFHTTPSessionManager manager];
        dispatch_group_enter(self.group);
        [MANAGER POST:@"" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nonnull responseObject) {
            dispatch_group_leave(self.group);
        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            dispatch_group_leave(self.group);
        }];
    
        dispatch_group_enter(self.group);
        [MANAGER POST:@"" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nonnull responseObject) {
            dispatch_group_leave(self.group);
        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            dispatch_group_leave(self.group);
        }];
        dispatch_group_notify(self.group, self.chquue, ^{
            NSLog(@"11111");
        });
    

    其他类似代码实现相同效果

    dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t conQueue = dispatch_queue_create("fsfsdfsf2", DISPATCH_QUEUE_CONCURRENT);
        dispatch_queue_t seQueue = dispatch_queue_create("asdsadasdasda", NULL);
        dispatch_queue_t seQueue1 = dispatch_queue_create("asdsadasasdadassda", NULL);
    //
    //
        
        dispatch_group_enter(group);
        dispatch_async(conQueue, ^{
            sleep(6);
            NSLog(@"111111");
            dispatch_group_leave(group);
        });
        
        dispatch_group_enter(group);
        dispatch_async(seQueue, ^{
            sleep(2);
            NSLog(@"222222");
            dispatch_group_leave(group);
        });
        dispatch_group_enter(group);
        dispatch_async(seQueue1, ^{
            sleep(4);
            NSLog(@"33333");
            dispatch_group_leave(group);
        });
    //
        dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
            NSLog(@"444444");
        });
    //
        NSLog(@"hhahahaha");
    
    其中的队列可以是不同的队列,只要group是同一个,都会得到最后执行notify代码的效果,如果是同一个串行队列,则会按照1-2-3-4的顺序串行执行以上4个任务

    实现方式2:dispatch_semaphore

    dispatch_async(self.chquue, ^{
            dispatch_semaphore_t SEM = dispatch_semaphore_create(0);
            AFHTTPSessionManager *MANAGER = [AFHTTPSessionManager manager];
            
            [MANAGER POST:@"" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nonnull responseObject) {
                dispatch_semaphore_signal(SEM);
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                dispatch_semaphore_signal(SEM);
            }];
            
            dispatch_semaphore_t SEM2 = dispatch_semaphore_create(0);
            [MANAGER POST:@"" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nonnull responseObject) {
                dispatch_semaphore_signal(SEM2);
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                dispatch_semaphore_signal(SEM2);
            }];
            dispatch_semaphore_wait(SEM, DISPATCH_TIME_FOREVER);
            dispatch_semaphore_wait(SEM2, DISPATCH_TIME_FOREVER);
            
            NSLog(@"hahaha");
        });
        NSLog(@"hello");
    
    此处为什么要套一个dispatch_ASYNC呢?这样做是为了防止阻塞主线程的任务执行!
    场景二

    一个页面多个网络请求,并且多个请求串执行,下一个请求需要等待上一个的请求结果才能继续请求
    实现方式1:dipatch_group,如何使用group的方式来实现这个需求,欢迎大神提出意见
    实现方式2:dispatch_semaphore

    dispatch_async(self.chquue, ^{
            dispatch_semaphore_t SEM = dispatch_semaphore_create(0);
            AFHTTPSessionManager *MANAGER = [AFHTTPSessionManager manager];
            
            [MANAGER POST:@"" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nonnull responseObject) {
                dispatch_semaphore_signal(SEM);
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                dispatch_semaphore_signal(SEM);
            }];
            
           dispatch_semaphore_wait(SEM, DISPATCH_TIME_FOREVER);
            [MANAGER POST:@"" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nonnull responseObject) {
                dispatch_semaphore_signal(SEM);
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                dispatch_semaphore_signal(SEM);
            }];
            dispatch_semaphore_wait(SEM, DISPATCH_TIME_FOREVER);
            
            NSLog(@"hahaha");
        });
        NSLog(@"hello");
    
    这段代码的关键点是只使用一个sem来实现串行执行异步任务

    当然如果哪位大神有更好的实现方式欢迎指点!!

    场景三

    死锁

    dispatch_queue_t serialQueue = dispatch_queue_create("com.dullgrass.serialQueue", DISPATCH_QUEUE_SERIAL);
      dispatch_sync(serialQueue, ^{
          NSLog(@"会执行的代码");
          //当嵌套的这个任务是同步且在串行队列中执行时,就会造成死锁
          dispatch_sync(serialQueue, ^{
              NSLog(@"代码不执行");
          });
      });
    

    关于dispatc_group

    dispatch_enter和dispatch_leave要成对出现,否则奔溃。

    补充

    关于dispatch_set_target_queue
    场景一

    使用dispatch_set_target_queue更改Dispatch Queue的执行优先级
    dispatch_queue_create函数生成的DisPatch Queue不管是Serial DisPatch Queue还是Concurrent Dispatch Queue,执行的优先级都与默认优先级的Global Dispatch queue相同,如果需要变更生成的Dispatch Queue的执行优先级则需要使用dispatch_set_target_queue函数

    - (void)testTeagerQueue1 {
    2     dispatch_queue_t serialQueue = dispatch_queue_create("com.oukavip.www",NULL);
    3     dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
    4     
    5     dispatch_set_target_queue(serialQueue, globalQueue);
    6     // 第一个参数为要设置优先级的queue,第二个参数是参照物,既将第一个queue的优先级和第二个queue的优先级设置一样。
    7 }
    
    场景二

    使用dispatch_set_target_queue将多个串行的queue指定到了同一目标,那么着多个串行queue在目标queue上就是同步执行的,不再是并行执行。

    - (void)testTargetQueue {
      //1.创建目标队列
      dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL);
      
      //2.创建3个串行队列
      dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
      dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);
      dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL);
      
      //3.将3个串行队列分别添加到目标队列
      dispatch_set_target_queue(queue1, targetQueue);
      dispatch_set_target_queue(queue2, targetQueue);
      dispatch_set_target_queue(queue3, targetQueue);
      
      
      dispatch_async(queue1, ^{
          NSLog(@"1 in");
          [NSThread sleepForTimeInterval:3.f];
          NSLog(@"1 out");
      });
      
      dispatch_async(queue2, ^{
          NSLog(@"2 in");
          [NSThread sleepForTimeInterval:2.f];
          NSLog(@"2 out");
      });
      dispatch_async(queue3, ^{
          NSLog(@"3 in");
          [NSThread sleepForTimeInterval:1.f];
          NSLog(@"3 out");
      });
    }
    

    以上是我在工作中遇到的关于gcd的一些总结,欢迎各路大神补充,共同进步!!如果您看过文章觉得用得上,请给个赞谢谢!

    相关文章

      网友评论

        本文标题:iOS多线程之GCD

        本文链接:https://www.haomeiwen.com/subject/jjybmxtx.html