多线程之GCD学习整理

字数 2401阅读 177

把GCD的资料重新整理了一下 ( ̄. ̄)

GCD全称Grand Central Dispatch
是Apple开发的一个多核编程的较新的解决方法。
它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。
它是一个在线程池模式的基础上执行的并行任务。
纯C语言 提供了非常多强大的函数
在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用。

设计
GCD是苹果公司为多核并行运算提出的解决方案
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
GCD会自动利用更多的CPU内核(比如双核、四核)

GCD是一个替代诸如NSThread等技术的很高效和强大的技术。
GCD完全可以处理诸如数据锁定和资源泄漏等复杂的异步编程问题。
GCD的工作原理是让一个程序,根据可用的处理资源,安排他们在任何可用的处理器核心上平行排队执行特定的任务。这个任务可以是一个功能或者一个程序段。
GCD创建的队列是轻量级的,苹果声明一个GCD的工作单元需要由15个指令组成。也就是说创造一个传统的线程很容易的就会需要几百条指令。
GCD中的一个任务可被用于创造一个被放置于队列的工作项目或者事件源。如果一个任务被分配到一个事件源,那么一个由功能或者程序块组成的工作单元会被放置于一个适当的队列中。苹果公司认为GCD相比于普通的一个接一个的执行任务的方式更为有效率。

任务(block ):要执行的操作


任务是由block封装的

dispatch_block_t task

void(^myBlock)()=^{
//任务/想要做的事情 
}

队列(queue): 用来存放任务


队列不是线程 队列中存放的任务最后都要由线程来执行
队列的原则:(FIFO) First In First Out 先进先出 后进后出

队列类型
串行队列:Serial Dispatch Queue
存放顺序执行的任务
线程池只提供一个线程用来执行任务
一个任务执行完毕 在执行下一个任务
并发队列:Concurrent Dispatch Queue
存放想要同时并发执行的任务 可以开启多线程 具体数量由底层GCD负责
线程池可以提供多个线程来执行任务 具体数量由底层GCD负责

我以前在这里打了个问号 回头却忘了有什么疑问 铭记教训 以后 有疑问 就把自己的疑问写清楚

并发执行 性能高 执行顺序不固定 费电因为绝大多数会使用全局队列,全局队列本身就是并发队列

dispatch_queue_create
//串行队列
dispatch_queue_t  SerialQueue;
//并发队列
dispatch_queue_t  ConcurrentQueue;
//后面这个参数可以不写的  默认填NULL就是串行
SerialQueue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL);
ConcurrentQueue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);

系统队列

系统提供了两个队列
** 主队列**:dispatch_get_main_queue
属于串行队列
负责调度主线程度的任务,没有办法开辟新的线程。
所以主队列下的任务不管是异步任务还是同步任务都不会开辟线程
任务只会在主线程顺序执行。
主队列异步任务:现将任务放在主队列中,但是不是马上执行,等到主队列中的其它所有除我们使用代码添加到主队列的任务的任务都执行完毕之后才会执行我们使用代码添加的任务。
主队列同步任务:容易阻塞主线程,所以不要这样写。原因:我们自己代码任务需要马上执行,但是主线程正在执行代码任务的方法体,因此代码任务就必须等待,而主线程又在等待代码任务的完成好去完成下面的任务,因此就形成了相互等待。整个主线程就被阻塞了。

全局队列dispatch_get_global_queue
属于并发队列
一般情况下 并发任务都可以放在全局并发队列中

全局队列和并发队列的区别:
1 全局队列没有名字,但是并发队列有名字。有名字可以便于查看系统日志
2 全局队列是所有应用程序共享的。
3 在mrc的时候,全局队列不用手动释放,但是并发队列需要。

dispatch_queue_t
dispatch_queue_t mymainQueue;
dispatch_queue_t myglobalQueue;
mymainQueue = dispatch_get_main_queue();
myglobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

/**参数说明:
参数1:代表该任务的优先级,默认写0就行,不要使用系统提供的枚举类型,因为ios7和ios8的枚举数值不一样,使用数字可以通用。
DISPATCH_QUEUE_PRIORITY_HIGH  2
DISPATCH_QUEUE_PRIORITY_DEFAULT 0
DISPATCH_QUEUE_PRIORITY_LOW (-2)
DISPATCH_QUEUE_PRIORITY_BACKGROUND (-32768)
参数2:苹果保留关键字,一般写0或NULL
所以也可以写为myglobalQueue = dispatch_get_global_queue(0, 0);
*/  

同步异步


sync同步运行
如果是同步执行 队列会等任务结束后 再调度后续的任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
async 异步运行
dispatch_sync(dispatch_queue_t queue, ^(void)block )
*dispatch_block_t 就是无返回值 无参数的block

