http://blog.xcodev.com/blog/2013/11/04/gcd-intro/

Dispatch Queue

Dispatch Queue是一个任务执行队列,可以让你异步或同步地执行多个Block或函数。Dispatch Queue是FIFO的,即先入队的任务总会先执行。目前有三种类型的Dispath Queue:

  • 串行队列(Serial dispatch queue)
  • 并发队列(Concurrent dispatch queue)
  • 主队列(Main dispatch queue)

串行队列

串行队列一次只能处理一个任务,可以由用户调用dispatch_queue_create创建:

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

dispatch_queue_create第一个参数是串行队列标识,一般用反转域名的格式表示以防冲突;第二个参数是queue的类型,设为NULL时默认是DISPATCH_QUEUE_SERIAL,将创建串行队列,在必要情况下,你可以将其设置为DISPATCH_QUEUE_CONCURRENT来创建自定义并行队列。

并行队列

并行队列可以同时处理多个任务,在不得以的情况下可以用dispatch_queue_create创建,但一般我们都要用系统预定义的并行队列,即全局队列(Global Concurrent Dispatch Queues)。目前系统预定义了四个不同运行优先级的全局队列,我们可以通过dispatch_get_global_queue来获取它们。

dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_get_global_queue第一个参数是队列的优先级,分别对应四个全局队列:

  • DISPATCH_QUEUE_PRIORITY_HIGH
  • DISPATCH_QUEUE_PRIORITY_DEFAULT
  • DISPATCH_QUEUE_PRIORITY_LOW
  • DISPATCH_QUEUE_PRIORITY_BACKGROUND

dispatch_get_global_queue中第二个参数目前系统保留,请设置为0即可。

主队列

主队列是一个特殊的队列,它是系统预定义的运行在主线程的一个Dispatch Queue。可以通过dispatch_get_main_queue来获取唯一的主队列。主队列一般运行一些需要与主线程同步的一些短时任务。

dispatch_queue_t mainQueue = dispatch_get_main_queue();

获取当前队列

你可以通过dispatch_get_current_queue获取运行时的队列:

dispatch_queue_t currentQueue = dispatch_get_current_queue();

如果在队列执行任务中调用,返回执行此任务的队列;如果在主线程中调用,将返回主队列;如果在一般线程(非主线程线程非队列执行任务)中调用,返回DISPATCH_QUEUE_PRIORITY_DEFAULT全局队列。

在队列中运行任务

你可以随时向一个队列中添加一个新任务,只需要调用一下dispatch_async即可:

dispatch_async(aQueue, ^{
//Do some work;
});

dispatch_async中的任务是异步执行的,就是说dispatch_async添加任务到执行队列后会立刻返回,而不会等待任务执行完成。然而,必要的话,你也可以调用dispatch_sync来同步的执行一个任务:

dispatch_sync(aQueue, ^{
//Do some work;
});

dispatch_sync会阻塞当前线程直到提交的任务完全执行完毕。

Dispatch Queue的内存管理

除了系统预定义的Dispatch Queue,我们自定义的Dispatch Queue需要手动的管理它的内存。dispatch_retaindispatch_release这两个函数可以控制Dispatch Queue的引用计数(同时可以控制后面会讲到的Dispatch Group和Dispatch Source的引用计数)。当Dispatch Queue引用计数变为0后,就会调用finalizer,finalizer是Dispatch Queue销毁前调用的函数,用来清理Dispatch Queue的相关资源。可以用dispatch_set_finalizer_f函数来设置Dispatch Queue的finalizer,这个函数同时可以设置Dispatch Group和Dispatch Source的销毁函数(后面会讲到)。

void dispatch_set_finalizer_f(dispatch_object_t object, dispatch_function_t finalizer);

Dispatch Queue的上下文环境数据

我们可以为每个Dispatch Queue设置一个自定义的上下文环境数据,调用dispatch_set_context来实现。同时我们也可以用dispatch_get_context获取这个上下文环境数据,这个函数同时可以设置Dispatch Group和Dispatch Source的上下文环境数据(后面会讲到)。

void dispatch_set_context(dispatch_object_t object,void *context);
void * dispatch_get_context(dispatch_object_t object);

注意Dispatch Queue并不保证这个context不会释放,不会对它进行内存管理控制。我们需要自行管理context的内存分配和释放。一般我们非配内存设置context后,可以在finalizer里释放context占有的内存。

并行执行循环

在编程过程中,我们经常会用到for循环,而且for循环要做很多相关的任务。比如:

for (i = 0; i < count; i++) {
//do a lot of work here.
doSomething(i);
}

如果for循环中处理的任务是可并发的,显然放到一个线程中处理是很慢的,GCD提供两个函数dispatch_applydispatch_apply_fdispatch_apply是用于Block的,而dispatch_apply_f可以用于c函数,它们可以替代可并发的for循环,来并行的运行而提高执行效率。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(count, queue, ^(size_t i) {
//do a lot of work here.
doSomething(i);
});

Dispatch Group

