配色: 字号:
对GCD的一些理解和实践
2016-09-10 | 阅:  转:  |  分享 
  
对GCD的一些理解和实践
GCD

GCD,全程GrandCentralDispatch,是苹果为了多核并行提出的解决方案。它是使用C语言实现,但是由于用了block来处理回调,所以使用起来十分方便。并且GCD会自动管理线程的生命周期,不需要我们去管理。

任务和队列

GCD中有两个重要的概念,任务和队列。

1、任务,就是我们想要处理的事情,任务可以分为同步执行和异步执行:

同步(sync):使用dispatch_sync(dispatch_queue_tqueue,dispatch_block_tblock)创建,同步执行任务时会阻塞当前线程等待block执行完毕返回。然后当前线程才会继续执行下去。

异步(async):使用dispatch_async(dispatch_queue_tqueue,dispatch_block_tblock)创建,异步任务不会阻塞当前线程,任务创建后立即返回线程往下执行。

2、队列,存放任务,并将任务由先入先出地派发出去。分为串行队列和并行队列:

串行队列(Serialqueue):队列中的任务根据创建顺序,先入先出地执行,等待上一个任务执行完毕后,才会执行下一个任务,有严格的执行先后顺序。

并行队列(Concurrentqueue):队列会根据先后顺序,将任务派发出去,并行执行。所有的任务几乎都是一起执行的。不过需要注意,GCD会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。有一个表,可以大体说明任务和队列之间的配合使用:

串行队列 并行队列
同步执行任务 当前线程,一个一个执行 当前线程,一个一个执行
异步执行任务 另开线程,一个一个执行 开很多线程,一起执行
下面是任务和队列的使用演示:



复制代码
/
串行队列中的任务会等待正在执行的任务执行结束,排队执行
/
dispatch_queue_tserial_queue=dispatch_queue_create("serial.queue",DISPATCH_QUEUE_SERIAL);
//主队列
dispatch_queue_tmainQueue=dispatch_get_main_queue();

/
并行,不等待正在执行的任务的处理结果,可以并发执行多个任务
/
dispatch_queue_tconcurrent_queue=dispatch_queue_create("concurrent.queue",DISPATCH_QUEUE_CONCURRENT);

//全局队列
dispatch_queue_tglobalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

//在串行队列中创建几个同步任务,在主线程顺序执行
dispatch_sync(serial_queue,^{
NSLog(@"taska,thread:%@",[NSThreadcurrentThread]);
});
dispatch_sync(serial_queue,^{
NSLog(@"taskb,thread:%@",[NSThreadcurrentThread]);
});
dispatch_sync(serial_queue,^{
NSLog(@"taskc,thread:%@",[NSThreadcurrentThread]);
});

//在串行队列中创建几个异步任务,在主线程顺序执行
dispatch_sync(serial_queue,^{
NSLog(@"taskaa,thread:%@",[NSThreadcurrentThread]);
});
dispatch_sync(serial_queue,^{
NSLog(@"taskbb,thread:%@",[NSThreadcurrentThread]);
});
dispatch_sync(serial_queue,^{
NSLog(@"taskcc,thread:%@",[NSThreadcurrentThread]);
});

//在串行队列中创建几个异步任务,另开1个线程顺序执行
dispatch_async(serial_queue,^{
NSLog(@"task1,thread:%@",[NSThreadcurrentThread]);
});
dispatch_async(serial_queue,^{
NSLog(@"task1,thread:%@",[NSThreadcurrentThread]);
});
dispatch_async(serial_queue,^{
NSLog(@"task1,thread:%@",[NSThreadcurrentThread]);
});

//在并队列中创建几个异步任务,另开多个线程同时执行
dispatch_async(concurrent_queue,^{
NSLog(@"task11,thread:%@",[NSThreadcurrentThread]);
});
dispatch_async(concurrent_queue,^{
NSLog(@"task22,thread:%@",[NSThreadcurrentThread]);
});
dispatch_async(concurrent_queue,^{
NSLog(@"task33,thread:%@",[NSThreadcurrentThread]);
});
复制代码


任务组和栅栏

有时候当我们想要为多个任务添加依赖关系的时候,就可以使用任务组dispatch_group和dispatch_barrier。

任务组是将若干个任务放在一个group中,这些任务可以在同一队列也可以在不同队列,然后用dispatch_group_notify()和dispatch_group_wait()对任务组中任务的完成进行处理。

