iOS开发:深入理解GCD 第二篇(dispatch_group、dispatch_barrier、基于线程安全的多读单写)

    1. Dispatch Group
      在追加到Dispatch Queue中的多个任务处理完毕之后想执行结束处理,这种需求会经常出现。如果只是使用一个Serial Dispatch Queue(串行队列)时,只要将想执行的处理全部追加到该串行队列中并在最后追加结束处理即可,但是在使用Concurrent Queue 时,可能会同时使用多个Dispatch Queue时,源代码就会变得很复杂。

      在这种情况下,就可以使用Dispatch Group。


      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      dispatch_group_t group = dispatch_group_create();

          dispatch_queue_t queue = dispatch_queue_create("com.gcd-group.www", DISPATCH_QUEUE_CONCURRENT);

          

          dispatch_group_async(group, queue, ^{

              for (int i = 0; i < 1000; i++) {

                  if (i == 999) {

                      NSLog(@"11111111");

                  }

              }

              

          });

          

          dispatch_group_async(group, queue, ^{

              NSLog(@"22222222");

          });

          

          dispatch_group_async(group, queue, ^{

              NSLog(@"33333333");

          });

          

          dispatch_group_notify(group, queue, ^{

              NSLog(@"done");

          });

      控制台的输出:

      因为向Concurrent Dispatch Queue 追加处理,多个线程并行执行,所以追加处理的执行顺序不定。执行顺序会发生变化,但是此执行结果的done一定是最后输出的。

      无论向什么样的Dispatch Queue中追加处理,使用Dispatch Group都可以监视这些处理执行的结果。一旦检测到所有处理执行结束,就可以将结束的处理追加到Dispatch Queue中,这就是使用Dispatch Group的原因。

      下面试一个使用Dispatch Group异步下载两张图片,然后合并成一张图片的medo(注意,我们总是应该在主线程中更新UI):


      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      41

      42

      43

      44

      45

      46

      47

      48

      49

      50

      51

      52

      53

      54

      55

      56

      57

      58

      59

      60

      61

      62

      63

      64

      65

      66

      67

      #import "ViewController.h"

      @interface ViewController ()

      @property (nonatomic, strong) UIImage *imageOne;

      @property (nonatomic, strong) UIImage *imageTwo;

      @property (nonatomic, weak) UILabel *textLabel;

      @end

      @implementation ViewController

      - (void)viewDidLoad {

          [super viewDidLoad];

          

          [self operation1];

      }

      - (void)operation1

      {

          UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectMake(200, 450, 0, 0)];

          textLabel.text = @"正在下载图片";

          [textLabel sizeToFit];

          [self.view addSubview:textLabel];

          self.textLabel = textLabel;

          [self group];

          NSLog(@"在下载图片的时候,主线程貌似还可以干点什么");

      }

      - (void)group

      {

          UIImageView *imageView = [[UIImageView alloc] init];

          [self.view addSubview:imageView];

          

          dispatch_group_t group = dispatch_group_create();

          dispatch_queue_t queue = dispatch_queue_create("cn.gcd-group.www", DISPATCH_QUEUE_CONCURRENT);

          

          dispatch_group_async(group, queue, ^{

              NSLog(@"正在下载第一张图片");

              NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://images2015.cnblogs.com/blog/471463/201509/471463-20150912213125372-589808688.png"]];

              NSLog(@"第一张图片下载完毕");

              self.imageOne = [UIImage imageWithData:data];

          });

          

          dispatch_group_async(group, queue, ^{

              NSLog(@"正在下载第二张图片");

              NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://images2015.cnblogs.com/blog/471463/201509/471463-20150912212457684-585830854.png"]];

              NSLog(@"第二张图片下载完毕");

              self.imageTwo = [UIImage imageWithData:data];

          });

          

          dispatch_group_notify(group, queue, ^{

              UIGraphicsBeginImageContext(CGSizeMake(300, 400));

              

              [self.imageOne drawInRect:CGRectMake(0, 0, 150, 400)];

              [self.imageTwo drawInRect:CGRectMake(150, 0, 150, 400)];

              

              UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();

              UIGraphicsEndImageContext();

              

              dispatch_async(dispatch_get_main_queue(), ^{

                  UIImageView *imageView = [[UIImageView alloc] initWithImage:newImage];

                  [self.view addSubview:imageView];

                  self.textLabel.text = @"图片合并完毕";

              });

          });

      }

      @end

    2. dispatch_barrier_async
      在访问数据库或者文件的时候,我们可以使用Serial Dispatch Queue可避免数据竞争问题,代码如下所示:
      先看看,如果我们在平常编码中,如果要保证某个属性可以线程安全的读写,如何写的:

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      #import <Foundation/Foundation.h>

      @interface ZYPerson : NSObject

      @property (nonatomiccopyNSString *name;

      @end

      #import "ZYPerson.h"

      static NSString *_name;

      @implementation ZYPerson

      - (void)setName:(NSString *)name

      {

          @synchronized(self) {

              _name = [name copy];

          }

      }

      - (NSString *)name

      {

          @synchronized(self) {

              return _name;

          }

      }

      @end

      这是我在刚学iOS开发,刚涉及并发中的数据竞争时,书本上提到的一种解决方案。如果有多个线程要执行同一份代码,那么有时候可能会出现问题,这种情况下,通常要使用锁来实现某种同步机制。iOS提供了一种加锁的方式,就是采用内置的synchronization block,也就是上面代码所写的。

      这种写法会根据给定的对象,自动创建一个锁,并等待块中的代码执行完毕。执行到这段代码结尾处,锁也就释放了。在上面的例子中,同步行为所针对的对象是self。这么写通常没错,但是@synchronized(self)会大大降低代码效率,甚至很多时候,还可以被人感觉到效率明显下降了,因为共用同一个锁的那些同步块,都必须按顺序执行。若在self对象上频繁加锁,那么程序可能就要等另一段与此无关的代码执行完毕,才可以继续执行当前代码,这样做是很没必要的。

      @synchronized(self)会大大降低代码效率,因为所有的同步块(  @synchronized(self)  )都会彼此抢夺同一个锁。要是有多个属性这么写,每个属性的同步块(  @synchronized(self)  )都要等其他所有的同步块执行完毕之后才能执行,这并不是我们想要的结果,我们只想要每个属性各自独立的同步。

      还有,不得不说,按上面这么做,虽然可以在一定程度上提供“线程安全”,但却无法保证访问该对象时是绝对线程安全的。事实上,上面的写法,就是atomic,也就是原子性属性xcode自动生成的代码,这种方法,在访问属性时,必定可以从中得到有效值,然而如果在一个线程上多次调用getter方法,每次得到的结果却未必相同,在两次读操作之间,其他线程可能会写入新的属性值。

      其实使用GCD可以简单高效的代替同步块或者锁对象,可以使用,串行同步队列,将读操作以及写操作都安排在同一个队列里,即可保证数据同步,代码如下:


      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      #import <Foundation/Foundation.h>

      @interface ZYPerson : NSObject

      @property (nonatomiccopyNSString *name;

      @end

      #import "ZYPerson.h"

      @interface ZYPerson ()

      @end

      static NSString *_name;

      static dispatch_queue_t _queue;

      @implementation ZYPerson

      - (instancetype)init

      {

          if (self = [super init]) {

             _queue = dispatch_queue_create("com.person.syncQueue", DISPATCH_QUEUE_SERIAL);

          }

          return self;

      }

      - (void)setName:(NSString *)name

      {

          dispatch_sync(_queue, ^{

              _name = [name copy];

          });

      }

      - (NSString *)name

      {

          __block NSString *tempName;

          dispatch_sync(_queue, ^{

              tempName = _name;

          });

          return tempName;

      }

      @end

      这样写的思路是:把写操作与读操作都安排在同一个同步串行队列里面执行,这样的话,所有针对属性的访问操作就都同步了。
      这种方法的确已经足够好了,但还不是最优的,它只可以实现单读、单写。整体来看,我们最终要解决的问题是,在写的过程中不能被读,以免数据不对,但是读与读之间并没有任何的冲突!

      多个getter方法(也就是读取)是可以并发执行的,而getter(读)与setter(写)方法是不能并发执行的,利用这个特点,还能写出更快的代码来,这次注意,不用串行队列,而改用并行队列:


      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      #import <Foundation/Foundation.h>

      @interface ZYPerson : NSObject

      @property (nonatomiccopyNSString *name;

      @end

      #import "ZYPerson.h"

      @interface ZYPerson ()

      @end

      static NSString *_name;

      static dispatch_queue_t _concurrentQueue;

      @implementation ZYPerson

      - (instancetype)init

      {

          if (self = [super init]) {

             _concurrentQueue = dispatch_queue_create("com.person.syncQueue", DISPATCH_QUEUE_CONCURRENT);

          }

          return self;

      }

      - (void)setName:(NSString *)name

      {

          dispatch_barrier_async(_concurrentQueue, ^{

              _name = [name copy];

          });

      }

      - (NSString *)name

      {

          __block NSString *tempName;

          dispatch_sync(_concurrentQueue, ^{

              tempName = _name;

          });

          return tempName;

      }

      @end

      这样优化,测试一下性能,可以发现这种做法肯定比使用串行队列要快。

      在这个代码中,我用了点新的东西,dispatch_barrier_async,可以翻译成栅栏(barrier),它可以往队列里面发送任务(块,也就是block),这个任务有栅栏(barrier)的作用。

      在队列中,barrier块必须单独执行,不能与其他block并行。这只对并发队列有意义,并发队列如果发现接下来要执行的block是个barrier block,那么就一直要等到当前所有并发的block都执行完毕,才会单独执行这个barrier block代码块,等到这个barrier block执行完毕,再继续正常处理其他并发block。在上面的代码中,setter方法中使用了barrier block以后,对象的读取操作依然是可以并发执行的,但是写入操作就必须单独执行了。

    3. 附录(也算是追加)

      也许会对并行、串行、异步、同步有所理解不深。
      并行:就是队列里面的任务(代码块,block)不是一个个执行,而是并发执行,也就是可以同时执行的意思
      串行:队列里面的任务一个接着一个执行,要等前一个任务结束,下一个任务才可以执行
      异步:具有新开线程的能力
      同步:不具有新开线程的能力,只能在当前线程执行任务

      那么,如果他们相互串起来,会怎么样呢?
      并行+异步:就是真正的并发,新开有有多个线程处理任务,任务并发执行(不按顺序执行)
      串行+异步:新开一个线程,任务一个接一个执行,上一个任务处理完毕,下一个任务才可以被执行
      并行+同步:不新开线程,任务一个接一个执行
      串行+同步:不新开线程,任务一个接一个执行

      如此,并行+异步,串行+异步倒是很好辨别,他们有各自的特点,那么并行+同步,串行+同步,貌似效果是一样的,好像用哪种都一样??!!
      不,这其中区别很大(不得不说一句,我所看多线程书籍,基本没讲到这点的,不知道是不是他们都认为这是基础,没必要说明)。

      这其中的区别,我觉得代码体现更直观,直接上代码吧:
      这一份是并行+同步,我在外面并发添加代码块:


      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

              dispatch_sync(dispatch_get_main_queue(), ^{

                  NSLog(@"11  %@",[NSThread currentThread]);

              });

          });

          

          dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

              dispatch_sync(dispatch_get_main_queue(), ^{

                  NSLog(@"22  %@",[NSThread currentThread]);

              });

          });

          

          dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

              dispatch_sync(dispatch_get_main_queue(), ^{

                  NSLog(@"33  %@",[NSThread currentThread]);

              });

          });

          

          dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

              dispatch_sync(dispatch_get_main_queue(), ^{

                  NSLog(@"44  %@",[NSThread currentThread]);

              });

          });

          

          dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

              dispatch_sync(dispatch_get_main_queue(), ^{

                  NSLog(@"55  %@",[NSThread currentThread]);

              });

          });

      看看打印:

      在一个线程里面,任务一个接一个的执行。

      再看看,并行+同步,并发调用:


      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

          dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

              NSLog(@"11  %@",[NSThread currentThread]);

          });

      });

      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

          dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

              NSLog(@"22  %@",[NSThread currentThread]);

          });

      });

      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

          dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

              NSLog(@"33  %@",[NSThread currentThread]);

          });

      });

      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

          dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

              NSLog(@"44  %@",[NSThread currentThread]);

          });

      });

      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

          dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

              NSLog(@"55  %@",[NSThread currentThread]);

          });

      });

      打印:

      新开了多个线程,任务并发执行。

