GCD Basic

Dispatch queues

Dispatch queue有两种类型,一种是线性queue,线性queue一个一个的执行queue上的任务,如果当前任务正在执行,queue一直等待在那里直它完成才会开始新的任务;另一种是并发queue,它并不等待正在执行的任务的完成,只要有资源让它开启任务,它就会分配queue上任务的执行。

提交到线性queue上的Blocks是按先进先出(FIFO)的顺序执行的。但要注意的是,不同线性queue上的Block的执行是相互独立的,所以互相之间是可能并发执行的。提交到并发queue上的Block也是按FIFO的顺序出队列的,因为并发queue并不等待任务的完成,所以只要有资源可运行并发queue就会以FIFO的顺序开启queue上的block的执行,block之间可能是并发的。

当你把任务提交到并发queue上,可并发执行的任务数是由系统的当前状态决定的。iOS或MacOS根据当前的状态(比如并发queue上的任务数、CPU核数和CPU的使用情况)来决定并发数。
如果你的任务应该有序地执行,或者不应该并发的执行,那么你应该用线性queue。

获取 Dispatch Queues

获取Dispatch queue有两种方法

  1. dispatch_queue_create
  2. dispatch_get_main_queue/dispatch_get_global_queue

dispatch_queue_create

用此方法创建新的dispatch queue。

dispatch_queue_t serialDispatchQueue = dispatch_queue_create("myUniqueIdentity", NULL);

上面你创建了一个新的线性queue。 dispatch_queue_create, 有两个参数:

参数 描述
label label 唯一标识你创建的queue,label在debug时(Instruments, sample, stackshots, crash reports)会有用.因为系统库或其它框架也会创建queue,为了保证label的唯一,请DNS的反转格式(com.example.myqueue)。这个参数是可选的,可以为NULL
attr OS X v10.7 , iOS 4.3及其之后版本, DISPATCH_QUEUE_SERIAL (或者 NULL) 来创建线性queue;DISPATCH_QUEUE_CONCURRENT 来创建并发queue. 更早版本, 此参数只能为NULL.

所以你可以如下所示来创建并发queue:

dispatch_queue_t concurrentQueue = dispatch_queue_create("myAnotherUniqueIdentify", DISPATCH_QUEUE_CONCURRENT);

另外,当一个线性queue被创建且有任务提交上去,系统会为每个线性queue创建一个线程。如果你创建了2000个线性queue,就会有相应的2000个线程被创建。太多的线程会增加内存的消耗,以及线程间的切换都会大大降低系统性能。所以不要滥用线性queue,只有当任务之间的执行是有序的或者为了避免多线程并发而引起的资源竞争时,才使用线性queue。而且线性queue的个数应该和你需求的一至,不要创建多余的线性queue。 如是任务没有数据一至性问题,且可以并发,请用并发queue,而不是用多个线性queue来并发。 因为并发queue使用的线程是由系统内核非常有效地管理的,用并发queue更高效。

虽然,编译器有强大的内存自动管理(ARC),但是GCD中创建的实例(这里不是Objective-C对象,而是GCD中一些结构体实例)须由我们自己手动释放,因为这些实例(如dispatch queue)不像Block那样被当成Objective-C对象。当你使用完这些实例时,应用dispatch_release释放它们,用dispatch_retain来拥有它们。 GCD函数中含有"create"的,往往都需要我们dispatch_release

dispatch_release(mySerialDispatchQueue);
dispatch_retain(myConcurrentDispatchQueue);

那么,看下面代码:

dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create( "com.example.gcd.MyConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(myConcurrentDispatchQueue, ^{NSLog(@"block on myConcurrentDispatchQueue");});
dispatch_release(myConcurrentDispatchQueue);

上例中,往并发queue提交了任务之后,马上就释放它。这样做安全吗?

这样没任何问题。当一个Block被提交到一个queue上时,这个Block会拥有这个queue(dispatch_retain, 即,使queue引用记数加1)。当Block执行结束时它会释放对queue的拥有权(dispatch_release, 即,使queue引用记数减1)。所以,即使提交block之后立马释放queue,queue也不会被系统回收,而Block也可以被执行。当Block执行完并释放queue的拥有权时,queue才真正被回收。

Main Dispatch Queue / Global Dispatch Queue

Main Dispatch Queue / Global Dispatch Queue 由系统提供。
Main dispatch queue, 由名字也可以知道, 它将任务分配到主线程上执行。Main dispatch queue是线性queue,且总共只有一个。因为Main dispatch queue在主线程上执行任务,所以你应该把那些必须由主线程执行的任务(如UI更新)提交到此queue上。performSelectorOnMainThread也有相似的功能。

如果不是有特殊需求,一般而言,你不需需用dispatch_queue_create创建自己的并发queue,系统提供的全局queue(gloabl queue)足已。 Global queue有四种分别对应四个不同优先级: high, default, low, and background.

Types of dispatch queues

Name Type Description
Main dispatch queue Serial dispatch queue Executed on the main thread
Global dispatch queue (High priority) Concurrent dispatch queue Priority: High (Utmost priority)
Global dispatch queue (Default priority) Concurrent dispatch queue Priority: Normal
Global dispatch queue (Low priority) Concurrent dispatch queue Priority: Low
Global dispatch queue (Background priority) Concurrent dispatch queue Priority: Background

下面展示了不同queue的获取方法:

/*
* How to get the main dispatch queue */
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
/*
* How to get a global dispatch queue of high priority 
*/
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
/*
* How to get a global dispatch queue of default priority 
*/
dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*
* How to get a global dispatch queue of low priority 
*/
dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
/*
* How to get a global dispatch queue of background priority 
*/
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

随带提一下, 如果你对系统提供的queue,进行 dispatch_retain 或者 dispatch_release, 什么都不会发生,并不会增加或减少queue的引用计数. 毫无疑问,使用系统提供的queue,比你自己创建queue理方便。
使用dispatch queue例子:

  /*
  * Execute a Block on a global dispatch queue of default priority. 
  */
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    /*
    * some tasks here to be executed concurrently 
    */
    /*
    * Then, execute a Block on the main dispatch queue 
    */
    dispatch_async(dispatch_get_main_queue(), ^{
      /*
      * Here, some tasks that can work only on the main thread. 
      */
    }); 
  });

Controlling Dispatch Queues

GCD提供了大量有用的API让我们管理dispatch queue上的任务。
让我们一个一个查看这些API有多么强大!

dispatch_set_target_queue

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

函数dispatch_set_target_queue可以给你的queue设置一个target queue。这主要用在给你新建的queue指定优先级。无论你用dispatch_queue_create创建的是线性queue(serial queue)还是并发queue(concurrent queue),它的优先级都与global queue的默认优先级相同。在创建queue之后,你就可以用这个函数来改变它的优先级。下面代码展示了如何让一个线性queue拥有background优先级。

dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);

dispatch_set_target_queue的第一个参数是你要设置的queue,第二个参数是target queue,这两个参数都不能为NULL。它使mySerialDispatchQueue的优先级和target queue(globalDispatchQueueBackground)一样。不要任何系统的queue(main queue和global queue)作为第一个参数传进去,这样做的结果是不可预料的!
dispatch_set_target_queue不仅可以用来改变queue的优先级,还可以创建queue 的层级关系。如果你把一个任务(block)提交到一个线性queue(A)上,同时这个线性queue的target queue是另一个线性queue(B)。那么这个任务(block)将不会和提交到B或其它以B为target queue的queue上的block ,并发执行。

Queue 的层级

如图所示,底下三个dispatch queue上的任务(blocks)将会线性执行。当你的各个任务不应该并发执行,同时又必须放在不同的queue上时;你就可以通过设置这些queue的target都为某个线性queue,来阻止并发。实际上,我自己一直想不出有类似需求的场景。

dispatch_after

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

dispatch_after来设置queue上某个任务(block)的启动时间。下面代码做的是,延迟三秒后将一个block添加到main queue上:

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC); 
dispatch_after(time, dispatch_get_main_queue(), ^{
  NSLog(@"waited at least three seconds."); 
});

ull是C语言中指定unsigned long long类型的。注意的是,此函数并不是在指定时间之后执行block,而是在指定时间之后将block添加到目标queue中。你可以把该函数看作是增强版的dispatch_async,当指定延迟时间是DISPATCH_TIME_NOW时,此函数功能上与dispatch_async等价,不过苹果官方文档建议你应该用dispatch_async,而延迟时间为DISPATCH_TIME_FOREVER时,结果是不可预料的:

