多线程-GCD

概念相关

  1. 从iOS4引入了GCD技术。它是多线程技术之一。和NSThread,performSelector一样可以实现多线程。
  2. NSThread,performSelector,GCD他们三者的区别是什么
    1. 从书写角度考虑。GCD书写简单易懂。其他两个书写繁琐。
    2. GCD是c的api执行速度更快。
    3. GCD可以很好的利用cpu核心数等硬件资源,也可以很好的利用系统的任务调度等软件资源合理的实现多线程。
    4. 因此我们应该不用NSThread,perforSelector。应该多用GCD,GCD解决不了任务就用NSOperationQueue。
    5. GCD实际上在管理一个线程池,这样就可以避免每次创建线程造成耗时耗内存的问题。以空间换时间的思想。

线程与队列,并行与并发(解释下队列,线程,并行,并发)

  1. 任务都是挂在队列上的。队列的特点是先进先出的。
  2. 线程是用来选择队列执行的。从而执行了任务
  3. 多线程技术包括并行与并发,GCD可以合理的选择是用并发,还是用并行。
  4. 并行是指利用cpu的多核心数特定实现同时执行任务。
  5. 并发是指利用系统的任务调度实现交替的执行任务。

同步异步与队列

  1. 同步的含义是指在当前线程执行任务,当该任务执行完毕后才会返回,它会阻塞当前线程。
  2. 异步的含义是新开个线程去执行任务它不会阻塞当前的线程。当前线程继续执行接下来的任务,先开个子线程去执行加入的任务。
  3. 串行队列的含义是指任务的执行室比较小一次只能执行一个任务。一个任务执行完毕后再执行下一个任务。
  4. 并发队列的含义是指任务的执行室比较大,支持多个任务同时执行。

