美文网首页多线程将来跳槽用iOS技术点
GCD常见用法和实践中需要注意的点

GCD常见用法和实践中需要注意的点

作者: AKyS佐毅 | 来源:发表于2016-12-06 18:34 被阅读1009次

常见问题总结:
1: GCD里面有多少种全局队列?
详情请查看dispatch queue.h 头文件


88AA8E08-1574-49C4-B87F-0E1B0A0A05B7.png 26AEE975-887C-4A41-BC40-D0B9A16B834D.png
  Global Dispatch Queue有如下8种
     Global Dispatch Queue (High priority)
     Global Dispatch Queue (Default priority)
     Global Dispatch Queue (Low priority)
     Global Dispatch Queue (Background priority)
     Global Dispatch Queue (High overcommit priority)
     Global Dispatch Queue (Default overcommit priority)
     Global Dispatch Queue (Low overcommit priority)
     Global Dispatch Queue (Background overcommit priority)
 
   注意前面四种 和后面四种不同优先级的Queue有一词之差:Overcommit。其区别就在于Overcommit Queue不管系统状态如何都会强制生成线程队列。

2: 基本概念阐述

 1:GCD 是什么?
 GCD全称Grand Central Dispatch
 GCD 在后端管理着一个线程池。GCD 不仅决定着你的代码块将在哪个线程被执行,它还根据可用的系统资源对这些线程进行管理。这样可以将开发者从线程管理的工作中解放出来,通过集中的管理线程,来缓解大量线程被创建的问题。
 GCD 带来的另一个重要改变是,作为开发者可以将工作考虑为一个队列,而不是一堆线程,这种并行的抽象模型更容易掌握和使用。当多个队列要处理块时,系统可以自由分配额外的线程来同时调用块。当队列变空时,这些线程会自动释放.
 
 首先,系统提供给你一个叫做 主队列(main queue) 的特殊队列。和其它串行队列一样,这个队列中的任务一次只能执行一个。然而,它能保证所有的任务都在主线程执行,而主线程是唯一可用于更新 UI 的线程。这个队列就是用于发生消息给 UIView 或发送通知的。
 
 系统同时提供给你好几个并发队列。它们叫做 全局调度队列(Global Dispatch Queues) 。目前的四个全局队列有着不同的优先级:background、low、default 以及 high。要知道,Apple 的 API 也会使用这些队列,所以你添加的任何任务都不会是这些队列中唯一的任务。
 
 最后,你也可以创建自己的串行队列或并发队列。
    主队列、
    串行队列、
    并发队列。
 
 2.GCD相比其他多线程有哪些优点?
 
 GCD 能通过推迟昂贵计算任务并在后台运行它们来改善你的应用的响应性能。
 GCD 提供一个易于使用的并发模型而不仅仅只是锁和线程,以帮助我们避开并发陷阱。
 GCD 具有在常见模式(例如单例)上用更高性能的原语优化你的代码的潜在能力。
 GCD 会自动利用更多的CPU内核(比如双核、四核)
 3.GCD术语
 
 串行(Serial):让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
 并发(Concurrent):可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)并发功能只有在异步(dispatch_async)函数下才有效。
 同步(Synchronous):在当前线程中执行任务,不具备开启新线程的能力
 异步(Asynchronous):在新的线程中执行任务,具备开启新线程的能力
#pragma mark - 串行队列
- (void)dispatch_serialQueue{
    //串行队列
    dispatch_queue_t serialQueue;
    serialQueue = dispatch_queue_create("com.example.SerialQueue", DISPATCH_QUEUE_SERIAL);// dispatch_queue_attr_t设置成NULL的时候默认代表串行。
    dispatch_async(serialQueue, ^{
         // something
         NSLog(@"current thread = com.example.SerialQueue  %@", [NSThread currentThread]);
    });
}
#pragma mark - 并发队列
- (void)dispatch_concurrentQueue{
    //并发队列
    dispatch_queue_t concurrentQueue;
    concurrentQueue = dispatch_queue_create("com.example.ConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // something
        NSLog(@"current thread = com.example.ConcurrentQueue %@", [NSThread currentThread]);
    });
}
#pragma mark - 主队列
- (void)dispath_mainQueue{
    // 主队列:
    dispatch_queue_t mainQueue;
    mainQueue = dispatch_get_main_queue();
    
    dispatch_async(dispatch_get_main_queue(), ^{
        // something
        NSLog(@"current thread = %@  main ", [NSThread currentThread]);
        
    });
}
#pragma mark - 自定义dispatch_queue_t
- (void)dispatch_queue_t{
    // 自定义dispatch_queue_t
    dispatch_queue_t emailQueue = dispatch_queue_create("www.summerHearts@163.com", NULL);
    dispatch_async(emailQueue, ^{
        NSLog(@"current thread = %@", [NSThread currentThread]);
    });
}

