GCD 和NSOperationQueue

同步异步、并行串行的区分

  • 两个容易搞混的概念:

  • 同步异步:是指任务添加到线程上这个过程的同步和异步

  • 串行并行:是指任务在线程上运行的串行和并行

  • 使用block,invocation或者是dispatch_function_t(C函数)添加任务,任务中都是需要执行return返回的,同步异步就是指是否和这个return同步,如果是同步添加到线程上的任务,把任务添加到线程上的添加操作本身会等到任务中的return执行之后才执行自己的return。如果是异步添加到线程上的任务,添加任务操作本身执行后会立即返回,并不会等待任务完成后自己才返回,例如如下这个bug:


- (void)viewDidLoad {

    [super viewDidLoad];



    dispatch_queue_t queue = dispatch_queue_create("LivyNN", DISPATCH_QUEUE_SERIAL);

    NSArray *array = [[NSArray alloc] initWithObjects:@"liwei", nil];

    void *p = (__bridge void*)array;

    dispatch_async_f(queue, p, sumAB);

}

void sumAB(void *input){

    id object = (__bridge id)input;

    [NSThread sleepForTimeInterval:3];

    NSLog(@"%@", object);

    NSLog(@"线程结束");

}

  • 创建了一个线程(串行:并行串行不关紧要,只有一个任务,都是串行),声明一个数组指针p传给sumAB函数,添加到线程上,结果崩溃了BAD_ACCESS,原因如下:

  • dispatch_async_f执行的是异步把任务添加到线程中,添加完成后立刻就返回了,这个时候任务还没执行,等到任务执行的时候viewDidLoad函数已经执行完,p指针已经被释放了,就引起了BAD_ACCESS。相比之下如果改为同步添加:


- (void)viewDidLoad {

    [super viewDidLoad];



    dispatch_queue_t queue = dispatch_queue_create("LivyNN", DISPATCH_QUEUE_SERIAL);

    NSArray *array = [[NSArray alloc] initWithObjects:@"liwei", nil];

    void *p = (__bridge void*)array;

    dispatch_sync_f(queue, p, sumAB);//修改了这里async->sync

}

void sumAB(void *input){

    id object = (__bridge id)input;

    [NSThread sleepForTimeInterval:3];

    NSLog(@"%@", object);

    NSLog(@"线程结束");

}

  • 运行后发现没有报错:原因如下:

  • dispatch_sync_f把任务同步添加到线程中,添加任务的操作要和任务保持同步返回,也就是说:把任务添加到线程上去还要等他执行完才能返回,所以,dispatch_sync_f函数一直等待sumAB函数执行,三秒钟后,sumAB执行完,dispatch_sync_f才执行完,最后viewDidLoad才执行结束,在sumAB执行期间一直处在viewDidLoad的函数周期里面,p没有被释放,所以不会崩溃。

  • 另外:如果一定要异步添加到线程上,需要保证p只能不被释放掉,使用__bridge_retain和__bridge_transfer关键字转交所有权,如下:


- (void)viewDidLoad {

    [super viewDidLoad];



    dispatch_queue_t queue = dispatch_queue_create("LivyNN", DISPATCH_QUEUE_SERIAL);

    NSArray *array = [[NSArray alloc] initWithObjects:@"liwei", nil];

    void *p = (__bridge_retained void*)array;//retain

    dispatch_async_f(queue, p, sumAB);

}

void sumAB(void *input){

    id object = (__bridge_transfer id)input;//transfer

    [NSThread sleepForTimeInterval:3];

    NSLog(@"%@", object);

    NSLog(@"线程结束");

}

  • P指针指向对象的所有权会转交到sumAB的object手中,直到sumAB执行完之前该对象都不会被释放。

  • 理解同步异步后并行和串行就容易理解了:


- (void)viewDidLoad {

    [super viewDidLoad];

    dispatch_queue_t queue = dispatch_queue_create("LivyNN", DISPATCH_QUEUE_SERIAL);//serial是串行

    NSArray *array = [[NSArray alloc] initWithObjects:@"liwei", nil];

    void *p = (__bridge_retained void*)array;//retain

    dispatch_async_f(queue, p, sumAB);

    dispatch_async_f(queue, p, sumAB);

}

void sumAB(void *input){

    id object = (__bridge_transfer id)input;//transfer

    [NSThread sleepForTimeInterval:3];

    NSLog(@"%@", object);

    NSLog(@"任务结束");

}

  • 上面一共执行了6秒钟,3秒钟和6秒钟分别打印了任务结束。

