GCD Blocks与Dispatch Queue的使用

本文介绍Block的概念和用途,以及Block与GCD结合在iOS中的并行编程技巧。探讨Block与C语言函数指针的区别,如何在Block中修改外部变量等内容。并通过示例详细解释如何使用DispatchQueue和Semaphore来实现并行任务管理和线程同步。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

block是什么
block是一个C level的语法以及运行时的一个特性,和标准C中的函数(函数指针)类似。用于回调函数的地方。两个对象间的通讯。实现轻量级的“代理”。


blocks和C语言函数指针的区别
Blocks应用 - 阿帕奇 - XGG  XGG
 

如何调用blocks
调用block和C语言函数指针的调用一模一样
Blocks应用 - 阿帕奇 - XGG  XGG
 
Blocks应用 - 阿帕奇 - XGG  XGG
 
如何在 block 中修改外部变量?????
考虑到 block 的目的是为了支持并行编程,对于普通的 local 变量,我们就不能在 block 里面随意修改(原因很简单,block 可以被多个线程并行运行,会有 问题 的),而且如果你在 block 中修改普通的 local 变量,编译器也会报错。那么该如何修改外部变量呢?有两种办法,第一种是可以修改 static 全局变量;第二种是可以修改用新关键字 __block 修饰的变量
__block关键字
一个Block的内部是可以引用自身作用域外的变量的,包括static变量,extern变量或自由变量(定义一个变量的时候,如果不加存储修饰符,默认情况下就是自由变量auto,auto变量保存在stack中的,除了auto之外还存在register,static等存储修饰符),
对于局部变量,在block中是只读的。在引入block的同时,还引入了一种特殊的关键字__block,用此声明一个局部变量可以被函数块修改。


实例:
void(^aBlock)(void) = 0;          // 声明一个block
    aBlock = ^(void){                 // 给block赋值
        NSLog(@"this is a block.");
    };
    aBlock();                            // 执行block


上面我们介绍了 block 及其基本用法,但还没有涉及并行编程。 block 与 Dispatch Queue 分发队列结合起来使用,是  iOS  中并行编程的利器。

    NSAutoreleasePool *pool = [[NSAutoreleasePoolalloc]init];

    

    // 创建一个串行分发队列

   dispatch_queue_t queue =dispatch_queue_create("studyBlocks",NULL);

    

    // 将一个 block任务加入到其中并行运行.这样 block就会在新的线程中运行,直到结束返回主线程

   //加入 dispatch_queue中的 block 必须是无参数也无返回值的

    dispatch_async(queue, ^(void){

        int sum = 0;

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

            sum += i;

        }

        NSLog(@"sum:%d",sum);

    });

   dispatch_release(queue);

    [pool drain];

1、dispatch_queue_t 类型 的定义如下:
typedef void (^dispatch_block_t)( void);
这意味着加入 dispatch_queue 中的 block 必须是无参数也无返回值的
2、dispatch_queue_create 函数 的定义如下:
dispatch_queue_t  dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
函数 创建分发队列 dispatch_queue。 带有两个参数:一个用于标识 dispatch_queue 的字符串;一个是保留的 dispatch_queue 属性,将其设置为 NULL 即可。
3、也可使用函数 dispatch_queue_t dispatch_get_global_queue(long priority, unsigned long flags);
来获得全局的 dispatch_queue,参数 priority 表示优先级,值得注意的是:我们不能修改该函数返回的 dispatch_queue。例如:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{

[[[selfcaptureManager]session]startRunning];

});

4、dispatch_async 函数的定义如下:
void  dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
该函数将一个 block 加入一个 dispatch_queue,这个 block 会再其后得到调度时,并行运行。
相应的 dispatch_sync 函数就是同步执行了,一般很少用到。比如上面的代码如果我们修改为 dispatch_sync,那么就无需编写 flag 同步代码了。
5、dispatch_block_t 类型
dispatch_block_t