Passing DISPATCH_TIME_NOW as the when parameter is supported, but is not as optimal as calling dispatch_async instead. Passing DISPATCH_TIME_FOREVER is undefined.

dispatch_after并不适合执行高精度要求的延时任务,它对于那些精度不是那么高的延时任务是非常有用的。函数的三个参数中, 这里只讲第一个参数time吧。首先你要用dispatch_timedispatch_walltime来创建time。此函数接收纳秒数,所以你应该用NSEC_PER_SEC(一秒的纳秒数)或NSEC_PER_MSEC, 类似的常量如:

#define NSEC_PER_SEC 1000000000ull //一秒的纳秒数
#define USEC_PER_SEC 1000000ull //一秒的微秒数
#define NSEC_PER_USEC 1000ull //一微秒的纳秒数
#define NSEC_PER_MSEC 1000000ull //一毫秒的纳秒数

要延迟一秒,你可以这样创建:

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);

要延迟150毫秒,你可以这样:

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 150ull * NSEC_PER_MSEC);

dispatch_time主要用来创建相对时间,而dispatch_walltime则用来创建绝对时刻。比如,你用dispatch_walltime来创建2016年11月11号11点11分11秒时刻。可以用dispatch_walltime来实现闹钟,不过此方法精度不高。dispatch_walltime根据结构体timespec来创建时刻的,如下面例子:

dispatch_time_t getDispatchTimeByDate(NSDate *date) {
  NSTimeInterval interval;
  double second, subsecond;
  struct timespec time;
  dispatch_time_t milestone;
  
  interval = [date timeIntervalSince1970];
  subsecond = modf(interval, &second);
  time.tv_sec = second;
  time.tv_nsec = subsecond * NSEC_PER_SEC;
  milestone = dispatch_walltime(&time, 0);
  
  return milestone;
}

Dispatch Group

Dispatch group 用来对block分组。当你有个任务,并且些任务要求其它任务都完成时才能开始,这时你就可以和Dispatch Group来实现。当然简单的情况下,你可以把任务都放进一个线性queue中,在queue尾放入那个任务。但是如果遇到并发队列或有多个队列情况时,就变得很复杂,dispatch group应此而生。下因代码例子将三个block分到一个组,并在这些block都被执行之后,执行main queue上的block:

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, ^{NSLog(@"blok0");});
dispatch_group_async(group, queue, ^{NSLog(@"blok1");});
dispatch_group_async(group, queue, ^{NSLog(@"blok2");});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"done")});
dispatch_release(group);

结果将是(前三个输出顺序不定):
blk1
blk2
blk0
done
首先我们用dispatch_group_create创建dispatch_group_t实例。因为函数中有"create",当不再需要它时你必须release它, GCD中所有的release都用dispatch_release。函数dispatch_group_async就像dispatch_async把block添加到队列中,唯一的区别是多了一个参数dispatch_group_t,将block和dispatch group关联起来。当block和某个dispatch group关联之后,block会拥有这个dispatch group,(dispatch_retain,就如同block被添加到queue中时会retain这个queue一样),当block执行完时,block会释放它拥有的dispatch group(dispatch_release). 当所有与dispatch group相关联的block执行完时,dispatch_group_notify会将block("done")添加到main queue上。dispatch_group_notify并不会阻塞当前线程,它监听指定的dispach group,当该group上所有block都执行时,它将block添加到指定queue上。如果你要等待group上的所有block完成,你可以使用dispatch_group_wait,如下面例子:

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, ^{NSLog(@"blk0");});
dispatch_group_async(group, queue, ^{NSLog(@"blk1");});
dispatch_group_async(group, queue, ^{NSLog(@"blk2");});

dispatch_group_wait(group, GISPATCH_TIME_FOREVER);
dispatch_release(group);

dispatch_group_wait的第二个参数是timeout(dispatch_time_t)指定等待的时长, 如果在timeout时长内group上的任务都完成则返回0(success), 否则返回非零(failed)。timeout参数也像dispatch_after那样:

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if (result == 0) {
  //所有与group相关联的任务都已完成
} else {
  //与group相关联的任务中还有在运行
}

dispatch_group_wait被调用时,并不会从该函数立即返回,当前线程阻塞在此函数上,等待group上所有任务完成,或者timeout。

