多线程GCD

2017-03-10  本文已影响32人  仰天风吹雪

什么是GCD

全称是Grand Central Dispatch
纯C语言,提供了非常多强大的函数
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)

GCD有三种队列:

//获取主队列
 dispatch_queue_t mainQueue = dispatch_get_main_queue();
 //开启异步线程, 在全局队列中执行     
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          [self dosomething]; 
  });
//(1)使用dispatch_get_global_queue函数获得全局的并发队列
    //参数1: 优先级
 //参数2: 暂时无用参数 (传0)
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 1.GCD有两种任务派发方式: 同步(`dispatch_sync`)和异步(`dispatch_async`)
// 这小段代码有问题, 出现了线程死锁
// 提示: 下面的代码在主线程`main_thread`中执行
   - (void)viewDidLoad{
         dispatch_queue_t queue = dispatch_get_main_queue();
         dispatch_sync(queue, ^{});
     }

dispatch_async(queue, block):直接回到调用线程(不阻塞调用线程)。
dispatch_sync(queue, block): 阻塞调用线程,等待 block() 执行结束,回到调用线程。
两者的区别:就是看是否阻塞调用线程。

同步和异步主要影响:能不能开启新的线程
同步:只是在当前线程中执行任务,不具备开启新线程的能力
异步:可以在新的线程中执行任务,具备开启新线程的能力
并发和串行主要影响:任务的执行方式
并发:允许多个任务并发(同时)执行
串行:一个任务执行完毕后,再执行下一个任务

主队列的特点是发现当前主线程中有任务在执行, 那么主队列会暂停调用队列中的任务, 直到队列中空闲为止。所以在主队列(main_thread)中调用dispatch_sync时会出现线程死锁
2.串行队列(Serial Dispatch Queue)和并行队列(Concurrent Dispatch Queue)区别是: 串行队列使用一个线程执行
并发功能只有在异步(dispatch_async)函数下才有效
创建串行队列(队列类型传递NULL或者DISPATCH_QUEUE_SERIAL)
dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", NULL);

  -(void)serialDisPatchQueue{
        dispatch_queue_t queue1 =  dispatch_queue_create("com.chuixue.download", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue1, ^{
             NSLog(@"download1---%@---", [NSThread currentThread]);
        });
            
        dispatch_queue_t queue2 =  dispatch_queue_create("com.chuixue.download", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue2, ^{
             NSLog(@"download2---%@---", [NSThread currentThread]);
        });

        dispatch_queue_t queue3 =  dispatch_queue_create("com.chuixue.download", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue3, ^{
             NSLog(@"download3---%@---", [NSThread currentThread]);
        });

        dispatch_queue_t queue4 =  dispatch_queue_create("com.chuixue.download", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue4, ^{
             NSLog(@"download4---%@---", [NSThread currentThread]);
        });
}
 download3---<NSThread: 0x60000007ad40>{number = 5, name = (null)}---
 download2---<NSThread: 0x608000266200>{number = 4, name = (null)}--- 
 download1---<NSThread: 0x608000260e40>{number = 3, name = (null)}---
 download4---<NSThread: 0x60800007b580>{number = 6, name = (null)}---

多个线程更新相同资源导致数据竞争时使用串行队列Serial Dispatch Queue
* 异步函数+并发队列
- (void)asyncConcurrent{
// 1.创建队列
/*
第一个参数: C语言的字符串, 标签
第二个参数: 队列的类型
DISPATCH_QUEUE_CONCURRENT: 并发
DISPATCH_QUEUE_SERIAL: 串行
*/
dispatch_queue_t concurrent_queue = dispatch_queue_create("com.chuixue.download", DISPATCH_QUEUE_CONCURRENT);

            // 2.封装任务, 添加任务到队列中
            dispatch_async(concurrent_queue, ^{
                NSLog(@"download1---%@", [NSThread currentThread]);
            });

            dispatch_async(concurrent_queue, ^{
                NSLog(@"download2---%@", [NSThread currentThread]);
            });

            dispatch_async(concurrent_queue, ^{
                NSLog(@"download3---%@", [NSThread currentThread]);
            });
             NSLog(@"Running on main Thread");
        }

    打印结果
        NSLog(@"Running on main Thread");
        download2---<NSThread: 0x600000272700>{number = 4, name = (null)}
        download1---<NSThread: 0x608000263680>{number = 3, name = (null)}
        download3---<NSThread: 0x608000267ec0>{number = 5, name = (null)}
    1.在dispatch_async使用的Thread是不同线程, 因为他们的指针地址完全不同, number也可以作为线程的标识, 说明开启了多条线程
     2.`Running on main Thread`这句话并没有在最后打印, 说明开启了新的线程
     3.打印结果并不是顺序的, 说明是并发执行的