根据使用场景选择GCD的API

  1. 想要不阻塞当前线程的情况下同时执行多个任务

    1. 这时候应该用异步并行队列
    2. 代码如下
    dispatch_queue_t queueConcurrent = dispatch_get_global_queue(0, 0);
        for (int i = 0; i < 10; i++) {
            dispatch_async(queueConcurrent,^{
                NSLog(@"curThread = %@,i = %d",[NSThread currentThread],i);
            });
        }
    
  2. 在同时执行多个任务情况下。因为同时执行多个任务情况下哪个任务先执行完不一定,因此需要排序

    1. 这时候在1的代码基础上要先创建个可变数组,在任务执行里边要创建个用于排序的字典,该字典要有两个元素,一个是order的key,index的value用于排序。第二个key为content内容,value为要存的值(往往是网络请求的结果)。之后再数组里add这个字典。最后排序这个数组即可。
    2. 代码详情可以参考本专题的 开发相关 1.2代码
  3. 在同时执行多个任务情况下。这些任务执行多执行完毕后要执行相关逻辑代码

    1. 这时候可以使用dispatch_group,利用dispatch_group_notify来解决问题。
    2. 代码如下
        dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t queueConcurrent = dispatch_get_global_queue(0, 0);
        for (int i = 0; i < 10; i++) {
            dispatch_group_async(group,queueConcurrent,^{
                sleep(0.5);
                NSLog(@"curThread = %@,i = %d",[NSThread currentThread],i);
            });
        }
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"所有任务执行完毕!");
        });
    
  4. 在同时执行多个任务情况下。想要一个线程等待一组线程执行完毕后这个线程在执行相关逻辑代码(线程与线程组的同步)

    1. 在这种情况下可以使用dispatch_group,配合使用dispatch_group_wait函数来实现线程与线程组的同步。
    2. 代码:
     dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t queueConcurrent = dispatch_get_global_queue(0, 0);
        for (int i = 0; i < 10; i++) {
            dispatch_group_async(group,queueConcurrent,^{
                sleep(0.5);
                NSLog(@"curThread = %@,i = %d",[NSThread currentThread],i);
            });
        }
        
        dispatch_group_wait(group,DISPATCH_TIME_FOREVER);
        NSLog(@"线程组执行完毕!");
    
    1. 其实这种情况下是在阻塞当前线程,让当前线程去等待线程组执行完毕,在让当前线程终止阻塞,去运行。
  5. 在同时执行多个任务情况下。这些任务同时访问了共同的数据,导致了数据竞争或者非法访问内存导致程序崩溃。(写入与写入同时进行会导致内存非法访问程序崩溃。写入与读取同时进行,因为涉及到先后关系,会出现数据竞争问题)
    0. 在这种情况下一共有三种解决办法,推荐使用dispatch_barrier_async配合dispatch_queue_create创建的并发队列。

    1. 利用dispatch_barrier_async配合dispatch_queue_create创建的并发队列将写入任务隔离
      1. 代码
       __block  int a = 3;
        dispatch_queue_t queueConcurrent = dispatch_queue_create("com.test.queueConcurrent", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(queueConcurrent, ^{
            //读取
            sleep(1);
            NSLog(@"任务1 a = %d",a);
        });
        dispatch_async(queueConcurrent, ^{
            //读取
            NSLog(@"任务2 a = %d",a);
        });
        dispatch_async(queueConcurrent, ^{
            //读取
            NSLog(@"任务3 a = %d",a);
        });
        
        dispatch_barrier_async(queueConcurrent, ^{
            //写入
            a = 5;
        });
        
        dispatch_async(queueConcurrent, ^{
            //读取
            NSLog(@"任务4 a = %d",a);
        });
        dispatch_async(queueConcurrent, ^{
            //读取
            NSLog(@"任务5 a = %d",a);
        });
    
    1. 利用串行队列

      1. 因为串行队列任务是串行一个一个执行的,这样就不会发生同时访问一个数据的问题了。
    2. 利用信号量

      1. 利用信号量使得任务一个一个执行,避免了多个线程同时访问同一个数据。
      2. 代码
      NSMutableArray *arrM = [NSMutableArray array];
      dispatch_queue_t queueConcurrent = dispatch_queue_create("com.test.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
      dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
      for (int i = 0; i < 1000; i++) {
          dispatch_async(queueConcurrent, ^{
              dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
              [arrM addObject:[NSNumber numberWithInt:i]];
              dispatch_semaphore_signal(semaphore);
          });
      }
      
  6. 线程同步

    1. 利用信号量可以实现线程的同步
    2. 代码
    dispatch_semaphore_t semaphone = dispatch_semaphore_create(0);
    dispatch_queue_t queue1 = dispatch_get_global_queue(0, 0);
    dispatch_async(queue1, ^{
        //线程1
        dispatch_queue_t queue2 = dispatch_get_global_queue(0, 0);
        dispatch_async(queue2, ^{
            //线程2
            sleep(3.0);
            NSLog(@"线程2执行完毕");
            dispatch_semaphore_signal(semaphone);
        });
        
        dispatch_semaphore_wait(semaphone, DISPATCH_TIME_FOREVER);
        NSLog(@"线程1等待线程2完毕");
    });
    
  7. 想要在不阻塞当前线程的情况下,串行的有序的执行任务

    1. 这时应该用异步串行队列
    2. 代码
    dispatch_queue_t queueSerial = dispatch_queue_create("com.test.queueSerial", NULL);
        for (int i = 0; i < 10; i++) {
            dispatch_async(queueSerial, ^{
                NSLog(@"curThread = %@, i = %d",[NSThread currentThread],i);
            });
        }
    
  8. 想要在阻塞当前线程的情况下,串行的有序的执行任务

    1. 这时应该用同步串行队列
    2. 代码
    dispatch_queue_t queueSerial = dispatch_queue_create("com.test.queueSerial", NULL);
        for (int i = 0; i < 10; i++) {
            dispatch_sync(queueSerial, ^{
                NSLog(@"curThread = %@, i = %d",[NSThread currentThread],i);
            });
        }
    
  9. 想要挂起或者恢复一个由dispatch_queue_create函数创建的队列

    1. 这时可以用dispatch_suspend,dispatch_resume
    2. 注意只能挂起由dispatch_queue_create函数创建的队列。其实这两个函数也用在dispatch_resource上,作为启动监听和取消监听。
    3. 代码
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        queue  = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
        dispatch_suspend(queue);
        dispatch_async(queue, ^{
            NSLog(@"fsa");
        });
        
    }
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
        dispatch_resume(queue);
    }
    
  10. 想要创建一个线程安全的单利

    1. 这时候可以用dispatch_once
    2. 代码
    + (instancetype)shareInstance{
        static ViewController *instance = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            instance = [[self alloc] init];
        });
        
        return instance;
    }
    
  11. 想要大致延时一定时间去执行相关逻辑代码

    1. 这时应该用dispatch_after函数
    2. 代码
     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)5*NSEC_PER_SEC), queue, ^{
            NSLog(@"curThread = %@, 执行任务",[NSThread currentThread]);
        });
    
    1. 上述代码,如果queue = dispatch_get_main_queu();那么就在主线程执行任务。 对于其他情况都是在子线程执行任务。

dispatch_source

  1. 利用dispatch_sources实现定时器