- (void)viewDidLoad {

    [super viewDidLoad];

    dispatch_queue_t queue = dispatch_queue_create("LivyNN", DISPATCH_QUEUE_ConCurrent);//concurrent并行

    NSArray *array = [[NSArray alloc] initWithObjects:@"liwei", nil];

    void *p = (__bridge_retained void*)array;//retain

    dispatch_async_f(queue, p, sumAB);

    dispatch_async_f(queue, p, sumAB);

}

void sumAB(void *input){

    id object = (__bridge_transfer id)input;//transfer

    [NSThread sleepForTimeInterval:3];

    NSLog(@"%@", object);

    NSLog(@"任务结束");

}

  • 上面一共执行3秒钟,第三秒钟同时打印了两个任务技术。

  • 另一方面:串行并行可以视为队列本身的能力,同步异步可以视为是调用者的调用方式:当调用者使用同步方式(sync)向异步(concurrent)线程中添加两个任务时,线程有能力开辟多线程并行执行两个任务,但是调用者同步添加说明他不需要线程这样做,所以两个任务是串行的;如果调用者异步(async)向一个串行(serial)线程添加两个任务,虽然调用者试图让两个任务并发执行,但是线程能力有限,所以两个任务还是串行执行的;所以,如果需要两个任务并发执行,需要异步地向并行线程中添加多个任务。

GCD

*创建一个线程:dispatch_queue_t queue = dispatch_queue_create("一个线程的名字,在调试的时候可以看到这个名字",DISPATCH_QUEUE_ConCurrent/DISPATCH_QUEUE_SERIAL);c是并行,s是串行。

  • 把一个任务添加到一个线程上去:提供block和void*函数指针两种方法分别是(带f的是void *,不带f的是block,带a的是异步,不带a的是同步):

*dispatch_async

*dispatch_async_f

*dispatch_sync

*dispatch_sync_f

  • 添加方法:

    dispatch_async(queue, ^{

        /*异步添加block*/

    });

    dispatch_async_f(queue, p, sumAB);//队列,参数,函数 



dispatch_sync(queue, ^{

        /*同步添加block*/

    });

    dispatch_sync_f(queue, p, sumAB);//队列,参数,函数,注意函数要定义成C语言的形状,p需要是void*类型的地址,如果需要传id类型,则需要__bridge关键字转化,int类型参数可以直接int a = 0;&a

  • 获取到主队列:

dispatch_queue_t mainQueue = dispatch_get_main_queue();

  • 获取到全局队列:

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

*其中:DISPATCH_QUEUE_PRIORITY_LOW是个枚举,有:HIGH,DEFAULT,LOW,BACKGROUND,分别对应于2,0,-2,MIN四个数字。

  • 第二个参数留待以后使用。

  • 通常看到的(0,0)就是获取到全局default优先级的队列。

注意注意:不能往主线程上加同步任务!!!不能往主线程上加同步任务!!!不能往主线程上加同步任务!!!

  • 原因是同步任务的添加任务操作会等待任务执行后才return,而同步添加的任务会等到主线程上当前的任务执行完才会执行后添加上去的任务,当前的任务是什么?就是添加任务操作。所以添加任务的任务要同步等待所添加的任务执行完,所添加的任务又要等待主线程当前的任务执行完,当前的任务又是添加任务的任务。。。。GG

  • 但是可以在其他线程里同步添加任务到主队列:其他线程需要等主线程,主线程不需要等其他线程。

*并行循环迭代:dispatch_apply:


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

    dispatch_apply(100, concurrentQueue, ^(size_t size) {

        [NSThread sleepForTimeInterval:1.0];

        NSLog(@"complete");

    });

  • 注意这个方法是同步的,会阻塞线程,与正常的for循环一样。且并不是完全并行的,如上面代码,实测每秒钟会同时执行6-8次代码块;而不是100次。

*队列挂起:dispatch_suspend(queue);当前正在执行的任务不会中断

*队列恢复:dispatch_resume(queue);

  • 信号量(可以实现concurrent线程的最大并发数):

    dispatch_queue_t queue = dispatch_queue_create("liwei", DISPATCH_QUEUE_CONCURRENT);

    dispatch_semaphore_t signal = dispatch_semaphore_create(3);//创建信号量,初始有三个

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

        dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);//如果有可用的信号量就直接返回,如果没有就在这等

        [NSThread sleepForTimeInterval:1];

        NSLog(@"%d",i);

        dispatch_semaphore_signal(signal);//运行完了释放信号量以循环使用

    });

  • 分组:

*创建分组:dispatch_group_t group = dispatch_group_create();

*把任务放进分组:dispatch_group_async/synx(group,queue,^{任务})

*等待分组内任务全部完成:dispatch_group_wait(group,FOREVER);第二个参数是最大等待时间,等不到就不阻塞了。