常用用法介绍


1.经典用法(子线程下载(耗时操作),主线程刷新UI)

dispatch_async(dispatch_get_global_queue(0,0), ^{
//执行耗时的异步操作…
    dispatch_async(dispatch_get_main_queue(), ^{
      //回到主线程,执行UI刷新操作
    });
});

2.GCD的延时执行
延时是延时任务加入到队列的时间 不是延时任务执行的时间

//1.延时不是一定时间后执行相应的任务,而是一定时间后,将任务加入到队列中(队列里面再分配执行的时间)    
//2.主线程 RunLoop 1/60秒检测时间,追加的时间范围 3s~(3+1/60)s   
//3.在哪个线程执行,跟队列类型有关

//dispatch_after(一定时间后,将执行的操作加入到队列中)
//dispatch_time_t when 指定时间        
/* NSEC_PER_SEC 秒     
* NSEC_PER_MSEC 毫秒     
* NSEC_PER_USEC 微秒     
*/    
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull*NSEC_PER_SEC);    
dispatch_queue_t que = dispatch_queue_create("h", DISPATCH_QUEUE_SERIAL);
//1.第一种用法       
dispatch_after(time, dispatch_get_main_queue(), ^{        
   NSLog(@"第一种延时 code to be executed on the main queue after delay");   
});        
//2.第二种用法   
//dispatch_function_t work 执行的c语言方法   
dispatch_after_f(time, que, NULL, fun1);

//3.第三种用法    
dispatch_after(time, que, ^{ 
   NSLog(@"第三种延时 code to be executed on the main queue after delay");    
});         

------代表方法外的分割-----
void fun1(){    
   NSLog(@"第二种延时 code to be executed on the main queue after delay");
} 

3.异步执行:

//dispatch_async +全局并发队列(可以开启多条线程)
//dispatch_async +自己创建的串行队列(开启一条线程)
dispatch_async(dispatch_get_global_queue(0, 0), ^{
      // something
 });
 
 dispatch_async(dispatch_get_main_queue(), ^{
      // 主队列异步
 });
 一次性执行:
static dispatch_once_t onceToken;
 dispatch_once(&onceToken, ^{
     // code to be executed once
 });

4.一次性执行和多次执行

/*
一次执行:dispatch_once
 作用:在多线程的情况下,同样能够保证指定的代码块只被执行一次
 快捷键:
 应用场景:单例设计模式
 */
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    NSLog(@"块代码只能执行一次");
});
/*
多次执行:dispatch_apply
以指定的次数将指定的Block加入到指定的队列中 并等待队列中操作全部完成.
当指定队列为串行时 有序单线程执行 
当指定队列为并发队列时 多线程无序执行
*/
dispatch_queue_t q1 = dispatch_queue_create("a1", DISPATCH_QUEUE_CONCURRENT);/
dispatch_apply(3, q1, ^(size_t index) {
    NSLog(@"重要的事情说三遍 第%zu遍 %@",index,[NSThread currentThread]);
});

**5.dispatch_group分组 **

/**
 作用:所有任务执行完成之后,统一通知用户 
可以实现监听一组任务是否完成 完成后得到通知执行其他的操作 
不包括延时任务 因为延时是任务放进队列的时间 
 */
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t q1 = dispatch_queue_create("a1", DISPATCH_QUEUE_SERIAL);
dispatch_block_t t1 = ^{
        NSLog(@"任务1");
    };
dispatch_async(q1, t1);
dispatch_group_async(group, q1, ^{
        NSLog(@"group1");
    });
dispatch_group_async(group, q1, ^{
        NSLog(@"group2");
    });
dispatch_group_notify(group, q1, ^{
        NSLog(@"end");
    });

6.dispatch_barrier_async的使用

/*
dispatch_barrier_async是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
*/
 dispatch_queue_t queue = dispatch_queue_create("aa", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"dispatch_async1");
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"dispatch_async2");
    });
    dispatch_barrier_async(queue, ^{
        
        NSLog(@"dispatch_barrier_async1");
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_barrier_async2");
        
    });
    dispatch_async(queue, ^{
        NSLog(@"dispatch_async3");
    });  

7、dispatch_set_target_queue
使用dispatch_set_target_queue将多个串行的queue指定到了同一目标,那么着多个串行queue在目标queue上就是同步执行的,不再是并行执行。

Important
If you modify the target queue for a queue, you must be careful to avoid creating cycles in the queue hierarchy.