dispatch_queue 的运作机制及线程间同步
我们可以将许多 blocks 用 dispatch_async 函数提交到到 dispatch_queue 串行运行。这些 blocks 是按照 FIFO(先入先出)规则调度的,也就是说,先加入的先执行,后加入的一定后执行,但在某一个时刻,可能有多个 block 同时在执行
在上面的例子中,我们的主线程一直在轮询 flag 以便知晓 block 线程是否执行完毕,这样做的效率是很低的,严重浪费 CPU 资源。我们可以使用一些通信机制来解决这个问题,如:semaphore(信号量)。 semaphore 的原理很简单,就是生产-消费模式,必须生产一些资源才能消费,没有资源的时候,那我就啥也不干,直到资源就绪。

    NSAutoreleasePool * pool = [[NSAutoreleasePoolalloc]init];

   initData();

    

   // Create a semaphore with 0 resource

    __blockdispatch_semaphore_t sem = dispatch_semaphore_create(0);

    

   // create dispatch semaphore

    dispatch_queue_t queue = dispatch_queue_create("StudyBlocks",NULL);     dispatch_async(queue, ^(void) {

        int sum = 0;

        for(int i =0; i < Length; i++)

            sum += data;

        NSLog(@" >> Sum: %d", sum);

        // signal the semaphore: add 1 resource

        dispatch_semaphore_signal(sem);

    });

    

    // wait for the semaphore: wait until resource is ready.

    dispatch_semaphore_wait(sem,DISPATCH_TIME_FOREVER);

    

    dispatch_release(sem);

    dispatch_release(queue);

    

    [pool drain];

1、dispatch_semaphore_create函数
此函数用于创建一个 __block semaphore,这里将其资源初始值设置为 0 (不能少于 0),表示任务还没有完成,没有资源可用主线程不要做事情。
2、dispatch_semaphore_signal函数
该函数 增加 semaphore 计数(可理解为资源数),表明任务完成,有资源可用主线程可以做事情了。
3、dispatch_semaphore_wait函数
主线程中的 dispatch_semaphore_wait 就是 减少 semaphore 的计数,如果资源数少于 0,则表明资源还可不得,我得按照FIFO(先等先得)的规则等待资源就绪,一旦资源就绪并且得到调度了,我再执行。

下面我们来看一个按照 FIFO 顺序执行并用 semaphore 同步的例子:先将数组求和再依次减去数组。

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

   initData();

    

    __block int sum = 0;

    

   // Create a semaphore with 0 resource

    __block dispatch_semaphore_t sem = dispatch_semaphore_create(0);

   __blockdispatch_semaphore_t taskSem =dispatch_semaphore_create(0);

    

   // create dispatch semaphore

    dispatch_queue_t queue = dispatch_queue_create("StudyBlocks",NULL);

    

    dispatch_block_t task1 = ^(void) {

        int s = 0;

        for (int i = 0; i < Length; i++)

            s += data;

        sum = s;

        

       NSLog(@" >> after add: %d", sum);

        

       dispatch_semaphore_signal(taskSem);

    };

    

    dispatch_block_t task2 = ^(void) {

       dispatch_semaphore_wait(taskSem,DISPATCH_TIME_FOREVER);

        

        int s = sum;

        for (int i = 0; i < Length; i++)

            s -= data;

        sum = s;

        

       NSLog(@" >> after subtract: %d", sum);

       dispatch_semaphore_signal(sem);

    };

    

    dispatch_async(queue, task1);

    dispatch_async(queue, task2);

    

   // wait for the semaphore: wait until resource is ready.

   dispatch_semaphore_wait(sem,DISPATCH_TIME_FOREVER);

    

   dispatch_release(taskSem);

   dispatch_release(sem);

   dispatch_release(queue);

    

    [pool drain];


在上面的代码中,我们利用了 dispatch_queue 的 FIFO 特性,确保 task1 先于 task2 执行,而 task2 必须等待直到 task1 执行完毕才开始干正事,主线程又必须等待 task2 才能干正事。 这样我们就可以保证先求和,再相减,然后再让主线程运行结束这个顺序。


使用 dispatch_apply 进行并发迭代:

对于上面的求和操作,我们也可以使用 dispatch_apply 来简化代码的编写.

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    

   initData();

    

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

    

    __block int sum = 0;

    __block int *pArray = data;

    

   // iterations

    //

    dispatch_apply(Length, queue, ^(size_t i) {

        sum += pArray;

    });

    

   NSLog(@" >> sum: %d", sum);

    

   dispatch_release(queue);

    

    [pool drain];


注意,这里使用了全局 dispatch_queue。
dispatch_apply 的定义如下:
dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));
参数 iterations 表示迭代的次数,void (^block)(size_t) 是 block 循环体。这么做与 for 循环相比有什么好处呢?答案是:并行,这里的求和是并行的,并不是按照顺序依次执行求和的。

dispatch group