有时候我们进行下一步操作,而这个操作需要等待几个任务处理完毕后才能继续,这时我们就需要用的Dispatch Group(类似thread join)。我们可以把若干个任务放到一个Dispatch Group中:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
// Some asynchronous work
});

dispatch_group_asyncdispatch_async一样,会把任务放到queue中执行,不过它比dispatch_async多做了一步操作就是把这个任务和group相关联。

把一些任务放到Dispatch Group后,我们就可以调用dispatch_group_wait来等待这些任务完成。若任务已经全部完成或为空,则直接返回,否则等待所有任务完成后返回。注意:返回后group会清空。

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// Do some work after.
dispatch_release(group);

Dispatch信号量

很多程序设计都设计到信号量,生产者-消费者模型在多线程编程中会频繁的使用。GCD提供了自己的一套信号量机制。

dispatch_semaphore_t sema = dispatch_semaphore_create(RESOURCE_SIZE);
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
//do some work here.
dispatch_semaphore_signal(sema);

dispatch_semaphore_wait用来获取信号量,若信号量为0,则等待直到信号量大于0。在处理任务结束后,应释放相关资源并调用dispatch_semaphore_signal使信号量增加1个。

Dispatch Source

Dispatch Source是GCD中监听一些系统事件的有个Dispatch对象,它包括定时器、文件监听、进程监听、Mach port监听等类型。

可以通过dispatch_source_create创建一个Dispatch Source:

dispatch_source_t dispatch_source_create(
dispatch_source_type_t type,
uintptr_t handle,
unsigned long mask,
dispatch_queue_t queue);

这里可以指定Dispatch Source的类型,type可以为文件读或写、进程监听等。handle为监听对象的句柄,如果是文件就是文件描述符,如果是进程就是进程ID。mask用来指定一些想要监听的事件,它的意义取决于typequeue指定事件处理的任务队列。

创建好Dispatch Source后,我们要为Dispatch Source设置一个事件处理模块。可以用dispatch_source_set_event_handlerdispatch_source_set_event_handler_f来设置:

void dispatch_source_set_event_handler(
dispatch_source_t source,
dispatch_block_t handler);

设置好Dispatch Source后就可以调用dispatch_resume来启动监听。如果相应的事件发生就会触发事件处理模块。

同时我们也可以设置一个取消处理模块:

dispatch_source_set_cancel_handler(mySource, ^{
close(fd); // Close a file descriptor opened earlier.
});

取消处理模块会在Dispatch Source取消时调用。

下面介绍一下主要的Dispatch Source类型和示例代码。

定时器

定时器Dispatch Source可以每隔一个固定的时间处理一下任务。

dispatch_source_t CreateDispatchTimer(uint64_t interval,
uint64_t leeway,
dispatch_queue_t queue,
dispatch_block_t block)
{
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
0, 0, queue);
if (timer)
{
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
} void MyCreateTimer()
{
dispatch_source_t aTimer = CreateDispatchTimer(30ull * NSEC_PER_SEC,
1ull * NSEC_PER_SEC,
dispatch_get_main_queue(),
^{ MyPeriodicTask(); }); // Store it somewhere for later use.
if (aTimer)
{
MyStoreTimer(aTimer);
}
}

dispatch_after和dispatch_after_f

有时候我们只想处理一次延迟任务,可以用dispatch_after和dispatch_after_f

void dispatch_after(
dispatch_time_t when,
dispatch_queue_t queue,
dispatch_block_t block);

监听文件事件

监听文件事件分好几个类型,有读、写、属性的监听。

读取文件

dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, queue);
dispatch_source_set_event_handler(source, ^{
// Get some data from the source variable, which is captured
// from the parent context.
size_t estimated = dispatch_source_get_data(source);
// Continue reading the descriptor...
});
dispatch_resume(source);

写文件

dispatch_source_t writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE,
fd, 0, queue);
if (!writeSource)
{
close(fd);
return NULL;
} dispatch_source_set_event_handler(writeSource, ^{
size_t bufferSize = MyGetDataSize();
void* buffer = malloc(bufferSize); size_t actual = MyGetData(buffer, bufferSize);
write(fd, buffer, actual); free(buffer); // Cancel and release the dispatch source when done.
dispatch_source_cancel(writeSource);
});

监听文件属性

dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE,
fd, DISPATCH_VNODE_RENAME, queue);
if (source)
{
// Copy the filename for later use.
int length = strlen(filename);
char* newString = (char*)malloc(length + 1);
newString = strcpy(newString, filename);
dispatch_set_context(source, newString); // Install the event handler to process the name change
dispatch_source_set_event_handler(source, ^{
const char* oldFilename = (char*)dispatch_get_context(source);
MyUpdateFileName(oldFilename, fd);
}); // Install a cancellation handler to free the descriptor
// and the stored string.
dispatch_source_set_cancel_handler(source, ^{
char* fileStr = (char*)dispatch_get_context(source);
free(fileStr);
close(fd);
}); // Start processing events.
dispatch_resume(source);
}
else
close(fd);

监听进程事件

DISPATCH_PROC_EXIT是一个监听进程退出的类型。

dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC,
parentPID, DISPATCH_PROC_EXIT, queue);
if (source)
{
dispatch_source_set_event_handler(source, ^{
MySetAppExitFlag();
dispatch_source_cancel(source);
dispatch_release(source);
});
dispatch_resume(source);
}

监听中断信号

dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGHUP, 0, queue);
if (source)
{
dispatch_source_set_event_handler(source, ^{
MyProcessSIGHUP();
}); // Start processing signals
dispatch_resume(source);
}

参考文献

【转】并发编程之GCD的更多相关文章

  1. [转载]并发编程之Operation Queue和GCD

    并发编程之Operation Queue http://www.cocoachina.com/applenews/devnews/2013/1210/7506.html 随着移动设备的更新换代,移动设 ...

  2. Java并发编程之CAS

    CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术.简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替 ...

  3. 并发编程之wait()、notify()

    前面的并发编程之volatile中我们用程序模拟了一个场景:在main方法中开启两个线程,其中一个线程t1往list里循环添加元素,另一个线程t2监听list中的size,当size等于5时,t2线程 ...

  4. 并发编程之 Exchanger 源码分析

    前言 JUC 包中除了 CountDownLatch, CyclicBarrier, Semaphore, 还有一个重要的工具,只不过相对而言使用的不多,什么呢? Exchange -- 交换器.用于 ...

  5. 并发编程之 Condition 源码分析

    前言 Condition 是 Lock 的伴侣,至于如何使用,我们之前也写了一些文章来说,例如 使用 ReentrantLock 和 Condition 实现一个阻塞队列,并发编程之 Java 三把锁 ...

  6. python并发编程之Queue线程、进程、协程通信(五)

    单线程.多线程之间.进程之间.协程之间很多时候需要协同完成工作,这个时候它们需要进行通讯.或者说为了解耦,普遍采用Queue,生产消费模式. 系列文章 python并发编程之threading线程(一 ...

  7. python并发编程之gevent协程(四)

    协程的含义就不再提,在py2和py3的早期版本中,python协程的主流实现方法是使用gevent模块.由于协程对于操作系统是无感知的,所以其切换需要程序员自己去完成. 系列文章 python并发编程 ...

  8. python并发编程之asyncio协程(三)

    协程实现了在单线程下的并发,每个协程共享线程的几乎所有的资源,除了协程自己私有的上下文栈:协程的切换属于程序级别的切换,对于操作系统来说是无感知的,因此切换速度更快.开销更小.效率更高,在有多IO操作 ...

  9. python并发编程之multiprocessing进程(二)

    python的multiprocessing模块是用来创建多进程的,下面对multiprocessing总结一下使用记录. 系列文章 python并发编程之threading线程(一) python并 ...

随机推荐

  1. linux内核下载

    01最新版:https://www.kernel.org/ 02老旧版:https://www.kernel.org/pub/linux/kernel/v3.x/ ------------------ ...

  2. OLAP、OLTP的介绍和比较

    OLTP与OLAP的介绍 数据处理大致可以分成两大类:联机事务处理OLTP(on-line transaction processing).联机分析处理OLAP(On-Line Analytical ...

  3. 启动PL/SQL Developer 报字符编码不一致错误 Database character set (AL32UTF8) and Client character set (ZHS16GBK) are different. Character set conversion may cause unexpected results. Note: you can set the client

    今天写hibernate时候遇到一些异常 代码: 出现异常情况: 出现以上原因是Session关闭 如果不是使用的SessionFactory.getSession()来获得Session. 而是使用 ...

  4. uva12230Crossing Rivers

    数学期望. 过每条河的时间的可能在[L/v,3*L/v]间均匀分布,数学期望为2*L/v. 然后在加上在陆上走的时间. #include<cstdio> #include<algor ...

  5. VB VS2003获取当前进程用户登录

    Page.User.Identity.Name获取当前进程用户名称,VS03才可以用

  6. bzoj1355: [Baltic2009]Radio Transmission

    将原串看成是循环节的后缀加上若干个循环节,那么考虑每种情况都会发现n-next[n]就是最小循环节.(一开始总输出n...然后发现build_next连调用都没有,%%% #include<cs ...

  7. UVa 10285 Longest Run on a Snowboard【记忆化搜索】

    题意:和最长滑雪路径一样, #include<iostream> #include<cstdio> #include<cstring> #include <c ...

  8. Codeforces Round #270

    A 题意:给出一个数n,求满足a+b=n,且a+b均为合数的a,b 方法一:可以直接枚举i,n-i,判断a,n-i是否为合数 #include<iostream> #include< ...

  9. sql2005主从数据库同步配置

    网站规模到了一定程度之后,该分的也分了,该优化的也做了优化,但是还是不能满足业务上对性能的要求:这时候我们可以考虑使用主从库.主从库是两台服务器上的两个数据库,主库以最快的速度做增删改操作+最新数据的 ...

  10. Android异常之 unable to write jarlist cache file

    异常: android开发调试时候不能运行,出现  unable to write jarlist cache file  错误. 解决方法: 1.找到appcompt文件夹如下的位置.