iOS多线程系列(四)----GCD之Dispatch Queues

转载自:http://blog.sina.com.cn/s/blog_6dce99b10101atsu.html,html

   http://blog.csdn.net/q199109106q/article/details/8566300 ,尊重原创!编程

 

详解IOS开发应用之并发Dispatch Queues是本文要介绍的内容,咱们几乎能够调度队列去完成全部用线程来完成的任务网络

调度队列相对于线程代码更简单,易于使用,更高效。下面讲主要简述调度队列,在应用中如何使用调度队列去执行任务。数据结构

一、关于调度队列并发

全部的调度队列都是先进先出队列,所以,队列中的任务的开始的顺序和添加到队列中的顺序相同。GCD自动的为咱们提供了一些调度队列,咱们也能够建立新的用于具体的目的。app

下面列出几种可用的调度队列类型以及如何使用。异步

(1)serial queues(串行队列)又称私有调度队列(private),通常用在对特定资源的同步访问上。咱们能够根据须要建立任意数量的串行队列,每个串行队列之间是并发的。async

(2)并行队列,又称global dispatch queue。并行队列虽然能够并发的执行多个任务,可是任务开始执行的顺序和其加入队列的顺序相同。咱们本身不能去建立并行调度队列。只有三个可用的global concurrent queues。ide

(3)main dispatch queue 是一个全局可用的串行队列,其在行用程序的主线程上执行任务。此队列的任务和应用程序的主循环(run loop)要执行的事件源交替执行。由于其运行在应用程序的主线程,main queue常常用来做为应用程序的一个同步点。异步编程

 

二、关于队列的一些技术

除了调度队列,GCD还提供了一些有用的技术来帮助咱们管理代码。

 
  1. dispath group ,dispatch semaphore, dispath sources 

 

三、使用blocks去实现tasks

block objects是基于C语言的特征,能够用在C,C++ Objective-c中。一个block虽然和函数指针有些类似,可是实际上表明一个底层数据结构,相似与对象,有编译器去建立和管理。

block的一个优点是可使用其本身做用域外的变量,例如,一个block能够读取其父做用域的变量值,此值是copy到了block heap的数据结构中。当block被加入到dispatch queue中,这些值一般为只读形式。

block的声明和函数指针相似,只是把*改成了^,咱们能够传递参数给block,也能够接收其返回的值。

 

四、建立和管理调度队列

(1)得到全局并发调度队列(global concurrent dispath queues)

系统给每个应用程序提供了三个concurrent dispatch queues。这三个并发调度队列是全局的,它们只有优先级的不一样。由于是全局的,咱们不须要去建立。咱们只须要经过使用函数dispath_get_global_queue去获得队列,以下:

dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

 

除了获得default的并发队列,还能够经过传递参数DISPATCH_QUEUE_PRIOPITY_HIGH和DISPATCH_QUEUE_PRIOPITY_LOW去获得高优先级或者低优先级的。(第二个参数是为之后扩展保留的)

虽然dispatch queue是引用计数对象,可是在此由于队列是全局的,不须要咱们去retain或者release,咱们须要使用的时候直接调用函数dispath_get_global_queue就能够。

 

(2)建立串行调度队列

当想要任务按照某一个特定的顺序执行时,串行队列是颇有用的。串行队列在同一个时间只执行一个任务。咱们可使用串行队列代替锁去保护共享的数据和锁不一样,一个串行队列能够保证任务在一个可预知的顺序下执行

和并发队列不一样,咱们要本身去建立和管理串行队列,能够建立任意数量的串行队列。当咱们建立串行队列时,应出于某种目的,如保护资源,或者同步应用程序的某些关键行为

下面的代码表述了怎么建立一个自定义的串行队列,函数dispath_queue_create须要两个参数,队列的名字,队列的属性。调试器和性能工具显示队列的名字帮助咱们去跟踪任务是如何执行,队列的属性被保留供未来使用,应该为NULL

dispatch_queue_t queue;  
queue = dispatch_queue_create("com.example.MyQueue", NULL); 

除了本身建立的自定义队列,系统会自动的给我建立一个串行队列并和应用程序的主线程绑定到一块儿。下面讲述如何得到它。

 

(3)运行时得到常见的队列

GCD提供了一些函数让咱们可以方便的访问到common dispatch queues

使用dispatch_get_current_queue函数用来调试或者测试得到当前队列的标识。
   
使用函数dispatch_get_main_queue能够获得与应用程序主线程相连的串行调度队列

 

(4)调度队列的内存管理

调度队列是引用计数类型,当咱们建立串行调度队列时,咱们要release它。能够使用函数dispatch_retain和dispatch_release去增长或者减小引用计数

 

