[iOS]GCD

来源:互联网 发布:三观尽毁的淘宝评论 编辑:程序博客网 时间:2024/06/05 06:53


本文参考自:

http://www.cnblogs.com/wendingding/p/3806821.html

http://www.cocoachina.com/ios/20160225/15422.html


Objective-C中多核编程的解决方法主要有三种:1、GCD(推荐);2、NSThread;3、NSOperationQueue;

GCD全称是Grand Central Dispatch,是苹果公司为多核的并行运算提出的纯C语言解决方案,会自动利用更多的CPU内核(比如双核、四核),自动管理线程的生命周期(创建线程、调度任务、销毁线程)。程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

GCD不属于Cocoa框架,它是一组实现并发编程的C接口,因此我们在编写GCD相关代码的时面对的是函数,而不是对象中的方法;GCD的API很大程度上基于BLOCK,GCD也可以脱离block来使用,GCD 和 block 的配合使用,可以方便地进行多线程编程。

GCD存在于libdispatch.dylib这个库中,这个调度库包含了GCD的所有的东西,但任何IOS程序,默认就加载了这个库。如果需要手动加载这个库:General--Linked Frameworks and Labraries——添加:



核心概念:任务,队列

任务:用来执行操作 队列:用来存放任务



GCD使用步骤:

1、定制任务;

2、任务添加到队列中,GCD会自动将队列里的任务取出来执行

任务的取出遵循队列的FIFO原则:先进先出,后进后出


要声明一个dispatch属性,一般只需要用strong即可

@property (nonatomic, strong) dispatch_queue_t queue;

或者

@property (nonatomic,strong) dispatch_queue_t queue;


执行任务:


1、GCD中执行任务的函数

左边的参数是队列,右边的参数是任务

(1)同步方式:

void dispatch_sync (dispatch_queue_t queue, dispatch_block_t block);

eg:

void dispatch_sync (dispatch_get_main_queue(),^{ ... } );

(2)异步方式:

void dispatch_async (dispatch_queue_t queue, dispatch_block_t block);

void disptach_async_f (dispatch_queue_t queue, void *context, dispatch_function_t work);

async接收block作为参数,async_f接收函数

执行async的时候block会被copy,在block执行完成之后 block再release,由于是系统持有block,所以不用担心循环引用的问题,block里面的self不需要weak

async_f中,work不能传入NULL,context可以,congtext会作为第一个参数传给work


2、同步和异步

同步:在当前的线程中执行

异步:在另一条线程中执行, 将任务提交到queue之后,立即返回,不等待任务的执行。


队列:

队列分为2种:

(1)并发队列(concurrent dispatch queue)

多个任务(并发)同时执行(自动开启多个线程执行任务),并发功能必须异步(disaptch_async)

(2)串行队列(serial dispatch queue)

任务一个接着一个执行


串行队列:

(1)使用dispatch_queue_create函数

eg:

dispatch_queue_t dispatch_queue_create(const char * label,  dispatch_queue_attr_t attr);

前一个参数是队列名称,后一个是队列属性,一般用NULL即可,表示串行,如果需要并行,attr传入_dispatch_queue_attr_concurrent

eg:

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

dispatch_release(queue);//非ARC需要释放手动创建的队列


(2)使用主队列(即和主线程相关的队列)

主队列是GCD自带的一种特殊串行队列,放在主队列里的任务都会放到主线程里执行。即使是异步,在主队列里也只会有一个线程。使用dispatch_get_main_queue( )获得主队列

eg: 

dispatch_queue_t queue = dispatch_get_main_queue();


并发队列:

GCD默认提供全局并发队列,该队列可以建立多个,即可以同时开启多个并发线程,使用dispatch_get_global_queue获得全局并发队列

eg:

dispathc_queue_t Queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0); 

获取一个全局的默认优先级的并发队列。第一个参数是优先级,使用全局默认的优先级DISPATCH_QUEUE_PRIORITY_DEFAULT,第二个参数暂时无用,用0即可


另:

-(void)performSelectorOnMainThread : (SEL)aMethod
  withObject : (id)obj
  waitUntilDone : (BOOL)waitUntilDone ;