你可以用DISPATCH_TIME_NOW来查看group上的任务是否都已完成:

long result = dispatch_group_wait(group, DISPATCH_TIME_NOW)
if (result == 0) {
  //此时group上任务都已完成
} else {
  //此时group上还有任务在运行
}

在关联block和group时,dispatch_group_async并不是唯一的函数,还有dispatch_group_enter(与之成对的是dispatch_group_leave)。先看例子:

dispatch_group_t group1 = dispatch_group_create();
dispatch_group_t group2 = dispatch_group_create();

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t mainQueue= dispatch_get_main_queue();

dispatch_async(queue, ^{
  dispatch_group_enter(group1);
  NSLog("group1 queue blk0");
  dispatch_group_leave(group1);
});

dispatch_async(queue, ^{
  dispatch_group_enter(group1);
  dispatch_group_enter(group2);
  NSLog("group1 group2 queue blk1");
  dispatch_group_leave(group1);
  dispatch_group_leave(group2);
});

dispatch_async(mainQueue, ^{
  dispatch_group_enter(group2);
  NSLog("group2 mainQueue, blk2");
  dispatch_group_leave(group2);
});

dispatch_async(mainQueue, ^{
  dispatch_group_enter(group1);
  dispatch_group_enter(group2);
  NSLog("group1 group2 mainQueue blk3");
  dispatch_group_leave(group1);
  dispatch_group_leave(group2);
});

/*
* 监测group1。当blk0,blk1,blk3都完成时,添加block到queue
*/
dispatch_group_notify(group1, queue, ^{
  NSLog("blk0, blk1, blk3, all have finished!");
});

/*
*  监测group2, 当blk1, blk2, blk3 都完成时添加block到mainQueue
*/
dispatch_group_notify(group2, mainQueue, ^{
  NSLog("blk1, blk2, blk3, all have finished!");
});

注意dispatch_group_enterdispatch_group_leave必须保持平衡(成对出现)。如果你需要把一个block关联到不同的group上,你只能使用dispatch_group_enter函数对,否则使用dispatch_group_async更方便一点。

下面看代码是AFNetWorking中的例子:

- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                              failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
    // completionBlock is manually nilled out in AFURLConnectionOperation to break the retain cycle.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"
    self.completionBlock = ^{
        if (self.completionGroup) {
            dispatch_group_enter(self.completionGroup);
        }

        dispatch_async(http_request_operation_processing_queue(), ^{
            if (self.error) {
                if (failure) {
                    dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                        failure(self, self.error);
                    });
                }
            } else {
                id responseObject = self.responseObject;
                if (self.error) {
                    if (failure) {
                        dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                            failure(self, self.error);
                        });
                    }
                } else {
                    if (success) {
                        dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                            success(self, responseObject);
                        });
                    }
                }
            }

            if (self.completionGroup) {
                dispatch_group_leave(self.completionGroup);
            }
        });
    };
#pragma clang diagnostic pop
}

dispatch_barrier_async

void dispatch_barrier_async( dispatch_queue_t queue, dispatch_block_t block);

函数只能两个参数,一个是queue,一个block。作用是把block加到queue上,特殊的地方在于,被加queue上的block称为barrier block。barrier block 把queue上的任务的执行分成三个阶段:

  1. 将先于barrier block前提交到queue上的所有block执行完
  2. 执行barrier block
  3. 只有当barrier block执行好后,才会执行后于barrier block添加到queue上的block

这里的参数queue,应该是你通过dispatch_queue_create 创建的并发queue,如果你传入的是线性queue或者全局并发queue,函数作用就和dispatch_queue_async一样。
来个例子,具体看看:

dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.moning", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, blk0_brush_tooth);
dispatch_async(queue, blk0_wash_face);
dispatch_async(queue, blk0_wash_body);
dispatch_async(queue, blk1_eat_breakfast);
dispatch_async(queue, blk2_goout_by_bike);
dispatch_async(queue, blk2_enjoy_music);

我们要实现这样的功能,在洗澡的时候同时洗脸刷牙洗脸。然后做完上述这些事之后(blk0),吃早饭(blk1),吃完早饭骑自行车出去(边骑边听音乐).很明显,上述代码并不符合需求,它将所有事件都并发了。
有了dispatch_barrier_async我们可以这样做:

dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.moning", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, blk0_brush_tooth);
dispatch_async(queue, blk0_wash_face);
dispatch_barrier_async(queue, blk0_wash_body);
dispatch_async(queue, blk1_eat_breakfast);
dispatch_async(queue, blk2_goout_by_bike);
dispatch_async(queue, blk2_enjoy_music);

对数据库或文件进行写操作时,我们也可以用此函数来完成:

// read operation added to queue before barrier.(May added on different thread)
dispatch_async(queue, blk0_read1);
dispatch_async(queue, blk0_read2);
dispatch_async(queue, blk0_read3);
...

dispatch_barrier_async(queue, blk1_write);

// read operation added to queue after barrier.(May added on different thread)
dispatch_async(queue, blk2_read1);
dispatch_async(queue, blk2_read2);
dispatch_async(queue, blk2_read3);
...

上面两处示例代码的执行顺序是这样的:blk0 -> blk1 -> blk2(blk0, blk2为前缀的block各自之间分别异步)。

dispatch_apply

void dispatch_apply( size_t iterations, dispatch_queue_t queue, void (^block)( size_t));

把block添加到queue上iterations次,并且等待所有添加的block执行完成。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
  NSLog(@"%zu, index);
});

NSLog(@"done");

结果可能是:

4
1
0
3
5
7
8
9
2
6
done

上面结果数字的排列顺序是不定的。
再看一个例子:

dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*
* 在 global dispatch queue 上异步执行 */
dispatch_async(queue, ^{
  /*
  * 在 global dispatch queue上, dispatch_apply 函数等待所有任务的完成
  */
  dispatch_apply([array count], queue, ^(size_t index) {
    /*
    * 异步地对array上的对象进行操作 */
    NSLog(@"%zu: %@", index, [array objectAtIndex:index]); 
  });
  /*
  *  dispatch_apply 所有的任务已完成*/
  /*
  * 在 main dispatch queue 上异步执行 */
  dispatch_async(dispatch_get_main_queue(), ^{
    /*
    * Executed on the main dispatch queue.
    * Something like updating userface, etc. */
    NSLog(@"done");
  });
});

dispatch_once

void dispatch_once( dispatch_once_t *predicate, dispatch_block_t block);

用来确保在整个app的生命周期内block只执行一次。
不用dispatch_once:

static int initialized = NO;
if (initialized == NO) {
  // do initializing
  initialized = YES;
}

使用dispatch_once显得更优雅:

static dispatch_once_t pred;
dispatch_once(&pred, ^{
  // do the initializing.
});

第一种方法虽然在大多数情况下是没问题的,但它是线程不安全的。比如A线程执行到行3,即将执行行4『initialized = YES;』。此时线程A的执行权被剥夺,线程B获得执行权,执行这块代码时,initailized仍旧为NO,所以又进行了一次初始化。所以当多线程情况下,第一种方法可能会被执行多次。而dispatch_once就没问题,它是线程安全的。dispatch_once经常被用于创建单例。

dispatch_suspend/dispatch_resume

这两个方法用来suspend或者resume dispatch queue的执行。一个dispatch queue可以这样被suspend(休眠):

dispatch_suspend(queue);

然后resume:

dispatch_resume(queue);

suspend并不会影响那些已经在执行的任务。它只会阻止还在queue的task的执行。resume之后,queue上的任务又可以继续被调度执行。
你可以多次调用dispatch_suspend(queue);每调用一次queue的计数加一,中要此计数大于0,queue就一直处于休眠中。所以请保持dispatch_suspend(queue);dispatch_resume(queue);的平衡。

Dispatch Semaphore

Dispatch semaphore 相比传统的信号量机制性能上更优秀;只有当线程需要阻塞时,dispatch semaphore才会调用系统内核;如果不会阻塞线程,则不会进行系统内核的调用。Dispatch semaphore也是通过计数来实现多线程中的控制的。当counter大于0时,程序继续执行;当counter小于等于0,程序就等待在那里直到counter大于0,才继续执行。
相关函数就三个:

/创建信号量变量
dispatch_semaphore_t dispatch_semaphore_create( long value);/