*示例:


    dispatch_queue_t queue = dispatch_queue_create("liwei", DISPATCH_QUEUE_CONCURRENT);

    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, queue, ^{

        [NSThread sleepForTimeInterval:1.0];

        NSLog(@"第一个任务完成了");

    });

    dispatch_group_async(group, queue, ^{

        [NSThread sleepForTimeInterval:3.0];

        NSLog(@"第二个任务执行完了");

    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{

        NSLog(@"分组中的任务都执行完了");

    });

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

  • 1秒后第一个任务执行完,再过两秒,后面两句一起打印。

NSOperationQueue

*NSOperationQueue是基于GCD的封装。

  • 创建一个

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

NSOperationQueue *queue = [NSOperationQueue mainQueue];

*添加block任务,相当于dispatch_async(queue,block):


[queue addOperationWithBlock:^{



}];

  • 创建任务,添加到queue上

//block任务

    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{



    }];

    [queue addOperation:blockOperation];

//invocation任务

    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationMethod) object:nil];

    [queue addOperation:invocationOperation];

 //子类化NSOperation,覆盖其main方法,得子类SubOperation

 SubOperation *subOperation = [[SubOperation alloc] initWith::::];

 [queue addOperation:subOperation]; 

 //添加一组

     NSArray *operationArray = @[blockOperation,invocationOperation];

    [queue addOperations:operationArray waitUntilFinished:NO]; //NO相当于async,yes相当于sync

  • 注意:一个Operation只能被加到一个线程中,且只能加一次;如果有很多类似的Operation可以使用子类化Operation的方式。

  • 添加依赖:


[blockOperation addDependency:invocationOperation];

  • 注意:如果invocationOperation从来没有被添加到任何一个queue中过,blockOperation也永远都不会被执行;另外,依赖可以跨越线程:

    [blockOperation addDependency:invocationOperation];

    [queue addOperation:invocationOperation];

    [mainQueue addOperation:blockOperation];

  • 这样写添加的依赖也是生效的,blockOperation在主线程中一直处于等待状态,知道blockOperation在queue中执行完。

*Operation优先级,代码如下:


NSBlockOperation *blockOperation0 = [NSBlockOperation blockOperationWithBlock:^{

        NSLog(@"blockOperation0");

    }];

    NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{

        NSLog(@"blockOperation1");

    }];

    NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{

        NSLog(@"blockOperation2");

    }];

    NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{

        NSLog(@"blockOperation3");

    }];

    NSBlockOperation *blockOperation4 = [NSBlockOperation blockOperationWithBlock:^{

        NSLog(@"blockOperation4");

    }];

    blockOperation0.queuePriority = NSOperationQueuePriorityVeryLow;

    blockOperation1.queuePriority = NSOperationQueuePriorityLow;

    blockOperation2.queuePriority = NSOperationQueuePriorityNormal;

    blockOperation3.queuePriority = NSOperationQueuePriorityHigh;

    blockOperation4.queuePriority = NSOperationQueuePriorityVeryHigh;

    NSArray *operationArray = @[blockOperation1,blockOperation3,blockOperation2,blockOperation0,blockOperation4];

    [queue addOperations:operationArray waitUntilFinished:NO];

  • 输出结果看到1-4都是随机出现的,这优先级也没用啊?不能够,随机出现是NSOperation在没指定最大并发数的情况下默认是0,无限的,如果这样写就可以清楚地看到4,3,2,1,0:

queue.maxConcurrentOperationCount = 1;

  • 上面这种情况下是串行队列,所以先找优先级高的执行再找优先级低的

queue.maxConcurrentOperationCount = 2;

  • 如果是这样,就会4,3或者3,4在前两个;1,2或2,1在中间,最后执行0

queue.maxConcurrentOperationCount = 3;

  • 如果是这样,就是4,3,2在前面随机排列,1,0在后面随机排列

  • 什么也不写的时候相当于:


queue.maxConcurrentOperationCount = MAX;

  • 意思就是能开多少子线程就开多少子线程,所以MAX>5的情况下,就是0-4随机排列,这种情况下优先级顺序不生效,因为能力太强不用考虑谁优先,大家都优先,都优先就随机了。

  • 取消线程:[operation cancel];

  • 取消队列中的所有线程[operationQueue cancelAllOperations];

  • 挂起:[queue setSuspended:YES];

  • 恢复:[queue setSuspended:NO];

  • 等待某个操作:[operation waitUntilFinished];//注意:千万不要在主线程中执行这个

  • 等待queue中的操作都完成:[queue waitUntilAllOperationsAreFinished];

GCD唯一的有点就是速度快

NSOperationQueue有更多的依赖,优先级功能,且封装性更好。

结束

你可能感兴趣的:(GCD 和NSOperationQueue)