(5)在一个队列中存储自定义context information

全部的调度对象容许咱们让其与一个自定义上下文数据关联,经过函数dispatch_set_contextdispatch_get_context来使用,系统不会去使用咱们的自定义数据,咱们本身在恰当的时间去分配和释放。

对于队列,上下文数据一般用来存储一个指向对象的指针,或者其余的数据结构,咱们能够在队列的finalizer函数中去释放context data。下面将给一个例子。

 

(6)为队列提供一个clean up 函数。

当咱们建立串行调度队列以后,咱们可让其和一个finalizer函数相连用来清理队列中须要清理的数据。咱们可使用dispatch_set_finalizer_f函数去设置一个函数,当队列的引用计数为0时会去自动的调用。使用此函数去清理和队列相关联的context data,当context 指针不会NULL时,此函数就会调用。

shows a custom finalizer function and a function that creates a queue and installs that finalizer.   
The queue uses the finalizer function to release the data stored in the queue’s context pointer.   
(The myInitializeDataContextFunction and myCleanUpDataContextFunction functions referenced from the code are custom functions that   
you would provide to initialize and clean up the contents of the data structure itself.)   
The context pointer passed to the finalizer function contains the data object associated with the queue. 

void myFinalizerFunction(void *context)  
{  
    MyDataContext* theData = (MyDataContext*)context;  
    // Clean up the contents of the structure  
    myCleanUpDataContextFunction(theData);  
    // Now release the structure itself.  
    free(theData);  
}
  
dispatch_queue_t createMyQueue()  
{  
    MyDataContext* data = (MyDataContext*)             malloc(sizeof(MyDataContext));  
    myInitializeDataContextFunction(data);  
    // Create the queue and set the context data.  
    dispatch_queue_t serialQueue = dispatch_queue_create("com.example.CriticalTaskQueue", NULL);  

    if (serialQueue)  
    {  
        dispatch_set_context(serialQueue, data);  
        dispatch_set_finalizer_f(serialQueue, &myFinalizerFunction);  
    }  
return serialQueue;  
} 

 

五、在队列中添加一个任务

(1)有两种方式在队列中添加一个任务,同步或者异步。尽量使用dispatch_asyncdispatch_async_f 函数去执行,比同步的要首选。当咱们向队列中添加一个块对象或者函数时,咱们没有方法去知道此代码什么时间执行。

使用异步不会去阻塞主线程。

虽然尽量异步添加任务,在有些时候同步的方式去添加一个任务会防止一些同步错误。同步的方式调用函数dispatch_sync和dispatch_sync_f。此函数阻塞主线程的执行,直到指定的任务完成。

dispatch_async(serialQueue, ^{
        [_friendList addObject:@"234"];
        NSLog(@"testSerial, 111 count = %lu",(unsigned long)_friendList.count);
});

 

(2)在任务完成的时候执行completion block