1、dispatch_group_notify()中的任务会在group中多有任务执行完毕后执行

复制代码
dispatch_group_tgroup=dispatch_group_create();

/
group中所有任务任务执行完毕后,执行dispatch_group_notify中的任务
/
dispatch_group_async(group,serial_queue,^{
sleep(2);
NSLog(@"serial_queue1");
});
dispatch_group_async(group,serial_queue,^{
sleep(2);
NSLog(@"serial_queue2");
});
dispatch_group_async(group,concurrent_queue,^{
sleep(2);
NSLog(@"concurrent_queue1");
});

dispatch_group_notify(group,dispatch_get_main_queue(),^{
NSLog(@"returnmainqueue");
});
复制代码


2、dispatch_group_wait()中可传入一个给定时间,如果在等待时间结束前group所有任务执行完毕则返回0,否则返回非0,这个函数是一个同步任务

复制代码
/
dispatch_group_wait给定一个时间,如果在等待时间结束前group所有任务执行完毕则返回0,否则返回非0,这个函数是一个同步任务
/
dispatch_group_async(group,serial_queue,^{
sleep(3);
NSLog(@"serial_queue1");
});
dispatch_group_async(group,serial_queue,^{
sleep(2);
NSLog(@"serial_queue2");
});
dispatch_group_async(group,concurrent_queue,^{
sleep(3);
NSLog(@"concurrent_queue1");
});

longi=dispatch_group_wait(group,dispatch_time(DISPATCH_TIME_NOW,NSEC_PER_SEC6));
NSLog(@"--%ld--",i);
dispatch_group_async(group,concurrent_queue,^{
NSLog(@"finishall");
});
复制代码


3、可以使用dispatch_group_enter()和dispatch_group_leave()来添加任务组的任务,两者必须要成对出现,两个函数之间的代码便是要加入任务住的任务

复制代码
/
使用dispatch_group_enter和dispatch_group_leave添加组任务,两者必须要成对出现
/
dispatch_group_enter(group);
sleep(2);
NSLog(@"1");
dispatch_group_leave(group);

dispatch_group_enter(group);
dispatch_async(concurrent_queue,^{
sleep(3);
NSLog(@"2");
dispatch_group_leave(group);
});

dispatch_group_enter(group);
sleep(2);
NSLog(@"3");
dispatch_group_leave(group);

longi=dispatch_group_wait(group,dispatch_time(DISPATCH_TIME_NOW,NSEC_PER_SEC6));
NSLog(@"--%ld--",i);
dispatch_group_async(group,concurrent_queue,^{
NSLog(@"finishall");
});
复制代码


栅栏是将一个队列中的任务分割成两部分,在栅栏任务之前添加的任务全部执行完毕后,单独执行栅栏任务,执行完毕后,再继续执行后面的任务。栅栏必须单独执行,不能与其他任务并发执行,因此,栅栏只对并发队列有意义。栅栏只有等待当前队列所有并发任务都执行完毕后,才会单独执行,待其执行完毕,再按照正常的方式继续向下执行。

复制代码
dispatch_queue_tconcurrent_queue=dispatch_queue_create("concurrent.queue",DISPATCH_QUEUE_CONCURRENT);

dispatch_async(concurrent_queue,^{
sleep(2);
NSLog(@"1");
});
dispatch_async(concurrent_queue,^{
sleep(2);
NSLog(@"2");
});
dispatch_async(concurrent_queue,^{
sleep(2);
NSLog(@"3");
});

dispatch_barrier_async(concurrent_queue,^{
sleep(2);
NSLog(@"barrier");
});
dispatch_async(concurrent_queue,^{
sleep(2);
NSLog(@"finish1");
});

dispatch_async(concurrent_queue,^{
NSLog(@"finish2");
});
复制代码


重复执行dispatchApply和单次执行dispatch_once

dispatch_apply()是将一个任务提交到队列中重复执行,并行或者串行由队列决定,dispatch_apply会阻塞当前线程,等到所有任务完成后返回

