多线程GCD.

        最近颇花了一番功夫把多线程GCD人的一些用法总结出来,一来帮自己巩固一下知识、二来希望能帮到对这一块还迷茫的同学。

        GCD是Grand Central Dispatch这三个英文单词的缩写,它是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理,我们只需要告诉干什么就行。同时它使用的也是c语言,不过由于使用了Block(Swift里叫做闭包),使得使用起来更加方便,而且灵活。

一、任务和队列

在GCD中,加入了两个非常重要的概念:任务和队列。

任务:即操作,你想要干什么,说白了就是一段代码,在GCD中就是一个Block,所以添加任务十分方便。任务有两种执行方式:同步执行和异步执行,他们之间的区别是是否会创建新的线程。

同步(sync)和异步(async)的主要区别在于会不会阻塞当前线程,直到Block中的任务执行完毕!

如果是同步(sync)操作,它会阻塞当前线程并等待Block中的任务执行完毕,然后当前线程才会继续往下运行。

如果是异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程。

队列:用于存放任务。一共有两种队列,串行队列和并行队列。

串行队列:队列中的任务,GCD会FIFO(先进先出)地取出来一个,执行一个,然后取下一个,这样一个一个的执行。

并行队列:队列中的任务,GCD也会FIFO的取出来,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。

1.0 创建和获得队列

1.1 主队列:这是一个特殊的串行队列。什么是主队列,大家都知道吧,它用于刷新UI,任何需要刷新UI的工作都要在主队列执行,所以一般耗时的任务都要放到别的线程执行。

dispatch_queue_t queue = dispatch_get_main_queue();

1.2 自己创建的队列:其中第一个参数是标识符,用于DEBUG的时候标识唯一的队列,可以为空。大家可以看xcode的文档查看参数意义。第二个参数用来表示创建的队列是串行的还是并行的,传入DISPATCH_QUEUE_SERIAL或NULL表示创建串行队列。传入DISPATCH_QUEUE_CONCURRENT表示创建并行队列。

串行队列

dispatch_queue_t queue1 = dispatch_queue_create("tk.bourne.testQueue", NULL);

dispatch_queue_t queue2 = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);

并行队列

dispatch_queue_t queue3 = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);

1.3 全局并行队列:只要是并行任务一般都加入到这个队列。这是系统提供的一个并发队列。

第一个参数线程优先级

第二个参数0 :占位符、目前没有意义

dispatch_queue_t queue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

1.4 Global Dispatch Queue(高优先级)的获取方法

dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

1.5 Global Dispatch Queue(默认优先级)的获取方法

dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

1.6 Global Dispatch Queue(低优先级)的获取方法

dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

1.7 Global Dispatch Queue(后台优先级)的获取方法

dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

2.0 创建任务

同步任务:会阻塞当前线程(SYNC)

dispatch_sync(queue1, ^{

NSLog(@"%@", [NSThread currentThread]);

});

异步任务:不会阻塞当前线程(ASYNC)

dispatch_async(queue3, ^{

NSLog(@"%@", [NSThread currentThread]);

});

二、队列组

        队列组可以将很多队列添加到一个组里,这样做的好处是,当这个组里所有的任务都执行完了,队列组会通过一个方法通知我们。

1.创建队列组

dispatch_group_t group = dispatch_group_create();

2.创建队列

dispatch_queue_t queue  = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

3.多次使用队列组的方法执行任务,只有异步方法

3.1.执行3次循环

dispatch_group_async(group, queue, ^{

for(NSIntegeri =0; i <3; i++) {

NSLog(@"group-01 - %@", [NSThreadcurrentThread]);

}

});

3.2.主队列执行8次循环

dispatch_group_async(group,dispatch_get_main_queue(), ^{

for(NSIntegeri =0; i <8; i++) {

NSLog(@"group-02 - %@", [NSThreadcurrentThread]);

}

});

3.3.执行5次循环

dispatch_group_async(group, queue, ^{

for(NSIntegeri =0; i <5; i++) {

NSLog(@"group-03 - %@", [NSThreadcurrentThread]);

}

});

4.都完成后会自动通知

dispatch_group_notify(group,dispatch_get_main_queue(), ^{

NSLog(@"完成- %@", [NSThreadcurrentThread]);

});

三、dispatch_barrier_sync