当任务完成时,咱们应用程序须要获得通知,以便去处理结果,在传统的异步编程中,咱们可能会使用回调函数,可是在调度队列中,咱们使用completion block。

 
void average_async(int *data, size_t len,  
 
dispatch_queue_t queue, void (^block)(int)) { // Retain the queue provided by the user to make sure it does not disappear before the completion block can be called.  dispatch_retain(queue); // Do the work on the default concurrent queue and then call the user-provided block with the results. 
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{   int avg = average(data, len);   dispatch_async(queue, ^{ block(avg);});   // Release the user-provided queue when done    dispatch_release(queue); }); }

 

(3)并发的执行循环迭代(loop iterations)

对于for循环,若是每一次的迭代相互都没有影响,能够并发的去执行迭代,使用函数dispatch_apply或者dispatch_apply_f 函数.

和正常的循环同样,函数dispatch_apply或者dispatch_apply_f直到全部的循环迭代完成时才返回。

以下代码:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  
dispatch_apply(count, queue, ^(size_t i) {  
    printf("%un",i);  
}); 

 

(4)在主线程上执行任务

咱们能够经过调用函数dispatch_get_main_queue 去去获得主线程的调度队列。

 

(5)延迟执行任务

dispatch_after 异步

double delayInSeconds = 0.5;
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);

dispatch_after(time, globalParallelQueue, ^{
        [_friendList addObject:@"234"];
        NSLog(@"testParallel, 111 count = %lu",(unsigned long)_friendList.count);
});

 

(6)循环执行任务

int i;  
int count = 10;  
for (i = 0; i < count; i++) {  
   printf("%d  ",i);  
}  

若是每次迭代执行的任务与其它迭代独立无关,并且循环迭代执行顺序也可有可无的话,你能够调用dispatch_apply或dispatch_apply_f函数来替换循环。

这两个函数为每次循环迭代将指定的block或函数提交到queue。当dispatch到并发 queue时,就有可能同时执行多个循环迭代。

用dispatch_apply或dispatch_apply_f时你能够指定串行或并发 queue。并发queue容许同时执行多个循环迭代,而串行queue就没太大必要使用了。

下面代码使用dispatch_apply替换了for循环,你传递的block必须包含一个size_t类型的参数,用来标识当前循环迭代。第一次迭代这个参数值为0,最后一次值为count - 1。

// 得到全局并发queue  
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  
size_t count = 10; dispatch_apply(count, queue, ^(size_t i) { printf("%zd ", i); }); 

打印信息:

1 2 0 3 4 5 6 7 8 9  

能够看出,这些迭代是并发执行的

和普通for循环同样,dispatch_apply和dispatch_apply_f函数也是在全部迭代完成以后才会返回,所以这两个函数会阻塞当前线程,主线程中调用这两个函数必须当心,可能会阻止事件处理循环并没有法响应用户事件。

因此若是循环代码须要必定的时间执行,能够考虑在另外一个线程中调用这两个函数。若是你传递的参数是串行queue,并且正是执行当前代码的queue,就会产生死锁。 同步。

 

(7)暂停、继续队列

咱们可使用dispatch_suspend函数暂停一个queue以阻止它执行block对象;使用dispatch_resume函数继续dispatch queue。

调用dispatch_suspend会增长queue的引用计数,调用dispatch_resume则减小queue的引用计数。当引用计数大于0时,queue就保持挂起状态。所以你必须对应地调用suspend和resume函数

挂起和继续是异步的,并且只在执行block之间(好比在执行一个新的block以前或以后)生效。挂起一个queue不会致使正在执行的block中止。

 

(8)dispath_group的使用

假设有这样一个需求:从网络上下载两张不一样的图片,而后显示到不一样的UIImageView上去,通常能够这样实现

// 根据url获取UIImage
- (UIImage *)imageWithURLString:(NSString *)urlString {
    NSURL *url = [NSURL URLWithString:urlString];
    NSData *data = [NSData dataWithContentsOfURL:url];
    return [UIImage imageWithData:data];
}

- (void)downloadImages {
    // 异步下载图片
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 下载第一张图片
        NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";
        UIImage *image1 = [self imageWithURLString:url1];
        
        // 下载第二张图片
        NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";
        UIImage *image2 = [self imageWithURLString:url2];
        
        // 回到主线程显示图片
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView1.image = image1;
            
            self.imageView2.image = image2;
        });
    });
}

虽然这种方案能够解决问题,但其实两张图片的下载过程并不须要按顺序执行,并发执行它们能够提升执行速度。

有个注意点就是必须等两张图片都下载完毕后才能回到主线程显示图片。Dispatch Group可以在这种状况下帮咱们提高性能。

下面先看看Dispatch Group的用处:

咱们能够使用dispatch_group_async函数将多个任务关联到一个Dispatch Group和相应的queue中,group会并发地同时执行这些任务。并且Dispatch Group能够用来阻塞一个线程, 直到group关联的全部的任务完成执行。

有时候你必须等待任务完成的结果,而后才能继续后面的处理。

 

下面用Dispatch Group优化上面的代码:

 
 

// 根据url获取UIImage
- (UIImage *)imageWithURLString:(NSString *)urlString {
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
// 这里并无自动释放UIImage对象
return [[UIImage alloc] initWithData:data];
}

 
 

- (void)downloadImages {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

// 异步下载图片
dispatch_async(queue, ^{
// 建立一个组
dispatch_group_t group = dispatch_group_create();

__block UIImage *image1 = nil;
__block UIImage *image2 = nil;

// 关联一个任务到group
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 下载第一张图片
NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";
image1 = [self imageWithURLString:url1];
});

// 关联一个任务到group
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 下载第一张图片
NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";
image2 = [self imageWithURLString:url2];
});

// 等待组中的任务执行完毕,回到主线程执行block回调
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            self.imageView1.image = image1;
            self.imageView2.image = image2;
            
            // 千万不要在异步线程中自动释放UIImage,由于当异步线程结束,异步线程的自动释放池也会被销毁,那么UIImage也会被销毁
            
            // 在这里释放图片资源
            [image1 release];
            [image2 release];
        });

// 释放group
dispatch_release(group);
});
}

 

dispatch_group_notify函数用来指定一个额外的block,该block将在group中全部任务完成后执行。

 

小结:详解IOS开发应用之并发Dispatch Queues的内容介绍完了,但愿经过本文的学习能对你有所帮助!

---恢复内容结束---

相关文章
相关标签/搜索