首页 > 代码库 > 小伙,多线程(GCD)看我就够了,骗你没好处!

小伙,多线程(GCD)看我就够了,骗你没好处!

      多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理(Chip-level multithreading)或同时多线程(Simultaneous multithreading)处理器.再一个程序中,这些独立运行的程序片段叫做线程(Thread).利用它编程的概念就叫做多线程.具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个一个线程,今儿提升整体处理性能.

一.什么叫进程? 

进程是指在系统中正在运行的一个应用程序. 每个进程之间是独立的.每个进程均运行在其赚佣且受保护的内存空间内.

技术分享

二.什么是线程? 

1.用来执行进程的任务的叫线程.

2.每个进程必须至少要有一个线程.

3.线程是进程的基本执行单元.

4.一个进程的所有任务都在线程中执行.

5.一个程序只有一个进程,一个进程可能会有一个或者多个进程.进程包含线程.

6.主线程是系统开辟的,其他任何线程都是手动开辟的.

 

 

三.线程的串行和并行分别是什么? 

1.串行.

每一个线程同一时间内只能执行一个任务.就像Boss指挥一个人做完A事情,再做B事情,最后做C事情.

技术分享

2.并行.

但是因为时间原因,Boss嫌一个人来做事件A,B,C时间太长.想要同时指挥多个人一起来做这三件事件.

技术分享

四.多线程. 

三个人一起完成这三件事用专业术语将就叫多线程.

1.多线程的原理.

同一时间,CPU只能处理一条线程,只有一个线程在工作.但是CPU如果快速的在多个线程之间切换的话,就能让多条线程同时执行.如果CPU切换的比较快的话,可以看成多个线程并发执行.但是CPU的工作能力毕竟有限,同时执行很多个线程,每条线程被切换到的频率就会降低,时间就会变长.所以要合理使用多线程.

2.多线程的优缺点

多线程的优缺点

优点                                      

缺点

1.能适当的提高传程序的执行效率

2. 能适当的提高资源的利用效率

1. 开启多线程需要占用一定的内存空间,主线程占用1M,子线程占用512KB.

2. 线程越多,CPU在调度线程上的开销就越大.

3. 程序设计更复杂.比如:线程之间的通讯,多线程的数据共享等

 

 

 

 

 

 

 

 

 

3. 多线程在开发中的应用

主线程:一个iOS程序运行后,默认会开启1条线程,称为"主线程"或者"UI线程". 在iOS中除了主线程,其他子线程都是独立于Cocoa Touch的,所以只有主线程可以更新UI界面.

主线程的作用: 显示/刷新UI界面,处理UI事件(点击事件,滚动事件,拖拽事件等)

注意点: 不用将刷新比较耗时的放到主线程中.耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验.

4. 多线程的使用.

如果我有一个个按钮A和一个UITextView. A按钮在当前线程(UI线程)下做一个for循环(循环10万次输出)点击完A按钮,立即拖拽UITextView,许久后才有反应.

技术分享技术分享

5.任务的概念.

有两种执行方式:同步执行和异步执行.区别是是否会创建新的线程.

(1) 同步执行(sync): 会阻塞当前线程并等待Block执行完毕,然后再当前线程才会继续往下执行. 会尽可能的在当前线程派发任务,单如果在其他队列往主队列中同步派发,任务会在主线程中执行.

(2) 异步执行(async): 当前线程继续执行,不会阻塞当前线程. 不一定会新建一个线程,例如在主线程异步派发到主线程,派发依旧是异步线程的,任务也会在主线程中执行.

同步异步的区别,不在于是否会开辟一个线程,在于派发方法是否需要等待Block完成后才返回.

6.队列的概念. 

用于存放任务.分为串行队列和并行队列.

(1)串行队列:放到串行队列中的任务,GCD会FIFO(先进先出)的取出一个,执行一个,然后取出下一个,这样一个一个的执行.

(2)并行队列:放到并行队列中任务,GCD也会FIFO的取出来,但不同的是,取出来一个任务就会放到别的线程中去,燃火取出来另一个又放到另一个线程中.由于取的动作很快,可以忽略不计,看起来所有的任务都是一起执行的.不过需要注意,GCD会根据系统资源控制并行的数量,所以任务很多也不会把所有的任务都执行.

无论串行还是并行队列,任务启动顺序都是按照FIFO的,只是并发队列允许同一时间有多个任务都在执行.

 FIFO是 First Input First Output的缩写,先入先出队列,这是一种传统的按序执行方法,先进入的指令先完成并引退,跟着才执行第二条指令。

四.目前有四种多线程的实现方法. 

1.Pthreads.

基于C的,适合做跨平台的SDK.

2.NSThread.  

NSThread是轻量级的多线程开发,使用起来也并不复杂,但是使用NSThread需要自己管理线程生命周期.