-ps: 但是工作中并行的情况不是一整块需要使用的话,也不会选择这种方式,所以它在工作中的应用相比如下的方式会更少一些
* 异步函数+串行队列
- (void)asyncSerial
{
dispatch_queue_t seaial_queue = dispatch_queue_create("download", DISPATCH_QUEUE_SERIAL);

            dispatch_async(seaial_queue, ^{
                NSLog(@"download1---%@", [NSThread currentThread]);
            });

            dispatch_async(seaial_queue, ^{
                NSLog(@"download2---%@", [NSThread currentThread]);
            });

            dispatch_async(seaial_queue, ^{
                NSLog(@"download3---%@", [NSThread currentThread]);
            });

            NSLog(@"Running on main Thread");

        }            

打印结果
Running on main Thread
download1---<NSThread: 0x608000261900>{number = 4, name = (null)}
download2---<NSThread: 0x608000261900>{number = 4, name = (null)}
download3---<NSThread: 0x608000261900>{number = 4, name = (null)}
1.在dispatch_async使用的是同一个Thread, 因为number和地址完全相同
2.打印顺序和代码相同, 符合FIFO原则
3.Running on main Thread这句话并没有在最后打印, 说明开启了新的线程
-串行队列可以保障消息的顺序,在直播间聊天/顺序操作数组等事件时都非常有用

 * 同步函数+并发队列

(不会开启新线程,并发执行任务失效!)
- (void)syncConcurrent
{
dispatch_queue_t concurrent_queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);

            dispatch_sync(concurrent_queue, ^{
                NSLog(@"download1------%@", [NSThread currentThread]);
            });

            dispatch_sync(concurrent_queue, ^{
                NSLog(@"download2------%@", [NSThread currentThread]);
            });

            dispatch_sync(concurrent_queue, ^{
                NSLog(@"download3------%@", [NSThread currentThread]);
            });
            NSLog(@"Running on main Thread");

        }

打印结果
download1------<NSThread: 0x608000066a40>{number = 1, name = main}
download2------<NSThread: 0x608000066a40>{number = 1, name = main}
download3------<NSThread: 0x608000066a40>{number = 1, name = main}
2017-03-10 18:28:16.331 Import[67018:21512467] Running on main Thread
在同步函数+并发队列时不会开启线程, 在主线程中执行, 任务是串行执行的

 * 同步函数+串行队列

       - (void)syncSerial
       {
           dispatch_queue_t concurrent_queue = dispatch_queue_create("download", DISPATCH_QUEUE_SERIAL);

           dispatch_sync(concurrent_queue, ^{
               NSLog(@"download1------%@", [NSThread currentThread]);
           });

           dispatch_sync(concurrent_queue, ^{
               NSLog(@"download2------%@", [NSThread currentThread]);
           });

           dispatch_sync(concurrent_queue, ^{
               NSLog(@"download3------%@", [NSThread currentThread]);
           });
           NSLog(@"Running on main Thread");

       }

打印结果

download1------<NSThread: 0x600000079740>{number = 1, name = main}
download2------<NSThread: 0x600000079740>{number = 1, name = main}
download3------<NSThread: 0x600000079740>{number = 1, name = main}
Running on main Thread