复制代码
dispatch_queue_tconcurrent_queue=dispatch_queue_create("concurrent.queue",DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(5,concurrent_queue,^(size_tindex){
sleepwww.wang027.com(1);
NSLog(@"index:%zu",index);
});
NSLog(@"finish");
复制代码
dispatch_once()确保block内代码在整个应用运行期间只执行一次

//确保block内代码在整个应用运行期间只执行一次
staticdispatch_once_tonceToken;
dispatch_once(&onceToken,^{
NSLog(@"justrunonceinapplication");
});
线程同步、信号量和线程死锁

多线程处理经常需要考虑到资源抢占的问题,比如经典的购票问题,写数据库等问题。处理问题的办法有很多,下面介绍最简单的两种做法:加上同步锁,或者用信号量来解决。

同步锁,每当有线程访问锁里的资源时,会将此部分锁住,拒绝其他线程访问。直到占用的线程推出后才解锁,允许其他资源访问。

//同步锁,对block内的代码加锁,同一时间内只允许一个线程访问
@synchronized(self){
NSLog(@"lock");
};
信号量dispatch_semaphore,一开始设置信号的总量,然后用dispatch_semaphore_wait()和dispatch_semaphore_signal()来管理信号量,达到控制线程访问的目的

复制代码
//信号量dispatch_semaphore
dispatch_group_tgroup=dispatch_group_create();

//设置总信号量
dispatch_semaphore_tsemaphore=dispatch_semaphore_create(10);
for(inti=0;i<100;i++){
//设置等待信号,如果此时信号量大于0,那么信号量减一并继续往下执行
//如果此时信号量小于0,会一直等待,直到超时
//如果超时返回非零,成功执行返回0
dispatch_semaphore_wait(semaphore,dispatch_time(DISPATCH_TIME_NOW,NSEC_PER_SEC50));

dispatch_group_async(group,concurrent_queue,^{
sleep(1);
NSLog(@"%d",i);
//发送信号,让信号量加一
dispatch_semaphore_signal(semaphore);
});
}

dispatch_group_notify(group,concurrent_queue,^{
NSLog(@"finish");
});
复制代码


GCD尽管使用起来非常方便,但是如果使用不当也活造成一些麻烦,下面列举几个会造成线程死锁的场合:

复制代码
//在并行队列中,在当前队列调用dispatch_sync,并传入当前队列执行,并不会造成deadlock。dispatch_sync会阻塞当前线程,但是由于队列是并行执行,所以block中的任务会马上执行后返回。
-(void)syncAndConcurrentQueue{
dispatch_queue_tqueue=dispatch_queue_create("concurrent.queue",DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue,^{

NSLog(@"Jumptoconcurrent.queue!,thread:%@",[NSThreadcurrentThread]);

dispatch_sync(queue,^{
sleep(3);
NSLog(@"success6,thread:%@",[NSThreadcurrentThread]);
});
NSLog(@"return");
});
}


//在串行队列中,在当前队列调用dispatch_sync,并传入当前队列执行,会造成deadlock。dispatch_sync会阻塞当前线程,等待block中的任务执行完之后再继续执行,但是由于队列是串行执行,block中的任务放在最后,所以永远没有机会执行,线程死锁
-(void)synAndSerialQueue{

dispatch_queue_tqueue=dispatch_queue_create("serial.queue",DISPATCH_QUEUE_SERIAL);
dispatch_async(queue,^{
NSLog(@"Jumptoserial.queue!");

dispatch_sync(queue,^{
NSLog(@"success");
});
NSLog(@"return");
});
}

//任务1会阻塞主线程,直到block中执行完毕返回,任务二在主线程添加了了一个同步任务,阻塞当前线程,知道任务执行完毕返回,而任务2没有机会被执行。造成两条线程死锁。
-(void)recycle{

dispatch_queue_tconcurrent_queue=dispatch_queue_create("concurrent.queue",DISPATCH_QUEUE_CONCURRENT);

//任务1
dispatch_sync(concurrent_queue,^{

NSLog(@"jumptoconcurrentqueue");

//任务2
dispatch_sync(dispatch_get_main_queue(),^{

NSLog(@"returnmainqueue");
});

});
}
复制代码




总结

以上是使用GCD的一些心得,GCD使用起来尽管十分便利,但是在处理一些场景比如取消任务时候会很麻烦,所以实现简单功能的时候推荐使用GCD,如果功能复杂的话建议使用NSOperation和NSOperationQueue,NSOperationQueue的底层也是由GCD实现的,完全面向对象,所以使用起来更好理解。下次有空讲讲NSOperation和NSOperationQueue
献花(0)
+1
(本文系thedust79首藏)