目前我就用到[NSThread currentThread]获取当前线程.主要用于调试.

3.NSOperation & NSOperationQueue

4.GCD.

(1)这四总方式是随着iOS的发展逐渐引进的,所以后者比前者使用更加简单,并且GCD也是目前苹果官方比较推荐的(充分利用了多核运算性能).

(2)GCD的全拼 --> Grand Central Dispatch --> 集中调度 的意思吧,英语不好..... 是iOS开发的一个多核编程解决方案.会自己管理线程的生命周期(创建线程,调度任务,销毁线程),不需要自己手动管理,只要要求它做就行了.使用灵活方便.So GCD is the leading role of today.

(3)在GCD中一个操作是多线程还是单线程执行取决于当前队列类型和执行方法,只有队列类型为并行队列并且使用异步方法执行才能在多个线程中执行.

(4)串行队列是可以按照顺序执行的,并行队列的异步方法是无法按照顺序执行的.

(5)UI界面的更新最好采用同步方法,其他采用异步方法.

(6)GCD中多线程操作方法不需要使用@autoreleasepool,GCD会管理内存.

GCD相关的单词: 

queue--线程    thread--队列  diapatch--派遣

五.代码示例 

1.获取主线程

    /** 获取主线程
     1. 所有的刷新UI界面的任务都要在主线程执行.
     2. 将消耗时间的任务放在别的线程中出来,尽量不要在主线程中处理.
     */
    dispatch_queue_t main_queue = dispatch_get_main_queue();
    NSLog(@"main_queue:\n %@",main_queue);
/*! 
 * @function dispatch_get_main_queue                                            
 *
 * @abstract
 * Returns the default queue that is bound to the main thread.
 *
 * @discussion
 * In order to invoke blocks submitted to the main queue, the application must
 * call dispatch_main(), NSApplicationMain(), or use a CFRunLoop on the main
 * thread.
 *
 * @result
 * Returns the main queue. This queue is created automatically on behalf of
 * the main thread before main() is called.
 */
DISPATCH_INLINE DISPATCH_ALWAYS_INLINE DISPATCH_CONST DISPATCH_NOTHROW
dispatch_queue_t
dispatch_get_main_queue(void)
{
    return DISPATCH_GLOBAL_OBJECT(dispatch_queue_t, _dispatch_main_q);
}

翻译一波
dispatch_get_main_queue的功能
1. 摘要
返回绑定到主线程的默认队列
2. 讨论
为了请求blocks提交到主线程,主队列的申请必须呼叫到dispatch_main,NSApplicationMain(),或者用一个CFRunLoop.
3.结果
返回主线程.为了主队列能再main()之前被呼叫,这个队列应该被自动的创建.

dispatch_get_main_queue 也是一种dispatch_queue_t 

2.自己创建队列

    /** 自己创建的队列  dispatch_queue_create
     参数1: 第一个参数是标识符.用于DEBUG的时候标志唯一的队列,可以为空.
     参数2: 第二个参数用来表示创建的队列是串行的还是并行的.传入DISPATCH_QUEUE_SERIAL或者NULL表示创建的是串行队列.传入DISPATCH_QUEUE_CONCURRENT表示创建的并行队列. (SERIAL--> serial连续的/CONCURRENT--> concurrent,并发的,一致的)
     */
    
    // 创建串行队列
    dispatch_queue_t serialQueue = dispatch_queue_create(nil, NULL);
    NSLog(@"serialQueue:\n %@",serialQueue);

    // 创建并行队列: 这应该是唯一一个并行队列,只要是并行任务一般都加入到这个队列
    dispatch_queue_t concurrentQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"concurrentQueue:\n %@",concurrentQueue);

3.创建任务

    // 创建任务
    /** 同步任务 (sync)
     1. 会阻塞当前线程.
     */
    dispatch_sync( serialQueue, ^{
        for (int i = 0; i < 10000; i ++) {
            NSLog(@"同步任务: \n%@",[NSThread currentThread]);
        }
    });
    
    /** 同步任务 (async)
     1. 不会阻塞当前线程.
     */
    dispatch_async(serialQueue, ^{
        NSLog(@"异步任务: %@",[NSThread currentThread]);
    });

4.创建队列组

    //1. 创建队列组
    dispatch_group_t group = dispatch_group_create();
    
    /**2. 创建队列  dispatch_get_global_queue 会获取一个全局队列,我们姑且理解为系统为我们开启的一些全局线程。我们用priority指定队列的优先级,而flag作为保留字段备用(一般为0)。并行队列的执行顺序与其加入队列的顺序相同.
     #define DISPATCH_QUEUE_PRIORITY_HIGH 2
     #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
     #define DISPATCH_QUEUE_PRIORITY_LOW (-2)
     */
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //3. 多次使用队列中的方法执行任务,只有异步任务
    //3.1 执行三次循环
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 3; i ++) {
            NSLog(@"group-01 - %@",[NSThread currentThread]);
        }
    });
    //3.2 主队列执行8次循环
    dispatch_group_async(group, dispatch_get_main_queue(), ^{
        for (int i = 0; i < 8; i ++) {
            NSLog(@"group-02 - %@",[NSThread currentThread]);
        }
    });
    //3.3 执行5次循环
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i ++) {
            NSLog(@"group-03 - %@",[NSThread currentThread]);
        }
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
       NSLog(@"完成 - %@",[NSThread currentThread]);
    });