说明并没有开启新的线程, 任务是串行执行

IvAv6fy.png!web.png
  1. 队列组
- (void)group{
// 创建队列
dispatch_queue_t queue = dispatch_queue_create(0, 0);
// 创建队列组
dispatch_group_t group = dispatch_group_create();

// 1) 封装任务
// 2) 把任务添加到队列中
// 3) 会监听任务的执行情况, 通知group
dispatch_group_async(group, queue, ^{
    NSLog(@"1-----%@", [NSThread currentThread]);
});

dispatch_group_async(group, queue, ^{
    NSLog(@"2-----%@", [NSThread currentThread]);
});

dispatch_group_async(group, queue, ^{
    NSLog(@"3-----%@", [NSThread currentThread]);
});

// 拦截通知, 当队列组中所有的任务都执行完毕的时候进入会进入到下面的方法
dispatch_group_notify(group, queue, ^{
    NSLog(@"-------dispatch_group_notify--------");
});

NSLog(@"---end---");
}

打印的结果
1-----<NSThread: 0x600000270c80>{number = 3, name = (null)}
---end---
2-----<NSThread: 0x600000270c80>{number = 3, name = (null)}
3-----<NSThread: 0x600000270c80>{number = 3, name = (null)}
-------dispatch_group_notify--------
根据end打印的位置可以看出 dispatch_group_notify 这个函数本身是异步的

应用场景
  1. 下载图片1
  1. 下载图片2
  2. 合成图片并显示图片
    因为3需要等待1和2执行完才能执行, 那么可以把1和2两个线程放在队列组中, 等到队列组完成再执行3
- (void)group1{
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    // 下载图片1, 开启子线程
    dispatch_group_async(group, queue, ^{
        
        NSURL *url = [NSURL URLWithString:@""];
        NSData *imageData = [NSData dataWithContentsOfURL:url];
        self.image1 = [UIImage imageWithData:imageData];
        
    });
    
    // 下载图片2, 开启子线程
    dispatch_group_async(group, queue, ^{
        
        NSURL *url = [NSURL URLWithString:@""];
        NSData *imageData = [NSData dataWithContentsOfURL:url];
        self.image2 = [UIImage imageWithData:imageData];
        
        
    });
    
    // 合并图片
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        
        UIGraphicsBeginImageContext(CGSizeMake(200, 200));
        
        [self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
        
        [self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
        
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        
        UIGraphicsEndImageContext();
        
        // 更新UI
        NSLog(@"UI---%@", [NSThread currentThread]);
        self.imageView.image = image;
    });

}

如果合成图片显示在界面上的操作并不耗时也可以写在主线程中

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    
    UIGraphicsBeginImageContext(CGSizeMake(200, 200));
    
    [self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
    
    [self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
    
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    
    UIGraphicsEndImageContext();
    
    // 更新UI
    NSLog(@"UI---%@", [NSThread currentThread]);
    self.imageView.image = image;
});

这两种的区别是如果是合成图片比较耗时可以使用上面的方法在子线程中执行。下面dispatch_get_main_queue()是全局并发队列, dispatch_group_t group = dispatch_group_create();是创建的并发队列, 两种的区别

  • 全局并发队队列在整个应用程序中本身是默认存在的, 并且对应有高优先级、默认优先级、低优先级和后台优先级一种四个并发队列, 我们只是拿来其中的一个直接拿来用, 而Create函数是真实的从头开始去创建一个队列。

队列组还可以解决这样一些问题, 比如因为一些服务器之类的原因, 一个界面可能有会两个接口, 在两个接口请求之前需要显示转圈, 只有等待两个接口都请求结束的时候才能停止转圈刷新这个界面, 这个时候就可以使用队列组, 在dispatch_group_notify方法中刷新界面

GCD 的其他用法

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 2秒后执行这里的代码...
});
使用dispatch_apply函数能进行快速迭代遍历
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index){
    // 执行10次代码,index顺序不确定
});
上一篇下一篇

猜你喜欢

热点阅读