多线程----GCD(牛逼的中枢调度器)

GCD的基本使用

需要经常使用的两个函数和队列

  • 同步函数dispatch_sync:立刻马上执行,如果没有执行完毕,后面的也不会执行
  • 异步函数dispatch_async:如果没有执行完毕,后面的也可以执行
  • 并发队列DISPATCH_QUEUE_CONCURRENT
  • 串行队列DISPATCH_QUEUE_SERIAL

创建队列方法

  • 方法1:自己创建一个新的线程
/**
       第一个参数: C语言的字符串,标签
       第二个参数: 队列的类型
            DISPATCH_QUEUE_CONCURRENT:并发队列
            DISPATCH_QUEUE_SERIAL:串行队列
     */
    dispatch_queue_t queue = dispatch_queue_create("asyncConcurrent", DISPATCH_QUEUE_CONCURRENT);
  • 方法二:获得全局并发队列 且并不是一个任务一条线程,是系统随机给你分配线程
// 获得全局并发队列:并不是一个任务一条线程,而是系统给你分配的线程
    /**
     第一个参数: 优先级
     第二个参数: 未来使用的,默认 0
     */
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

封装任务方法

 /**
     第一个参数: 队列
     第二个参数: 要执行的任务
     */
  • 方法一:异步函数
 dispatch_async(<#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)
  • 方法二:同步函数
dispatch_sync(<#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)

异步函数+并发队列

会开启多条线程,队列中的任务是并发(异步)执行 无顺序

// 异步函数+并发队列:会开启多条线程,队列中的任务是并发(异步)执行无顺序
- (void) asyncConcurrent{
    
    // 1.创建队列
    /**
       第一个参数: C语言的字符串,标签
       第二个参数: 队列的类型
            DISPATCH_QUEUE_CONCURRENT:并发队列
            DISPATCH_QUEUE_SERIAL:串行队列
     */
//    dispatch_queue_t queue = dispatch_queue_create("asyncConcurrent", DISPATCH_QUEUE_CONCURRENT);
    
    
    // 获得全局并发队列:并不是一个任务一条线程,而是系统给你分配的线程
    /**
     第一个参数: 优先级
     第二个参数: 未来使用的,默认 0
     */
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 2 .封装任务 -> 添加任务到队列中
    /**
     第一个参数: 队列
     第二个参数: 要执行的任务
     */
    dispatch_async(queue, ^{
        NSLog(@"asyncConcurrent1------%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"asyncConcurrent2------%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"asyncConcurrent3------%@",[NSThread currentThread]);
    });

}

异步�函数+串行队列

只会开启一条线程,任务是串行执行有顺序

// 异步�函数+串行队列:只会开启一条线程,任务是串行执行有顺序
- (void) asyncSerial{
    //1. 创建队列
    dispatch_queue_t queue = dispatch_queue_create("asyncSerial", DISPATCH_QUEUE_SERIAL);
    
    // 2. 封装任务
    dispatch_async(queue, ^{
        NSLog(@"asyncSerial1------%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"asyncSerial2------%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"asyncSerial3------%@",[NSThread currentThread]);
    });
}

同步函数+并发队列

不会开启线程,任务是串行执行

// 同步函数+并发队列:不会开启线程,任务是串行执行
- (void) syncConcurrent{
    
    // 1. 创建队列
    dispatch_queue_t queue = dispatch_queue_create("syncConcurrent", DISPATCH_QUEUE_CONCURRENT);
    
    // 2. 封装任务
    dispatch_sync(queue, ^{
        NSLog(@"syncConcurrent1------%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"syncConcurrent2------%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"syncConcurrent3------%@",[NSThread currentThread]);
    });
    
}

同步函数+串行队列

不会开启线程,任务是串行执行

// 同步函数+串行队列:不会开启线程,任务是串行执行
- (void) syncSerial{
    
    // 1. 创建队列
    dispatch_queue_t queue = dispatch_queue_create("syncSerial", DISPATCH_QUEUE_SERIAL);
    
    // 2. 封装任务
    dispatch_sync(queue, ^{
        NSLog(@"syncSerial1------%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"syncSerial2------%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"syncSerial3------%@",[NSThread currentThread]);
    });
}

特别的队列----主队列

特点:如果主队列中发现当前主线程有任务在执行,那么主队列会暂停调用队列中的任务,直到主线程空闲为止

异步函数+主队列

所有任务都在主线程中执行,不会开启线程

- (void) asyncMain{
    
    // 1. 获得主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 2.封装任务 - 异步函数
    dispatch_async(queue, ^{
        NSLog(@"asyncMain1------%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"asyncMain2------%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"asyncMain3------%@",[NSThread currentThread]);
    });
}
同步函数+主队列

死锁 如果该方法在子线程中执行,那么所有的任务都会在主线程中执行

// 同步函数+主队列:死锁
// 注意:如果该方法在子线程中执行,那么所有的任务都会在主线程中执行
- (void) syncMain{
    
    // 1. 获得主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 2.封装任务 - 同步函数
    dispatch_sync(queue, ^{
        NSLog(@"syncMain1------%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"syncMain2------%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"syncMain3------%@",[NSThread currentThread]);
    });
}

备注

dispatch_asyncdispatch_async_f的区别在于它们封装任务的方法,dispatch_async使用的是block而dispatch_async_f使用的则是C语言函数,其它是没有任何区别的,鉴于_f用得比较少 这里只做简单的了解

- (void) note{
    
    // 创建队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    // 区别:封装任务的方法(block  函数)
    
    int context = 10;
    /**
     * 第一个参数:队列
       第二个参数:参数 函数指针 注意传地址 不要传值
       第三个参数:要调用函数名称 C语言函数
     */
    dispatch_async_f(queue, &context, task);
}

void task (void * context){
    int * c = context;
    NSLog(@"%d",*c);
}

GCD线程间的通信

这里以下载网络图片为例子

- (void) downloadImage{
    // 创建子线程下载图片
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        // 1. 确定图片url
        NSURL * url = [NSURL URLWithString:@"https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1909600733,2304577724&fm=26&gp=0.jpg"];
        
        // 2.下载图片二进制数据到本地
        NSData * imageData = [NSData dataWithContentsOfURL:url];
        
        // 3.转换图片
        UIImage * image = [UIImage imageWithData:imageData];
        
        NSLog(@"当前线程------%@",[NSThread currentThread]);
        
        // 更新UI 回到主线程
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
            NSLog(@"当前线程------%@",[NSThread currentThread]);
        });
        
    });
}