时间: 2024-11-09 00:17:28

iOS开发:深入理解GCD 第二篇(dispatch_group、dispatch_barrier、基于线程安全的多读单写)的相关文章

iOS开发:深入理解GCD 第一篇

最近把其他书籍都放下了,主要是在研究GCD.如果是为了工作,以我以前所学的GCD.NSOperation等知识已经足够用了,但学习并不仅仅知识满足于用它,要知其然.并且知其所以然,这样才可以不断的提高自身技术水平. 本文主要参考http://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1 和 <iOS与OS X 多线程和内存管理>,以及其他一些杂七杂八的书籍或者博客. GCD已经面世很久了,基于GCD面向对象

斯坦福IOS开发第五课(第二部分)

转载请注明出处 http://blog.csdn.net/pony_maggie/article/details/27845257 作者:小马 五 代码示例 上面讲到的知识点在这个示例都有涉及.另外我这里也只是分析部分重要的代码,更多的知识了解请自行下载代码(文章最下面有地址)并结合公开课一起看. 新建一个single view的工程,然后新增一个视图类,叫FaceView,如下图所示: 然后我们在storyboard里拖进来一个通用的视图控件,作为上面那个视图类对应的视图,如下图所示:    

iOS开发之多线程——GCD介绍

iOS开发之多线程——GCD的介绍 一.简单介绍 1.GCD ( Grand Central Dispatch) 可以翻译为“中枢调度器”.纯C语言,并且提供了非常强大的函数. 2.GCD 有什么优势: GCD是苹果公司为多核的并行运算提出的解决方案 GCD会自动利用更多地CPU 内核 (比如双核.四核) GCD会自动管理线程的生命周期 (创建线程.调度任务.销毁线程) 程序猿只需要告诉GCD想要执行设呢任务,不需要编写任何线程管理代码. 二.任务和队列 GCD中有两个核心概念 (1)任务: 执