我们可以将完成一组相关任务的 block 添加到一个 dispatch group 中去,这样可以在 group 中所有 block 任务都完成之后,再做其他事情。比如 6 中的示例也可以使用 dispatch group 实现:

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    

   initData();

    

    __block int sum = 0;

    

   // Create a semaphore with 0 resource

    //

    __block dispatch_semaphore_t taskSem = dispatch_semaphore_create(0);

    

   // create dispatch semaphore

    //

    dispatch_queue_t queue = dispatch_queue_create("StudyBlocks",NULL);

   dispatch_group_t group =dispatch_group_create();

    

    dispatch_block_t task1 = ^(void) {

        int s = 0;

        for (int i = 0; i < Length; i++)

            s += data;

        sum = s;

        

        NSLog(@" >> after add: %d", sum);

        

        dispatch_semaphore_signal(taskSem);

    };

    

    dispatch_block_t task2 = ^(void) {

        dispatch_semaphore_wait(taskSem,DISPATCH_TIME_FOREVER);

        

        int s = sum;

        for (int i = 0; i < Length; i++)

            s -= data;

        sum = s;

        

        NSLog(@" >> after subtract: %d", sum);

    };

    

   // Fork

    dispatch_group_async(group, queue, task1);

    dispatch_group_async(group, queue, task2);

    

   // Join

   dispatch_group_wait(group,DISPATCH_TIME_FOREVER);

    

   dispatch_release(taskSem);

   dispatch_release(queue);

   dispatch_release(group);

    

    [pool drain];


在上面的代码中,我们使用 dispatch_group_create创建一个 dispatch_group_t,然后使用语句:dispatch_group_async(group, queue, task1)将 block 任务加入队列中,并与组关联,这样我们就可以使用 dispatch_group_wait(group, DISPATCH_TIME_FOREVER)来等待组中所有的 block 任务完成再继续执行

至此我们了解了 dispatch queue 以及 block 并行编程相关基本知识,开始在项目中运用它们吧。


使用GCD需要注意的地方:
1)更新UI只能在主线程中执行。
2)使用线程不安全的对象要注意
3)创建的dispatch_queue_t要release掉,否则内存会报错。
//==============================================
GCD之dispatch queues:
1、一个dispatch queue是一个像类型的结构,管理着你提交给它的tasks。所有的dispatch queue都是FIFO(先进先出)的数据结构。因此,你添加到queue的task总是以它们添加的顺序启动。GCD自动为你提供了一些dispatch queues,但是你可以创建自己的queue。下面列出了你可以使用的dispatch queue的类型:
1)Searial类型:也称为private dispatch queue,按照添加的顺序在一个时间执行一个task。当前执行的task运行于一个单独的线程(不同的task可能有不同的线程)。Serial queue经常用作同步访问到一个特定的资源。 你可以创建任意多的serial queue,并且每个queue可以和其他queue同时操作。换句话说,如果逆创建了4个serial queue,每个queue在同一时刻只能执行一个task,但是4个queue可以同时执行4个task。
2)Concurrent类型:也称为global dispatch queue,可以同时执行多个task,但是task仍然是按照添加顺序开始执行。同时执行的task在各自的线程中执行。同时执行的task数量在任意时刻都是不同的,这取决于系统条件。你不能自己创建concurrent类型的queue。取而代之的,有3个globla concurrent queue供你使用。使用dispatch_get_global_queue函数获得。有3个不同的优先级.
3)Main dispatch queue:这个queue是全局的serial queue,代表主线程。这个queue工作于应用的run loop。

使用dispatch queue需要注意的:
1)系统决定了同时执行task的总数量。例如逆创建了100个task在100个queue里,它们可能不能同时执行(除非系统又100个有效的cores)。
2)queue的priority影响系统计算启动哪个task。
3)被加入到queue的task必须随时准备好运行。
4)private dispatch queue是reference-counted 对象。符合内存管理规则。

2、QUeue-Related技术
1)Dispatch Groups:用来监视一组block对象的完成。你可以同步或异步地监视。Groups提供了一个有用的同步机制,让代码可以基于其他task的完成。
2)Dispatch semaphores:一个Dispatch semaphores和传统的semaphore类似,但是有更高的效率。
3)Dispatch Sources:一个dispatch source生成notification来响应特定类型的系统事件。你可以使用dispatch sources来监视事件,例如process notification、信号和descriptor events amony others。当一个事件发生时,dispatch source异步提交你的task code到特定的dispatch queue来处理。