GCD的常用函数

延迟执行

dispatch_after GCD 可以自由选择在哪个线程执行

// 方法3. GCD 可以自由选择在哪个线程执行
//    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    /**
     * 第一个参数:DISPATCH_TIME_NOW 从现在开始计算时间
       第一个参数:延迟2.0 GCD时间单位:纳秒  (需要 * 10的9次方 转换为秒)
     */
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), queue, ^{
        NSLog(@"GCD延迟执行------%@",[NSThread currentThread]);
        NSLog(@"------GCDend------");

    });

一次性代码

dispatch_once 注意:不能够在懒加载中使用

// 一次性代码
- (void) once{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"------noce------");
    });
}

栅栏函数

dispatch_barrier_async 当之前的任务执行完成之后才会执行后面的任务 栅栏函数不能使用全局并发队列

// 1 获取全局并发队列
    dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);
    
    // 1. 异步函数
    dispatch_async(queue, ^{
        NSLog(@"download1------%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"download3------%@",[NSThread currentThread]);
    });
    
    // 栅栏函数
    // 栅栏函数不能使用全局并发队列
    dispatch_barrier_async(queue, ^{
        NSLog(@"+++++++++++++++++++++");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"download2------%@",[NSThread currentThread]);
    });

GCD的快速迭代

dispatch_apply 因为在迭代的过程中会开启子线程 所以比一般的for循环迭代要快速很多

// GCD快速迭代 :开启
- (void) apply{
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    /**
     * 第一个参数:遍历次数
       第二个参数:队列(并发队列)
       第三个参数:索引
     */
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%zd-----------%@",index,[NSThread currentThread]);
    });
}

下面通过一个文件剪切的Demo来演示👇

- (void) moveFileWithGCD{
    // 1. 拿到文件路径
    NSString * form = @"/Users/ChangRJey/Desktop/修改前后对比图";
    
    // 2. 获得目标文件路径
    NSString * to = @"/Users/ChangRJey/Desktop/yoyoyo";
    
    // 3. 得到目录下面的所有文件
    NSArray * subPaths=  [[NSFileManager defaultManager] subpathsAtPath:form];
    
    NSLog(@"文件名------%@",subPaths);
    
    // 4. 遍历所有文件,然后执行剪切操作
    NSInteger count = subPaths.count;
    dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t index) {
        NSString * fullPath = [form stringByAppendingPathComponent:subPaths[index]];
        NSString * toFullPath = [to stringByAppendingPathComponent:subPaths[index]];
        
        NSLog(@"文件名------%@",fullPath);
        
        // 4.2 执行剪切操作
        /**
         * 第一个参数:要剪切的文件路径
         第二个参数:文件应该被存放的目标路径
         第三个参数:错误信息
         */
        [[NSFileManager defaultManager] moveItemAtPath:fullPath toPath:toFullPath error:nil];
        NSLog(@"%@------%@------%@",fullPath,toFullPath,[NSThread currentThread]);
    });
}

GCD队列组的基本使用

创建Group

dispatch_group_t

完成通知