iOS开发——完整项目实战OC篇&amp;百思不得姐第四天

iOS开发——完整项目实战OC篇&百思不得姐第四天 上午 一:自定义按钮使用九宫格布局 二:控件不能点击 三:获取用户点击了那个按钮 四:调整按钮内部控件的位置:主流->上下 五:不能直接使用self.navigationController中或者View中获取导航控制器 方法一: 方法二: 六:布局取整 1 // 总行数 2 3 // NSUInteger rows = sqaures.count / maxCols; 4 5 // if (sqaures.count % maxCols)

iOS开发中的gcd多线程tips

iOS开发中的gcd多线程tips 我们经常用到的: dispatch_async(dispatch_get_global_queue(0, 0), ^{ // 处理耗时操作的代码块 //通知主线程刷新 dispatch_async(dispatch_get_main_queue(), ^{ //回调或者说是通知主线程刷新 }); }); 其中main_queue是系统默认的串行队列,global_queue是系统默认的并行队列. 什么是串行队列(Serial)? 创建任意个数的串行队列,每个队

iOS开发SDWebImageOptions理解

iOS开发SDWebImageOptions理解 原文 http://www.cnblogs.com/WJJ-Dream/p/5816750.html typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { SDWebImageRetryFailed = 1 << 0, SDWebImageLowPriority = 1 << 1, SDWebImageCacheMemoryOnly = 1 << 2, SDWebImag

iOS GCD中级篇 - dispatch_group的理解及使用

前文我们讲了GCD基础篇,以及同步.异步,并发.并行几个概率的理解. 参考链接: iOS GCD基础篇 - 同步.异步,并发.并行的理解 现在讲一下dispatch_group的概念以及几种场景下的使用 1.关于dispatch_group 把一组任务提交到队列中,这些队列可以不相关,然后监听这组任务完成的事件. 最常见的几个方法: 1.dispatch_group_create创建一个调度任务组 2.dispatch_group_async 把一个任务异步提交到任务组里 3.dispatch_

IOS开发之多线程 -- GCD的方方面面

前言:这篇GCD的博文是本人阅读了很多海内外大神的关于GCD的文章,以及结合之前自己对GCD的粗浅的认识,然后取其精华,去其槽粕,综合起来的笔记,而且是尽可能的以通熟易懂的并且是正确的理论论述方式呈现给读者,同时也是讲大神博客中有的深涩的理论理解的通熟易懂转述给读者,已经是尽可能的让读者深入理解和掌握多线程的知识以及GCD的使用技术.最后的附录中,我将会给出所有本人阅读的大神写的关于多线程或者是GCD的文章链接,大家感兴趣的,可以去参考和学习.也许,看我的这篇就够了,因为我就是参考他们的,嘻嘻.

iOS开发之多线程技术——NSOperation篇

本篇将从四个方面对iOS开发中使用到的NSOperation技术进行讲解: 一.什么是NSOperation 二.我们为什么使用NSOperation 三.在实际开发中如何使用NSOperation 1.自定义NSOperation 2.NSOperation的基本使用 3.NSOperation实现线程间通信 1)利用代理进行消息传递 2)利用通知实现消息传递 3)利用block进行消息传递 四.与GCD比较 一.什么是NSOperation NSOperation是一个抽象的基类,表示一个独