3、使用blocks实现tasks:
4、创建和管理Dispatch Queue:
在你添加你的task到queue之前,你需要决定你要使用哪种类型的queue。
1)获得Global Concurrent Dispatch Queues:有3个Global queue,它们只是有不同的优先级。你不需要创建它们。使用dispatch_get_global_queue函数获得。
dispatch_queue_t aQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);//DISPATCH_QUEUE_PRIORITY_HIGH、DISPATCH_QUEUE_PRIORITY_LOW
注意:第2个参数为保留参数,现在只需要使用0即可。
这些global queue object你不需要retain或release(应该因为它们是全局的单例模式,retain和release都不会起作用)。

2)创建Serial Dispatch Queues:
dispatch_queue_t queue;
queue=dispatch_queue_create("com.example.MyQueue",NULL);
//第一个参数为queue name,第二个参数为queue attributes,是为将来保留使用的,现在使用NULL就可以了。

3)获得常用的Queue:
a)使用dispatch_get_current_queue函数来获得当前queue,一般用来作为debug使用。在一个block内部调用这个函数返回block被提交到的queue。在block外部调用这个函数返回默认的concurrent queue。
b)使用dispatch_get_main_queue函数返回主线程queue。
c)使用dispatch_get_global_queue函数来获得任意共享的concurrent queue。

4)Dispatch Queue的内存管理:
使用dispatch_retain和dispatch_release来管理。
即使你使用了垃圾收集系统,你也仍然需要retain和release你的dispatch queue和其他dispatch对象。GCD不支持垃圾收集模型。

5)存储当前context information with a Queue:
所有的dispatch对象(包括dispatch queues)允许你有一个相关的自定义的context数据。使用dispatch_set_context和dispatch_get_context函数来设置和读取。系统不使用你的context data,你需要自己allocate和deallocate它们。
对于queues,你可以使用context data来存储一个指针到一个Objective-C对象或其他数据结构来帮助逆识别queue或其他目的。你可以使用queue的销毁函数来deallocate你的context data。

6)提供queue的清理函数:
使用dispatch_set_finalizier_f函数来设置。相当于dealloc函数。例子:
void myFinalizerFunction(void *context){
  MyDataContext *theData=(MyDataContext *)context;
  //Clean up
  myCleanUpdataContextFunction(theData);
  //release 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_create_queue("com.example.CriticalTaskQueue",NULL);
  if(serialQueue){
    dispatch_set_context(serialQueue,data);
    dispatch_set_finalizer_f(serialQueue,&myFinalizerFunction);
  }
  return serialQueue;
}

7)添加tasks到queue:
a)添加一个单独的task到queue:
有两种方法添加:同步或异步。
异步使用dispatch_async和dispatch_async_f函数,一般都优先使用异步。
当你添加一个block对象到queue中后,你无法得知何时代码会执行。所以异步的添加block可以让你在添加的线程中继续做其他的工作。尤其是在主线程中操作时。
使用dispatch_sync和dispatch_sync_f函数来同步添加task。这会阻塞线程直到block执行完。

b)在一个task完成后执行一个completion block:
c)同时迭代循环:
例如,假设逆又一个for 循环如下:
for (i=0;i<count;i++){
  printf("%u\n",i)
}
如果每个迭代的工作都独立于其他迭代,而且迭代完成的顺序也不重要,你可以替换loop为 dispatch_apply或dispatch_apply_f函数。这个函数为每个单独的迭代循环 提交特定的block或函数到一个queue中,当dispatch到一个concurrent global queue中后,就有可能同时执行循环迭代。
注意:像一个常规的for loop,dispatch_apply和dispatch_apply_f函数会在所有的循环迭代完成后才返回。因此你应该小心。避免造成死锁(deadlock)和阻塞主线程。
例子:
dispatch_apply(count,queue,^(size_t i){
  printf("%u\n",i);
});//queue一般为一个global_queue

d)在主线程执行任务
使用dispatch_get_main_queue获得主线程,然后将其传递给dispatch_sync

8)挂起和回复Queues
你可以通过挂起来临时阻止一个queue执行block对象,使用dispatch_suspend函数和dispatch_resume函数。
调用dispatch_suspend使queue的suspension reference count加1,使用dispatch_resume函数减1.相当于retain和release。当reference count大于0时,queue就保持挂起状态。因此,你必须平衡使用suspend和resume。
重要通知:suspend和resume是异步的,而且只在不同的blocks之间生效。挂起一个queue不能使已经运行的block停止运行。