dispatch_group_notify
  • 方法一 简化版
// 拦截通知,当队列组中所有任务都执行完成完毕之后会进行通知
// 内部本身是异步的
- (void) groupOne{
    // 1. 创建队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    // 2. 创建队列组
    dispatch_group_t group = dispatch_group_create();
    
    // 3. 异步函数封装任务
    dispatch_group_async(group, queue, ^{
        NSLog(@"download_group1------%@",[NSThread currentThread]);
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"download_group2------%@",[NSThread currentThread]);
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"download_group3------%@",[NSThread currentThread]);
    });
    
    // 拦截通知,当队列组中所有任务都执行完成完毕之后会进行通知
    // 内部本身是异步的
    dispatch_group_notify(group, queue, ^{
        NSLog(@"------dispatch_group_notify------");
    });
}
  • 方法二 以前版本
- (void) groupTwo{
    // 1. 创建队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    // 2. 创建队列组
    dispatch_group_t group = dispatch_group_create();
    
    // 3. 在该方法后面的异步任务会被纳入到队列组的监听范围
    // dispatch_group_enter | dispatch_group_leave 必须配对使用
    dispatch_group_enter(group);
    
    dispatch_async(queue, ^{
        NSLog(@"download_group1------%@",[NSThread currentThread]);
        
        // 离开队列组
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);

    dispatch_async(queue, ^{
        NSLog(@"download_group2------%@",[NSThread currentThread]);
        
        // 离开队列组
        dispatch_group_leave(group);
    });
    
//    dispatch_group_notify(group, queue, ^{
//        NSLog(@"------dispatch_group_notify------");
//    });
    
    // 等待
    // DISPATCH_TIME_FOREVER:死等,知道队列组中所有任务执行完毕之后才能执行 本身是阻塞的
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"------end------");
    
}
示例

下面通过一个例子来实际使用GCD的队列组 通过下载 图片1 和 图片2 之后然后合并两张图片 因为合并两张图片必须得让子线程中的 图片1 和 图片2 下载完成之后才能够进行合并图片

- (void) groupThree{
    
    /**
     * 1. 下载图片1 开子线程
       2. 下载图片2 开子线程
       3. 合成图片并显示图片 开子线程
     */
    
    // 获得并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    
    // 1. 下载图片1 开子线程
    dispatch_group_async(group, queue, ^{
        NSLog(@"download_image1------%@",[NSThread currentThread]);

        // 1. 1 确定url
        NSURL * url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1502282744935&di=aaefb03a83917b6b94da36fbf69fa080&imgtype=0&src=http%3A%2F%2Fpic.ilitu.com%2Fy3%2F1475_39900960921.jpg"];
        
        // 1.2 下载图片二进制数据到本地
        NSData * imageData = [NSData dataWithContentsOfURL:url];
        
        // 1.3 转换图片
        self.image1 = [UIImage imageWithData:imageData];
    });
    
    
    // 2. 下载图片1 开子线程
    dispatch_group_async(group, queue, ^{
        NSLog(@"download_image2------%@",[NSThread currentThread]);

        // 2. 1 确定url
        NSURL * url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1502282773716&di=9b788f0dda94c259067c2826795caef4&imgtype=0&src=http%3A%2F%2Fi3.sinaimg.cn%2Fgm%2F2015%2F0422%2FU3932P115DT20150422122125.jpg"];
        
        // 2.2 下载图片二进制数据到本地
        NSData * imageData = [NSData dataWithContentsOfURL:url];
        
        // 2.3 转换图片
        self.image2 = [UIImage imageWithData:imageData];
        
    });
    
    // 3. 合成图片并显示图片 开子线程
    dispatch_group_notify(group, queue, ^{
        NSLog(@"合并图片------%@",[NSThread currentThread]);

        // 3.1 创建图形上下文
        UIGraphicsBeginImageContext(CGSizeMake(300, 400));
        
        // 3.2 画图1
        [self.image1 drawInRect:CGRectMake(0, 0, 300, 200)];
        self.image1 = nil;
        
        // 3.3 画图2
        [self.image2 drawInRect:CGRectMake(0, 200, 300, 200)];
        self.image2 = nil;
        
        // 3.4 根据上下文得到一张图片
        UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
        
        // 3.5 关闭上下文
        UIGraphicsEndImageContext();
        
        // 3.6 得到图片 回到主线程刷新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
            NSLog(@"更新UI------%@",[NSThread currentThread]);
        });
        
    });
}

以上就是GCD所有的基本常用方法功能,希望能够帮助到大家,所有Demo我已经上传到GitHub,有喜欢的朋友记得给个Stare,谢谢!
另外分享一个觉得好用的已经封装好的GCD

我是Renjiee 我要做最骚的程序猿‍‍‍‍‍‍👨‍💻‍

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容