//Increment the counting semaphore. If the previous value was less than zero, this function wakes a thread currently waiting in dispatch_semaphore_wait.
//把信号量计数加1,如果之前信号量值小于等于0则唤醒一个由dispatch_semaphore_wait阻塞的线程
long dispatch_semaphore_signal( dispatch_semaphore_t dsema);

//Decrement the counting semaphore. If the resulting value is less than zero, this function waits in FIFO order for a signal to occur before returning.
//把信号量计数减1,如果减1后值小于0,此函数以先进先出FIFO的顺序阻塞等待singal。
//如果成功则返回0; 否则返回非0,表示timeout(timeout的时间过去了,还是没收到singal)
long dispatch_semaphore_wait( dispatch_semaphore_t dsema, dispatch_time_t timeout);

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_semaphore_wait(semaphore, time); 
if (result == 0) {
  /*
  * The counter of the dispatch semaphore was more than one.
  * Or it became one and more before the specified timeout.
  * The counter is automatically decreased by one.
  *
  * Here, you can execute a task that needs a concurrency control. */
} else {
  /*
  * Because the counter of the dispatch semaphore was zero, * it has waited until the specified timeout.
  */
}

应用场景大致可归为么两类:

  • 多个线程访问有限资源

  • 两个线程之间相对某事件的同步
    比如有两个线程A和B,B的某一任务mession1执行的前提条件是A完成A的任务mession2。当执行到B的mession1时,如果A的mession2没有完成,B就被阻塞在那里直到A完成mession1.

多个线程访问有限资源

先看代码:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i < 100000; ++i) { 
  dispatch_async(queue, ^{
    [array addObject:[NSNumber numberWithInt:i]];
  }); 
}

上面代码在并发queue上更新array,即多个线程同时修改array,这就会使array变脏(corrupted)数据一至性问题,导至程序crash。我们可以用dispatch semaphore来防止多个线程同时修改array。
使用dispatch semaphore后,上面代码应是这样的:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//你的资源数只有一个(即array),所以参数传1;参数值应该等于你的资源个数。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i < 100000; ++i) { 
  dispatch_async(queue, ^{
    //等待直到semphore大于0
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
    [array addObject:[NSNumber numberWithInt:i]];
    
    //完成任务,释放资源的控制,使别的线程也能访问。
    dispatch_semaphore_signal(semaphore);
  }); 
}
dispatch_release(semaphore);
两个线程之间相对某事件的同步

看代码

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
void (^blk1)(void) = ^{
  NSLog("Mum give a life to me");
  NSLog("My mother and father get together");
};
void (^blk2)(void) = ^{
  NSLog("I grow up");
};
dispatch_async(queue, blk1);
dispatch_async(queue, blk2);

blk2的事件("I grow up")依赖于blk1的事件("Mum give a life to me"),"I grow up"应该在"Mum give a life to me"之后发生。但是上述代码这两个事件的执行顺序是不确定的。
使用semaphore代码应该是这样:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
void (^blk1)(void) = ^{
  NSLog("Mum give a life to me");
  //事件发生,增加semaphore值
  dispatch_semaphore_signal(semaphore);
  NSLog("My mother and father get together");
};
void (^blk2)(void) = ^{
  //如果事件没有发生, semaphore为0,一直阻塞等待;如果事件发生,semaphore为1,继续执行
  dispatch_semaphore_wait(semaphore);
  NSLog("I grow up");
};
dispatch_async(queue, blk1);
dispatch_async(queue, blk2);
dispatch_release(semaphore);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • 简介 GCD(Grand Central Dispatch)是在macOS10.6提出来的,后来在iOS4.0被引...
    sunmumu1222阅读 850评论 0 2
  • 背景 担心了两周的我终于轮到去医院做胃镜检查了!去的时候我都想好了最坏的可能(胃癌),之前在网上查的症状都很相似。...
    Dely阅读 9,226评论 21 42
  • 我们知道在iOS开发中,一共有四种多线程技术:pthread,NSThread,GCD,NSOperation: ...
    请叫我周小帅阅读 1,482评论 0 1
  • Dispatch Queues dispatch queues是执行任务的强大工具,允许你同步或异步地执行任意代码...
    YangPu阅读 630评论 0 4
  • 3.GCD GCD的全称是Grand Central Dispatch,提供了非常多的纯C语言的函数 GCD的优势...
    Mario_ZJ阅读 472评论 0 0