9)使用Dispatch Semaphores来调节有限资源的使用:
如果你的task要访问一些有限的(finite)资源,你可能就想使用一个dispatch semaphore来调整同时访问资源的task的数量。一个dispatch semaphore就像一个常规的semaphore一样工作。当资源可用时,它使用比获得一个传统的系统semaphore更少的时间来获得一个dispatch semaphore。这是因为GCD不需要call down into kernal for this particular case.
使用dispatch semaphore的要点:
a)当你使用dispatch_semaphore_create函数来创建semaphore时,你可以指定一个正整数值来指示resource可用的number值。
b)在每个task中,调用dispatch_semaphore_wait来等待semaphore
c)当wait调用返回时,获得资源并工作。
d)当完成后,释放掉资源并标志semaphore,使用dispatch_semaphore_signal函数。
例子:
//Create the semaphore,specifying the initial pool size
dispatch_semaphore_t fd_sema=dispatch_semaphore_create(getdtablesize()/2);

//wait for a free file descriptor
dispatch_semaphore_wait(fd_sema,DISPATCH_TIME_FOREVER);
fd=open("/etc/services",0_RDONLY);

//release the file descriptor when done
close(fd);
dispatch_semaphore_signal(fd_sema);

10)Waiting on Groups of Queued Tasks:
Dispatch groups用来在其他task执行完之前来堵塞一个线程。
使用dispatch_group_async函数来代替dispatch_async。
使用dispatch_group_wait函数来等待。
例子:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
 
// Add a task to the group
dispatch_group_async(group, queue, ^{
   // Some asynchronous work
});
 
// Do some other work while the tasks execute.
 
// When you cannot make any more forward progress,
// wait on the group to block the current thread.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
 
// Release the group when it is no longer needed.
dispatch_release(group);

11)Dispatch Queues and Thread Safety线程安全
a)Dispatch Queues本身是线程安全的。换句话说,你可以在任意线程中提交task到dispatch queue。
b)不要使用dispatch_sync函数提交task到当前的queue中。这会造成死锁。
c)避免把使用带锁的任务提交到dispatch queue中,虽然你的task使用锁是安全的,但是当你获得一个lock时,你正在冒险将整个queue堵塞。类似地,对于concurrent queue,等待一个锁可能阻止其他任务执行。如果逆需要在你的代码中同步锁,那就使用一个serial dispatch queue来代替锁。
d)虽然你可以获得运行task的后台线程的信息,但是最好还是不要这么做。

//========================================================================
Grand Central Dispatch(GCD) Reference:只列出了常用的函数
1、创建和管理Queue
dispatch_get_global_queue
dispatch_get_main_queue
dispatch_queue_create
dispatch_get_current_queue
dispatch_queue_get_label

2、添加task到queue
dispatch_async、dispatch_async_f
dispatch_sync、dispatch_sync_f
dispatch_after
dispatch_apply
dispatch_once

3、使用Dispatch groups
dispatch_group_async
dispatch_group_create
dispatch_group_enter
dispatch_group_leave
dispatch_group_notify
dispatch_group_wait

4、管理Dispatch对象:
dispatch_debug
dispatch_get_context
dispatch_set_context
dispatch_release
dispatch_retain
dispatch_suspend
dispatch_resume
dispatch_set_finalizer_f

5、使用Semaphores:
dispatch_semaphore_create
dispatch_semaphore_signal
dispatch_semaphore_wait

6、类型
1)dispatch_block_t :添加到queue中的block原型
typedef void (^dispatch_block_t) (void);

2)dispatch_group_t:添加到一个queue的一组block
typedef struct dispatch_group_s *dispatch_group_t;

3)dispatch_object_t:一个多态类型,GCD dispatch object函数使用的。
typedef union {
 ....
} dispatch_object_t __attribute__((transparent_union));

4)dispatch_once_t:一个predicate,dispatch_once函数使用的
typedef long dispatch_once_t;

5)dispatch_queue_t:
typedef struct dispatch_queue_s *dispatch_queue_t;

7、常量
dispatch_queue_priority_t

#define DISPATCH_QUEUE_PRIORITY_HIGH        2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT     0
#define DISPATCH_QUEUE_PRIORITY_LOW         (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND  INT16_MIN

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值