#pragma mark -信号量
- (void)dispatch_semaphore{
    //信号量:
    /*
     dispatch Semaphore 是持有计数的信号,该计数是多线程编程中的计数类型信号。所谓信号,就是过马路时常用的手旗。可以通过时举起手旗,不可通过时放下手旗。使用计数来实现该功能。计数为0时等待,计数为1或者大于1,减去1而不等待。
     */
    
    dispatch_queue_t queues = dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    
    NSMutableArray *array = [NSMutableArray array];
    
    for (int i = 0; i <  10; ++i) {
        dispatch_async(queues, ^{
            /*
             * 等待 Dispatch Semaphore.
             * 一直等待,直到Dispatch Semaphore的计数达到大于等于1
             *  // 由于是异步执行的,所以每次循环Block里面的dispatch_semaphore_signal根本还没有执行就会执行dispatch_semaphore_wait,从而semaphore-1.当循环10此后,semaphore等于0,则会阻塞线程,直到执行了Block的dispatch_semaphore_signal 才会继续执行
             */
            
            long result =  dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@">>>>   %ld",result);
            
            /*
                此时 result为0, 可安全的执行需要进行排他控制的处理。该处理结束的时候通过dispatch_semaphore_signal函数将dispatch_semaphore的计数值加1.
             */
            /*
             由于Dispatch semaphore 的计数值达到大于或者等于等一1,所以将 Dispatch semaphore的计数值减去1. dispatch_semaphore_wait函数执行返回。即执行到此,Dispatch semaphore 的计数值为0.
             
             */
            [array addObject:[NSNumber numberWithInt:i]];
            
            /*
             dispatch_semaphore_signal函数将dispatch semaphore 的计数值加1.如果有通过 dispatch_semaphore_wait函数等待 dispatch semaphore的计数值增加的线程就由最先等待的线程执行。
             */
            dispatch_semaphore_signal(semaphore);
        });
            //dispatch_semaphore_wait 函数等待Dispatch Semaphore的计数值达到大于或者等于1.当计数值大于等于1,或者在待机中技术值大于或者等于1,对该计数进行减法并从dispatch_semaphore_wait函数返回。
    }
}

#pragma mark - 栅栏
- (void)dispatch_barrier_async{
    // 自定义并发队列
    /*
     通过dispatch_barrier_async函数提交的任务会等它前面的任务执行完才开始,然后它后面的任务必须等它执行完毕才能开始.
     必须使用dispatch_queue_create创建的队列才会达到上面的效果.dispatch_barrier_async 起到了“承上启下”的作用。它保证此前的任务都先于自己执行,此后的任务也迟于自己执行。正如barrier的含义一样,它起到了一个栅栏、或是分水岭的作用。
     这样一来,使用并行队列和 dispatc_barrier_async 方法,就可以高效的进行数据和文件读写了。
    */

    /*
     dispatch_barrier_async函数会等待追加到concurrent dispatch queue 上的并行执行的处理全部结束之后,再将制订的处理追加到该concurrent dispatch queue中。然后再由dispatch_barrier_async函数追加的处理执行完毕之后,concurrent dispatch queue才恢复过来一般的动作,追加到该concurrent dispatch queue的处理又开始并行执行。
     */
    // 自定义串行队列
    dispatch_queue_t serialQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(serialQueue, ^() {
        NSLog(@"dispatch_async--1");
    });
    
    dispatch_async(serialQueue, ^() {
        NSLog(@"dispatch_async--2");
    });
    
    dispatch_barrier_async(serialQueue, ^() {
        NSLog(@"dispatch-barrier_async");
        sleep(1);
    });
    
    // 虽然异步, 但是在串行队列中, 会按照顺序执行
    dispatch_async(serialQueue, ^() {
        NSLog(@"dispatch_async--3");
        sleep(1);
    });
    
    dispatch_async(serialQueue, ^() {
        NSLog(@"dispatch_async--4");
    });
}