5. 死锁现象

(1)现象1

    NSLog(@"之前==> %@",[NSThread currentThread]);
    
    dispatch_sync(dispatch_get_main_queue(), ^{    
        NSLog(@"sync==> %@",[NSThread currentThread]);
    });
    
    NSLog(@"之后==> %@",[NSThread currentThread]);
    
    /** 解释
     1. 只会打印第一句:之前==> <NSThread: 0x7fe66b700610>{number = 1, name = main} ,然后主线程就卡死了,你可以在界面上放一个按钮,你就会发现点不了了。
     2. 打印完第一句,dispatch_sync(因为是一个同步任务,会阻塞当前的线程)会阻塞当前的主线程,然后把Block中的任务放到main_queue中,main_queue中的任务会被取出来放到主线程中执行,但主线程种鸽时候已经被阻塞了,所以Block种鸽的任务就不能完成,它不完成,dispatch_sync就会一直阻塞主线程.导致主线程一直卡死.这就是死锁现象.
     */

(2)现象2

    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    
    NSLog(@"输出1.之前==> %@",[NSThread currentThread]);
    
    dispatch_async(queue, ^{
        NSLog(@"输出2.sync之前==> %@",[NSThread currentThread]);
        
        dispatch_sync(queue, ^{
            NSLog(@"输出3.sync==> %@",[NSThread currentThread]);
        });
        NSLog(@"输出4.sync之后==> %@",[NSThread currentThread]);
    });
    
    NSLog(@"输出5.之后==> %@",[NSThread currentThread]);
    
    /** 解释
     1. 当前线程为默认的主线程
     2. 输出结果为,输出1,输出5和输出2 执行了输出.输出3和输出4没有被执行.
     3. 按照执行顺序分析.
        (1)我们创建的队列queue是一个串行队列(DISPATCH_QUEUE_SERIAL).串行队列的特点是,所持有的任务会取出一个执行一个.当前任务没有执行完,下一个任务不会被执行.
        (2)打印出输出1.
        (3)在queue队列中开启了一个异步任务(async).异步任务的特点是,当前的线程不会被阻塞.所以有了两条线程,一条是主线程中执行输出5.另一条是在新开辟的queue线程中执行输出2.
        (4)在开辟的queue线程中,又执行了一个同步的任务(sync),同步任务的特点是执行一个任务会阻塞当前的线程.当前的线程是queue,已经被阻塞了.又要求它去执行下一个任务.就造成了死锁现象.所以 sync 所在的线程被卡死了,输出3和输出4自然就不会打印了.
     */

 

 六.GCD常用方法 

1.延迟

 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        <#code to be executed after a specified delay#>
    });

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        // 你的代码

    });

 2.从其他线程回到主线程的方法

   dispatch_async(dispatch_get_main_queue(), ^{
        // 回到主线程之后,需要执行的代码
    });

  3. 一次性执行

 static dispatch_once_t onceToken;
 dispatch_once(&onceToken, ^{
     // code to be executed once
 });

 &onceToken的意思: &表示取地址符,这个是定义一个静态变量,然后再dispatch_once函数第一次运行时,写入数据,之后就不会再次写入,可以保证后面的block函数内部的代码只被执行一次.

4.自定义dispatch_queue_t

dispatch_queue_t urls_queue = dispatch_queue_create("blog.devtang.com", NULL);
 dispatch_async(urls_queue, ^{  
   // your code 
 });

5.程序在后台较长时间运行.

// AppDelegate.h文件
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundUpdateTask;

// AppDelegate.m文件
- (void)applicationDidEnterBackground:(UIApplication *)application
{
    [self beingBackgroundUpdateTask];
    // 在这里加上你需要长久运行的代码
    [self endBackgroundUpdateTask];
}

- (void)beingBackgroundUpdateTask
{
    self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        [self endBackgroundUpdateTask];
    }];
}

- (void)endBackgroundUpdateTask
{
    [[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
    self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}

 

 

技术支持:

简书.作者:伯恩的遗产. 地址:http://www.jianshu.com/p/0b0d9b1f1f19

博客园.作者:文顶顶. 地址:http://www.cnblogs.com/wendingding/p/3805088.html

 

小伙,多线程(GCD)看我就够了,骗你没好处!