为了高效率地进行访问,读取处理追加到Concurrent Dispathc Queue中,写入处理在任一个读取处理没有执行的状态下,追加到Serial Dispatch Queue中即可(在写入处理结束之前,读取处理不可执行)。

虽然利用Dispatch Group和dispatch_set_target_queue函数也可实现,但是源码会很复杂。GCD为我们提供了高效的dispatch_barrier_sync函数。该函数同dispatch_queue_create函数生成的Concurrent Dispathc Queue一起使用。

dispatch_queue_tqueue =dispatch_queue_create("id=929",DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{

[NSThreadsleepForTimeInterval:6];

NSLog(@"reading01");

});

dispatch_async(queue, ^{

[NSThreadsleepForTimeInterval:3];

NSLog(@"reading02");

});

dispatch_async(queue, ^{

[NSThreadsleepForTimeInterval:2];

NSLog(@"reading03");

});

dispatch_async(queue, ^{

[NSThreadsleepForTimeInterval:9];

NSLog(@"reading04");

});

#warning dispatch_barrier_async会监控queue直到先加入队列的任务执行完才会执行自己加入的任务,并且会阻塞queue直到自己block中的任务执行完才会让后续任务执行。dispatch_barrier_async函数本身是异步的不会阻塞当前线程

dispatch_barrier_async(queue, ^{

[NSThreadsleepForTimeInterval:5];

NSLog(@"writing");

});

NSLog(@"走到dispatch_barrier_async函数下面了");

dispatch_async(queue, ^{

NSLog(@"reading05");

});

dispatch_async(queue, ^{

NSLog(@"reading06");

});

dispatch_async(queue, ^{

NSLog(@"reading07");

});

四、dispatch_set_target_queue

/**

dispatch_queue_create函数生成的Dispatch Queue不管是Serial Dispatch Queue还是Concurrent Dispatch Queue,都使用与默认优先级Global Dispatch Queue相同执行优先级的线程。

*/

dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("id=19", DISPATCH_QUEUE_SERIAL);

dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);

//变更优先级的函数dispatch_set_target_queue

/**

参数一:要变更优先级的Dispatch Queue

参数二:目标优先级Dispatch Queue,与目标Dispatch Queue有相同的执行优先级

*/

dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);

五、dispatch_after

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

/**

参数一:指定时间用的 dispatch_time_t 类型值

参数二:在哪个队列执行

参数三:要执行的任务

*/

dispatch_after(time, dispatch_get_main_queue(), ^{

NSLog(@"waited at least three seconds.");

});

#warning dispatch_after函数并不是指定延迟后执行block里的任务,而是指定时间后把任务加进队列

六、dispatch_apply

/**

dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API。该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束。

*/

/**

参数一: 重复次数

参数二: 追加对象的Dispatch Queue

参数三: 追加的处理

与目前为止所出现的例子不同,第三个参数的Block为带有Block的参数。这是为了按第一个参数重复追加Block并区分各个Block而使用。

*/

//    dispatch_apply(10, queue, ^(size_t index) {

//        NSLog(@"%zu",index);

//    });

//#warning dispatch_apply会阻塞当前线程直到queue中新添加的10次任务执行结束才会取消阻塞。要想10次任务依次执行可以把queue换成一个串行队列

//    NSLog(@"done");

/* 例如要对NSArray类对象的所有元素执行处理时,不必一个一个编写for循环部分。*/

//    NSArray *array = [NSArray arrayWithObjects:@1,@2,@3,@4,@5,@6,@7,@8,@9,@10,@11, nil];

//    dispatch_apply([array count], queue, ^(size_t index) {

//        NSLog(@"%zu: %@", index, [array objectAtIndex:index]);

//    });

/* 由于dispatch_apply函数也与dispatch_sync函数相同,会等待处理执行结束,因此推荐在dispatch_async函数中非同步地执行dispatch_apply函数 */

NSArray *array = [NSArray arrayWithObjects:@1,@2,@3,@4,@5,@6,@7,@8,@9,@10,@11, nil];

dispatch_async(queue, ^{

//Global Dispatch Queue 等待dispatch_appply函数中全部处理执行结束

dispatch_apply([array count], queue, ^(size_t index) {

//并列处理包含在NSArray对象的全部对象

NSLog(@"%zu: %@", index, [array objectAtIndex:index]);

});

//dispatch_apply函数中的处理全部执行结束

/* 在Main Dispatch Queue中非同步执行 */

dispatch_async(dispatch_get_main_queue(), ^{

/*

在Main Dispatch Queue中执行处理

用户界面更新等

*/

NSLog(@"done");

});

});