#pragma mark - 一次操作
- (void)dispatch_once_t{
    // 一次性执行:
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // code to be executed once
    });
}
#pragma mark -延时执行操作
- (void)dispatch_queue_after{
    // 延迟2秒执行:
    double delayInSeconds = 2.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        // code to be executed on the main queue after delay
    });
}
#pragma mark - 线程组
- (void)dispatch_queue_group{
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
        // 并行执行的线程一
    });
    dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
        // 并行执行的线程二
    });
    dispatch_group_notify(group, dispatch_get_global_queue(0,0), ^{
        
        NSLog(@"current thread = %@", [NSThread currentThread]);
        
    });
}
多线程.png

需要注意的点

  #define LOCK(...) dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER); \
  __VA_ARGS__; \
  dispatch_semaphore_signal(self->_lock);

如何使用

       dispatch_queue_t concurrentQueue =    dispatch_queue_create("www.fangchuang.com", DISPATCH_QUEUE_CONCURRENT);
       dispatch_group_t group = dispatch_group_create();
       _lock = dispatch_semaphore_create(1);
       for (int i = 0; i < 50; i++) {
        //并列队列的异步执行
        dispatch_group_async(group, concurrentQueue, ^{
            //如果lock的值大于等于1继续执行,否则(-1)返回
            LOCK(
                 NSLog(@">>>>  %d",i);
            );
        });
    }

如何给一个线程设置标识符

     dispatch_queue_t MessageDataPrepareQueue(){
     static dispatch_queue_t queue;
     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
           queue = dispatch_queue_create("DataPrepareSpecificKeyMessage.queue", 0);
           dispatch_queue_set_specific(queue, DispatchMessageDataPrepareSpecificKey, (void *)DispatchMessageDataPrepareSpecificKey, NULL);
    });
          return queue;
    }

UITableView滑动时并发其它操作需要注意点

dispatch_sync(MessageDataPrepareQueue(), ^{

    if (dispatch_get_specific(DispatchMessageDataPrepareSpecificKey)) {
        //当前队列是queue1队列,所以能取到DispatchMessageDataPrepareSpecificKey对应的值,故而执行
        //后台线程处理宽度计算,处理完之后同步抛到主线程插入
    }else{
       
    }
});

//此时遇到tableView 正在滑动就延时操作
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), MessageDataPrepareQueue(), ^{
    NSLog(@"延时操作");
});

@synchronized需要注意点

滥用 @synchronized (self) 会很危险,因为所有同步块都会彼此抢夺同一个锁。要是有很多属性都这样写,那么每个属性的同步块都要等待其他所有同步块执行完毕才能执行,其实我们只是想要每个属性各自独立的同步
实用@synchronized (self)从某种程度上来说,是线程安全的,但却无法保证访问该对象时是线程安全的。当然,访问属性的操作确实是原子的。实用属性时,确实能从中获取有效值,然而在同一个线程上多次调用getter方法,每次获取的结果却是未必相同的。在两次访问操作之间,可能有其他线程写入了新的值。
解决方案: 将写入操作和读取操作放在同一个线程中执行,保证数据同步。
dispatch_barrier在并发队列中创建一个同步点,当并发队列中遇到一个 dispatch_barrier时,会延时执行该 dispatch_barrier,等待在 dispatch_barrier之前提交的任务block执行完后才开始执行,之后,并发队列继续执行后续block任务。
在队列中,栅栏块必须单独执行,不能和其他块并行。并发队列如果发现接下来要处理的块是个栅栏块,那么就一直等待当前所有并发块都执行完毕,才会单独执行这个栅栏块。等待栅栏块执行过后,再按正常方式继续向下执行。

下边给出案例:银行账户存取案例的优化,保证真正的线程安全
#import <Foundation/Foundation.h>