dispatch_queue_t tq = dispatch_queue_create("tq",DISPATCH_QUEUE_SERIAL); 
dispatch_queue_t q1 = dispatch_queue_create("q1", DISPATCH_QUEUE_SERIAL); 
dispatch_queue_t q2 = dispatch_queue_create("q2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t q3 = dispatch_queue_create("q3", DISPATCH_QUEUE_SERIAL);
    
    dispatch_set_target_queue(q1, tq);
    dispatch_set_target_queue(q2, tq);
    dispatch_set_target_queue(q3, tq);
  
    dispatch_async(q1, ^{
        NSLog(@"1 in %@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:3.f];
        NSLog(@"1 out%@",[NSThread currentThread]);
    });
   
    dispatch_async(q2, ^{
        NSLog(@"2 in %@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:2.f];
//        dispatch_suspend(queue1);
        NSLog(@"2 out %@",[NSThread currentThread]);
    });
    dispatch_async(q3, ^{
        NSLog(@"3 in %@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.f];
        NSLog(@"3 out %@",[NSThread currentThread]);
    }); 

8.信号量 dispatch_semaphore

dispatch_semaphore是GCD用来同步的一种方式 相关函数有三个

dispatch_semaphore_create 
dispatch_semaphore_signal 
dispatch_semaphore_wait 

(1)dispatch_semaphore_create
dispatch_semaphore_t dispatch_semaphore_create(long value);
传入的参数为long,输出一个dispatch_semaphore_t类型且值为value的信号量
*这里的传入的参数value必须大于或等于0 否则dispatch_semaphore_create会返回NULL。

(2)dispatch_semaphore_signal
long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
这个函数会使传入的信号量dsema的值加1
返回值为long类型
当返回值为0时表示当前并没有线程等待其处理的信号量,其处理的信号量的值加1即可。
当返回值不为0时,表示其当前有一个或多个线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级时,唤醒优先级最高的线程;否则随机唤醒)

(3) dispatch_semaphore_wait
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
这个函数会使传入的信号量dsema的值减1;
如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;
如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t 不能直接传入整形或float型数)
如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。
如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。
设置timeout时,比较有用的两个宏:
DISPATCH_TIME_NOW 表示当前;
DISPATCH_TIME_FOREVER 表示遥远的未来;
一般可以直接设置timeout为这两个宏其中的一个,或者自己创建一个dispatch_time_t类型的变量。
返回值为long型。当其返回0时表示在timeout之前,该函数所处的线程被成功唤醒。当其返回不为0时,表示timeout发生。

dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [NSMutableArray array];
    
    for (int index = 0; index < 10000; index++) {
        dispatch_async(queue, ^(){  
            dispatch_semaphore_wait(semaphore, timeout); 

            NSLog(@"addd :%d", index);
            [array addObject:[NSNumber numberWithInt:index]];
            
            dispatch_semaphore_signal(semaphore);
        });
    }

GCD 死锁


-(void)viewDidLoad { [super viewDidLoad]; NSLog(@"1"); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"2"); }); NSLog(@"3"); }
我们来一步一步解析。首先dispatch_sync 是同步的,它会造成一个后果那就是阻塞主线程,并且会一直会等待block,而block放入到了主线程队列dispatch_get_main_queue()中,这是一个FIFA队列,就是先进先出的队列。他在等待主线程执行。那么一目了然了。我用伪代码敲出来大家就知道了
MainThread { dispatch_get_main_queue(){ syncblock(); } }
MainThread等待dispatch_sync,dispatch_sync等待blockblock等待mainquen,maiden等待MainThread,而MainThread等待dispatch_sync。这样就形成了一个死循环。俗称DeadLock死锁。

NSLog(@"1"); dispatch_async(dispatch_get_main_queue(), ^{ while (1) { NSLog(@"2"); } }); dispatch_async(dispatch_get_main_queue(), ^{ while (1) { NSLog(@"3"); } }); NSLog(@"4");
结果大家一运行就知道,
为什么第一个只输出1和4 和2。我们来解析问题的代码
这里面有两部异步block但是放到了主线程队列里面,但是block里面执行的是一个 while (1) 的死循环
我们一步一步解析。 主线程队列是个FIFO 也就是先进先出,先进的完了才执行第二个。而当
^{ while (1) { NSLog(@"2"); } }
放入到主线程队列后,它就永远执行不完,永远不会退出,所以
^{ while (1) { NSLog(@"3"); }
这个只能永远的等待,而这两个block又是异步的不会阻塞主线程所以主线程的输出依然木有问题。


关于命名

所有带t结尾的 都是用来声明变量的对象
所有带f结尾的 都是面向C的function


GCD官方文档OC版
GCD学习总结
Grand Central Dispatch
SDK源码解读系列:《iOS与OSX多线程和内存管理》书摘之GCD内部实现(一)

推荐阅读更多精彩内容