七、dispatch_suspend / dispatch_resume

/**

当追加大量处理到Dispatch Queue时,在追加处理过程中,有时希望不执行已经追加的处理。例如演算结果被Block截获时,一些处理会对这个演算结果造成影响。

在这种情况下,只要挂起Dispatch Queue即可。当可以执行时再恢复。

*/

dispatch_queue_t queue1 = dispatch_queue_create("id = 88", DISPATCH_QUEUE_CONCURRENT);

//dispatch_suspend函数挂起指定的Dispatch Queue

dispatch_suspend(queue1);

//dispatch_resume函数恢复指定的Dispatch Queue

dispatch_resume(queue1);

#warning 这些函数对已经执行的处理没有影响。挂起后,追加到Dispatch Queue中尚未执行的处理在此之后停止执行。而恢复则使得这些处理能够继续执行。

八、Dispatch Semaphore

/**

当并行执行的处理更新数据时,会产生数据不一致的情况,有时应用程序还会异常结束。虽然使用Serial Dispatch Queue和dispatch_barrier_async函数可避免这类问题,但有必要进行更细粒度的排他控制。

*/

#pragma mark - 出错的代码

//    NSMutableArray *array = [[NSMutableArray alloc]init];

//    for (int i = 0; i < 100000; i++) {

//        dispatch_async(queue, ^{

//            [array addObject:[NSNumber numberWithInt:i]];

//        });

//    }

/*因为该源代码使用Global Dispatch Queue更新NSMutableArray类对象,所在执行后由于内存错误导致应用程序结束的概率很高。此时应使用Dispatch Semaphore。

Dispatch Semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号。计数为0时等待,计数为1或大于1时,减1而不等待。

*/

//    //设置参数为1

//    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

//    //设置超是等待,即最长等待时间

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

//    /**

//    参数一: dispatch_semaphore_t对象

//    参数二: 最长等待时间,与dispatch_group_wait中的超时等待是一样的

//    */

//    long result = dispatch_semaphore_wait(semaphore, time);

//    if (result == 0) {

//

//    }else{

//

//    }

/** 用Dispatch Semaphore改写出错的代码

生成Dispatch Semaphore

Dispatch Semaphore的计数初始值设定为1

保证可访问NSMutableArray类对象的线程同时只能有一个。

*/

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

NSMutableArray *array = [[NSMutableArray alloc]init];

for (int i = 0; i < 100000; i++) {

dispatch_async(queue, ^{

/**

等待Dispatch Semaphore

一直等待,直到Dispatch Semaphore的计数值达到大于等于1

*/

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

/**

由于Dispatch Semaphore的计数值达到或大于等于1

所以将Dispatch Semaphore的计数值减去1

dispatch_semaphore_wait函数执行返回

即执行到此时的Dispatch Semaphore的计数恒为0

由于可访问NSMutableArray类对象的线程只有1个

因此可安全地进行更新

*/

[array addObject:[NSNumber numberWithInt:i]];

NSLog(@"%d",i);

/**

排他控制处理结束,

所以通过dispatch_semaphore_signal函数将Dispatch Semaphore的计数值加1

如果有通过dispatch_semaphore_wait函数

等待Dispatch Semaphore的计数值增加的线程,

就由最先等待的线程执行。

*/

dispatch_semaphore_signal(semaphore);

});

}

九、dispatch_once

/* dispatch_once函数是保证在应用程序执行中只执行一次指定处理的API。 */

static int initialized = NO;

if (initialized == NO) {

//初始化

initialized = YES;

}

#warning 上面的源代码在大多数情况下也是安全的,但是在多核CPU中,在正在更新表示是否初始化的标志变量时读取,就有可能多次执行初始化处理。而dispatch_once函数就没有这个问题,通过dispatch_once函数生成的代码即使在多线程环境下执行,也可保证百分百安全。

/* 上面的代码可以简化 */

static dispatch_once_t pred;

dispatch_once(&pred, ^{

//初始化

});

-----------------------我是无趣的分割线---------------------

上面这么枯躁的东西都能坚持看完,到此已经证明你有编程的资质!祝你编程之路一路顺风!!!

你可能感兴趣的:(多线程GCD.)