typedef void(^AccountBalanceBock)();

@interface Account : NSObject

@property (nonatomic ,assign) NSInteger balance;

@property (copy ,nonatomic) AccountBalanceBock accountBalanceBock;

- (void)withdraw:(NSInteger)amount success:(AccountBalanceBock)success;

- (void)deposit:(NSInteger)amount success:(AccountBalanceBock)success;
@end
  
#import "Account.h"

@interface Account ()

@property (nonatomic ,strong) dispatch_queue_t queue;

@property (nonatomic ,assign) NSInteger privateBalance;

@end

@implementation Account

- (Account *)init{
     self = [super init];
     if (self) {
         self.queue = dispatch_queue_create("www.fangchang.com", NULL);
         self.privateBalance = 0;
     }
       return self;
 }

 - (NSInteger)balance{

     dispatch_sync(self.queue, ^{
           _balance = self.privateBalance;
     });
    // 将写入操作和读取操作放在同一个线程中执行,保证数据同步。
      return _balance;
}

- (void)withdraw:(NSInteger)amount success:(AccountBalanceBock)success{

         NSInteger newBalance = self.privateBalance - amount;
         if (newBalance<0) {
                NSLog(@"当前账户余额不足100");
               return ;
            }
          sleep(1);
          self.privateBalance = newBalance;
          dispatch_async(dispatch_get_main_queue(), ^{
              if (success!=nil) {
                  success();
              }
          });
 }

- (void)deposit:(NSInteger)amount success:(AccountBalanceBock)success{

    NSInteger newBalance = self.privateBalance + amount;
    self.privateBalance = newBalance;

    dispatch_async(dispatch_get_main_queue(), ^{
        if (success!=nil) {
             success();
        }
   });
}

- (void)setPrivateBalance:(NSInteger)privateBalance{
      dispatch_barrier_async(self.queue, ^{
           _privateBalance = privateBalance;
      });
    // dispatch_barrier在并发队列中创建一个同步点,当并发队列中遇到一个 dispatch_barrier时,会延时执行该 dispatch_barrier,等待在 dispatch_barrier之前提交的任务block执行完后才开始执行,之后,并发队列继续执行后续block任务
}
@end

github 地址:
https://github.com/summerHearts/ThreadSanitizer
https://github.com/summerHearts/iOSTips

相关文章

  • GCD常见用法和实践中需要注意的点

    常见问题总结:1: GCD里面有多少种全局队列?详情请查看dispatch queue.h 头文件 2: 基本概...

  • iOS多线程之GCD浅析

    本文用来介绍 iOS 多线程中 GCD 的相关知识以及使用方法,分析GCD的原理,总结GCD的用法和需要注意的地方...

  • iOS面试--GCD常见用法

    项目中常见的GCD用法有已下几种: 1.GCD栅栏函数2.GCD快速迭代(遍历)3.GCD队列组的使用 1.GCD...

  • GCD常见用法总结

    提交 block 在主队列提交一个异步 block 在默认优先级的全局队列提交一个同步 block,请尽量使用默认...

  • UIPickView和UIDatePicker

    UIPickView和UIDatePicker UIPickView 样式 作用 常见用法 注意 显示数据 UID...

  • 关于GCD常用的方法

    iOS开发多线程篇—GCD的常见用法 一、延迟执行 1.介绍 iOS常见的延时执行有2种方式 (1)...

  • GCD摘要

    这里只写一些自己总结的要点,和使用时需要注意的地方.跟着原作者的思路一点点记录. 什么是 GCD 1.GCD 能通...

  • 【多线程】GCD的常见用法

    一、延迟执行1.介绍iOS常见的延时执行有2种方式(1)调用NSObject的方法 [self performSe...

  • IOS Block 的使用 weakSelf strongSel

    Block块 在OC里边应该是比较常见和使用的了,本文只是根据个人经验,记录一些关键的用法提示点和注意点,以方便以...

  • 浅谈iOS GCD常见的几种用法和线程锁的应用

    关于GCD的一些常见的用法。 GCD iOS 4.0以后苹果推出,是苹果公司为多核的并行运算提出的解决方案。相对于...

网友评论

    本文标题:GCD常见用法和实践中需要注意的点

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