```
{
    dispatch_source_t sourceTimer;
}


- (void)viewDidLoad {
    [super viewDidLoad];
    
    //dispatch_source实现timer
    sourceTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    dispatch_source_set_timer(sourceTimer, dispatch_time(DISPATCH_TIME_NOW, (int64_t)5*NSEC_PER_SEC), 1*NSEC_PER_SEC, 1*NSEC_PER_SEC);
    dispatch_source_set_event_handler(sourceTimer, ^{
        NSLog(@"开始执行---");
    });
    dispatch_source_set_cancel_handler(sourceTimer, ^{
        NSLog(@"结束执行---");
    });
    dispatch_resume(sourceTimer);
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    [super touchesBegan:touches withEvent:event];
    
    dispatch_suspend(sourceTimer);
}
```
  1. dispatch_source的定时器和NSTimer对比。
    dispatch_source的定时器更精确,因为NSTimer依靠NSRunlopp,NSRunloop要处理好多事导致NSTimer不精确。而dispatch_source的定时器是系统内核事件优先级较高所以定时更加精确。

  2. GCD的dispatch_source其实是支持启动和取消的。但是dispatch_queue不支持。

  3. 在进行网络编程时候不应该每次都创建新的子线程
    因为每次网络请求都创建新的子线程,那么很快就会把系统的线程用尽,也会占用大量的内存。每次创建还会很耗时。也就是说这种做法又耗时又耗内存,又可能把系统的线程用尽。所以AFN的早期版本使用了线程常驻。

在主队列中执行异步函数的使用场景

  1. 往往在子线程中用它回归主线程,更新UI。
  2. 主线程也可以用它,特别是有约束的情况下用它可以获得ui的frame。这样在它的block中其实已经知道了所有ui的frame了,可以进行后续逻辑代码。
  3. 这个任务会被放到主队列对末端执行,这样有时候是需要这样操作的。

死锁

  1. 造成死锁的条件是相互等待。

  2. 比如在主队列中执行同步函数会造成死锁。

    1. 分析:
    2. 主队列是一个串行队列,串行队列的特点是执行完前一个任务之后才能执行下一个任务。那么当前任务要等待前一个viewDidLoad任务执行完之后才能执行。而同步函数的特点是在当前线程也就是主线程中执行任务执行完后返回那么viewDidLoad任务就会等待里边的任务返回,这样相互等待就造成了死锁。
  3. 在一个串行队列的任务里嵌套一个任务,这个任务也是用的这个串行队列,并且使用了同步函数,这时候会造成死锁。

开发相关

  1. 问利用多线程实现多个图片的下载合并或者问上传多个图片到服务器之后排序

    1. 分析:其实就是利用多线程并发请求,之后再排序的问题。

    2. 代码:

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        NSMutableArray *arrM = [NSMutableArray array];//准备接收执行完的任务结果数组
        NSArray *arr = @[@"http://img.zcool.cn/community/0117e2571b8b246ac72538120dd8a4.jpg@1280w_1l_2o_100sh.jpg",
                         @"http://img.zcool.cn/community/01f09e577b85450000012e7e182cf0.jpg@1280w_1l_2o_100sh.jpg",
                         @"http://img.zcool.cn/community/0125fd5770dfa50000018c1b486f15.jpg@1280w_1l_2o_100sh.jpg",
                         @"http://pic5.nipic.com/20100221/2839526_090902782678_2.jpg"
                         ];//下载图片任务地址数组(这里往往是多个任务地址)
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_group_t group = dispatch_group_create();
        [arr enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            dispatch_group_async(group, queue, ^{
                NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:arr[idx]]];
                UIImage *img = [UIImage imageWithData:data];
                
                //创建一个字典用于数组排序
                NSDictionary *dict = @{@"order":[NSString stringWithFormat:@"%ld",idx],@"img":img};
                [arrM addObject:dict];
            });
        }];
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"name = %@",[NSThread currentThread]);
            //结果数组进行排序
            NSArray *arrSorted = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"order" ascending:YES]];
            NSArray *arrResult = [arrM sortedArrayUsingDescriptors:arrSorted];
            
             [self displayImg:arrResult];
        });
    }
    - (void)displayImg:(NSArray *)arrImg{
        [arrImg enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            NSDictionary *dict = obj;
            UIImage *img = dict[@"img"];
             UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(0, idx*150, self.view.frame.size.width, 150.0)];
            imgView.image = img;
            [self.view addSubview:imgView];
        }];
    }
    

你可能感兴趣的:(iOS)