dispatch_async( dispatch_get_main_queue() , ^{  //call aMethod  }) ;


withObject的参数可以为nil, waitUntilDone一般是NO
此方法类似diapatch_asynce


说明:全局并发队列的优先级

QOS_CLASS_USER_INTERACTIVE: 最高优先级,交互级别。使用这个优先级会占用几乎所有的系统CUP和I/O带宽,仅限用于交互的UI操作,比如处理点击事件,绘制图像到屏幕上,动画等
QOS_CLASS_USER_INITIATED: 次高优先级,用于执行类似初始化等需要立即返回的事件
QOS_CLASS_DEFAULT: 默认优先级,当没有设置优先级的时候,线程默认优先级。一般情况下用的都是这个优先级
QOS_CLASS_UTILITY: 普通优先级,主要用于不需要立即返回的任务
QOS_CLASS_BACKGROUND: 后台优先级,用于用户几乎不感知的任务。
QOS_CLASS_UNSPECIFIED: 未知优先级,表示服务质量信息缺失


#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高 QOS_CLASS_USER_INITIATED
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中),也可以传入NULL QOS_CLASS_DEFAULT
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低 QOS_CLASS_UTILITY
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台 QOS_CLASS_BACKGROUND


各种队列的执行效果
全局并发队列   手动创建串行队列 主队列 同步不开启新线程

串行执行任务
不开启新线程

串行执行队伍
不可用异步有开启新线程

并发执行任务
有开启新线程

串行执行任务
不开启新线程

串行执行任务
主队列永远不会开启新线程,其他情况下异步一般会开启新线程

只有全局并发队列和异步,能够并发执行任务


发生死锁需要2个条件:
1、代码运行的当前队列是串行队列。2、使用sync将任务加入到自己队列中
如果queue是并行队列,或者将任务加入到其他队列中,这是不会发生死锁的。


线程同步

多线程操作过程中往往多个线程是并发执行的,同一个资源可能被多个线程同时访问,造成资源抢夺,所以多线程应用中必须引入锁机制

三种方法:

1、NSLock同步锁

需要加锁的代码放在lock和unlock中间,注意只需要放对"抢占资源的读取和修改"的代码,如果将将其他操作代码放入其中,否则一个线程执行的时候另一个线程就一直在等待,就无法发挥多线程的作用了

NSLock *lock;

……

lock = [[NSLock alloc]init];

……

[lock lock];

……(需加锁的代码)

[lock unlock];




2、使用@synchronized代码块

首先选择一个对象作为同步对象(一般使用self),然后将”加锁代码”(争夺资源的读取、修改代码)放到代码块中。 @synchronized中的代码执行时先检查同步对象是否被另一个线程占用,如果占用该线程就会处于等待状态,直到同步对象被释放。

@synchronized(self) //self是同步的对象

{

……

(需要加锁的代码)

}




3、控制线程通信

GCD中信号量是dispatch_semaphore_t类型,支持信号通知和信号等待。
每当发送一个信号通知,则信号量+1;每当发送一个等待信号时信号量-1,;如果信号量为0则信号会处于等待状态,直到信号量大于0开始执行
根据这个原理我们可以初始化一个信号量变量,默认信号量设置为1,每当有线程进入“加锁代码”之后就调用信号等待命令(此时信号量为0)开始等待,此时其他线程无法进入,执行完后发送信号通知(此时信号量为1),其他线程开始进入执行,如此一来就达到了线程同步目的。
dispatch_semaphore_t _semaphore;
.........
_semaphore = dispatch_semaphore_create(1);//默认信号量设置为1
......
{
dispatch_semaphore_wait(_semaphore,DISPATCH_TIME_FOREVER);
........
(需要加锁的代码)
dispatch_semaphore_signal(_semaphore);//执行完发送信号通知,此时信号量为1
return ......
}


推荐后两种






队列组 

使用队列组可以使得组中的所有任务都执行完毕后再回到主线程执行其他操作。

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@“第一个任务"); });


__block:

在某个变量前面如果加上修饰字 _ _block (注意block前有两个下划线),这个变量又叫做block variable(变量)。那在block里就可以任意修改此变量值,变量值的变化也可以知道。


其他的函数

dispatch_apply():重复执行某个任务,这个方法没有办法异步执行(为不阻塞线程可以使用dispatch_async()包装一下再执行)。 该函数提交一个块到调度队列多次调用,它有三个参数:第一个指定迭代的数量。第二个参数指定block添加到的队列。第三是块本身

dispatch_once():单次执行一个任务,此方法中的任务只会执行一次,重复调用也没办法重复执行。

eg:

static dispatch_once_t  onceToken;
dispatch_once(&onceToken, ^{ .... } );


 dispatch_time():延迟一定的时间后执行。

eg:

// 延迟 2 秒执行:
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ .... } );


 dispatch_barrier_async():使用此方法创建的任务首先会查看队列中有没有别的任务要执行,如有则会等待已有任务执行完毕再执行;同时在此方法后添加的任务必须等待此方法中任务执行后才能执行。


在Object C中定义属性时有nonatomic和atomic两种选择。 

● atomic:原子属性,会为setter方法加锁(默认为atomic) 

● nonatomic:非原子属性,不会为setter方法加锁 

原子属性是线程安全,但需要消耗大量的资源。非原子属性非线程安全,但适合内存小的移动设备。在iOS开发中,大多数属性都应声明为非原子属性,尽量避免多线程抢夺同一块资源,应该将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力


关于dispatch_once:

dispatch_once在整个运行过程中只会被运行一次,而且是线程安全的。即使有多个线程同时调用,也只有一个执行,其他的被阻塞,而一但完成了一次,以后就会快速跳过dispatch_once部分,不会被运行多次,所以可以节省运行的开销。

eg:

+ (instancetype)sharedInstance {
 
  static id sharedInstance;
 
  static dispatch_once_t onceToken;
 
  dispatch_once(&onceToken, ^{
 
    sharedInstance = [self new];
 
  });
 
  return sharedInstance;
 
}

本例子来源于: http://www.2cto.com/kf/201412/365671.html


















0 0