笔记-多线程底层再探

函数与队列

把任务添加到队列,并且指定执行任务的函数

  • 任务使用block封装,且任务的block没有参数也没有返回值
  • 执行任务的函数
    • 异步 dispatch_async{}
      • 不用等待当前语句执行完毕,就可以执行下一条语句
      • 会开启线程执行block的任务
      • 异步是多线程的代名词
    • 同步 dispatch_sync{}
      • 必须等待当前语句执行完毕,才会执行下一条语句
      • 不会开启线程
      • 在当前执行block的任务
        还原最基本的写法:
    // 把任务添加到队列 --> 函数
    // 任务 
    dispatch_block_t block = ^{
        NSLog(@"hello GCD");
    };
    //串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.zb.cn", NULL);
    // 函数
    dispatch_async(queue, block);

队列:


image

特殊的两种队列:

主队列 dispatch_get_main_queue()

  • 专门用来在主线程上调度任务的队列,是串行队列
  • 不会开启线程
  • 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度

全局队列 dispatch_get_global_queue()

  • 全局队列是一个并发队列
  • 在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列

队列与函数:

image

理解上面几种组合后,尝试解答出下面的任务输出顺序、、

问题一
- (void)textOne {
    dispatch_queue_t queue = dispatch_queue_create("zb", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"任务1");
    dispatch_async(queue, ^{
        NSLog(@"任务2");
        dispatch_async(queue, ^{
            NSLog(@"任务3");
        });
        NSLog(@"任务4");
    });
    NSLog(@"任务5");
}

首先明确是一个并发队列,里面有任务1dispatch_asyncblock任务、任务5,所以按顺序输出任务1任务5;里面嵌套的异步操作和外面的分析一模一样,即整个的输出顺序为1、5、2、4、3

问题二
- (void)textTwo {
    dispatch_queue_t queue = dispatch_queue_create("zb", DISPATCH_QUEUE_SERIAL);
    NSLog(@"任务1");
    dispatch_async(queue, ^{
        NSLog(@"任务2");
        dispatch_sync(queue, ^{
            NSLog(@"任务3");
        });
        NSLog(@"任务4");
    });
    NSLog(@"任务5");
}

这里是一个串行队列,dispatch_async任务开启了一个线程专门处理,不必等待,所以先按顺序输出任务1任务5;进入第一个dispatch_async任务,串行队列,所以也是按顺序执行任务2dispatch_asyncblock任务、任务4;此时的block任务是一个同步函数,所以当任务2执行完毕以后,走到这个发现是同步,然后就把任务3加入到队列里执行,此时队列里的任务是任务2dispatch_asyncblock任务、任务4任务3;根据 FIFO 原则正常行走,任务2结束后,执行dispatch_asyncblock任务,但是因为同步的原因,执行这个block任务又必须要执行任务3,执行任务3的前提是任务4执行结束,执行任务4的前提是block任务执行结束,这里发生里死锁。所以任务的输出顺序为任务1任务5任务2,然后奔溃。

死锁的产生

  • 主线程因为同步函数的原因等着先执行任务
  • 主队列等着主线程的任务执行完毕在执行自己的任务
  • 主队列和主线程相互等待会造成死锁

如果把上面的串行队列改成并发队列,输出的结果又是什么样的呢?

下面看一个面试题

- (void)viewDidLoad {
    [super viewDidLoad];
    int a = 0; 
    while (a < 10) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            a ++;
        });
    }
    NSLog(@"%d",a);
}

问题一:a++ 报错的原因?
问题二:修改正确后,输出a=?
问题三:在不改变函数以及队列的前提下,如何让a的输出为10?

答案一:__block 修饰a的初始化,把a的指针和值从栈区copystruct,堆区。
答案二:输出结果a >= 10,在while循环里,每一次的循环都会产生一个线程,执行异步操作,不等待直接执行后面的任务,同时这也是耗时操作,所以在循环里可能会走很多次a++操作
答案三:可以通过加锁的方式,实现输出a=10

具体代码如下

- (void)viewDidLoad {
    [super viewDidLoad];
    __block int a = 0; 
    // 信号量
    dispatch_semaphore_t lock =  dispatch_semaphore_create(1);
    while (a < 10) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            a ++;
            dispatch_semaphore_signal(lock);
        });
        // 堵死
        dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    }
    NSLog(@"%d",a);
}

GCD的使用

栅栏函数 dispatch_barrier_sync

- (void)demo2{
    dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    /* 1.异步函数 */
    dispatch_async(concurrentQueue, ^{
        for (NSUInteger i = 0; i < 5; i++) {
            NSLog(@"download1-%zd-%@",i,[NSThread currentThread]);
        }
    });
    
    dispatch_async(concurrentQueue, ^{
        for (NSUInteger i = 0; i < 5; i++) {
            NSLog(@"download2-%zd-%@",i,[NSThread currentThread]);
        }
    });
    
    /* 2. 栅栏函数 */
    dispatch_barrier_sync(concurrentQueue, ^{
        NSLog(@"---------------------%@------------------------",[NSThread currentThread]);
    });
    NSLog(@"加载那么多,喘口气!!!");
    /* 3. 异步函数 */
    dispatch_async(concurrentQueue, ^{
        for (NSUInteger i = 0; i < 5; i++) {
            NSLog(@"日常处理3-%zd-%@",i,[NSThread currentThread]);
        }
    });
    NSLog(@"**********起来干!!");
    
    dispatch_async(concurrentQueue, ^{
        for (NSUInteger i = 0; i < 5; i++) {
            NSLog(@"日常处理4-%zd-%@",i,[NSThread currentThread]);
        }
    });
}

这里就达到了download1download2任务完成后,才去执行日常处理3日常处理4任务的效果。

提问1:如果把并发队列dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT); 改成dispatch_get_global_queue(0,0);后效果会怎么样?

执行代码后会神奇的发现,栅栏效果神奇的失效了

这里需要注意了,栅栏函数一定要是自定义的并发队列,不然就无效,分析一下也可以得知,dispatch_get_global_queue是全局的并发队列,加上栅栏实际上就是一个堵塞,如果有效的话,系统就。。GG了。

提问2:如果把download1download2任务的队列换成一个其他的队列,效果会怎么样?

执行代码后,也会发现,不在同一个队列的话,栅栏也是无效,所以这里也是一个需要注意的地方,必须要求都在同一个队列

这是栅栏函数的第一个作用,保证顺序执行

看下面代码:

for (int i = 0; i < 5000; i++) {
    dispatch_async(concurrentQueue, ^{
        NSString *imageName = [NSString stringWithFormat:@"%d.jpg", (i % 10)];
        NSURL *url = [[NSBundle mainBundle] URLForResource:imageName withExtension:nil];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];
        [self.mArray addObject:image];
    });
}

执行后,会发生crash,异步函数,创建了多条线程,同时对数组执行addObject操作,造成资源抢夺,发送崩溃。

    dispatch_queue_t concurrentQueue = dispatch_queue_create("zb", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 5000; i++) {
        dispatch_async(concurrentQueue, ^{
            .
            .
            .
            dispatch_barrier_async(concurrentQueue, ^{
                [self.mArray addObject:image];
            });
        });
    }

执行后,可以正常执行,输出结果。
第二个作用,保证线程安全

调度组 group

创建组 dispatch_group_create
进组任务 dispatch_group_async
进组任务执行完毕通知: dispatch_group_notify
进组任务执行等待时间:dispatch_group_wait

进组 dispatch_group_enter
出组 dispatch_group_leave

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
    
dispatch_group_async(group, queue, ^{
    NSLog(@"任务1");
});
    
long timeOut = dispatch_group_wait(group, 0.5);
dispatch_group_notify(group, queue, ^{
    NSLog(@"任务2");
});

或

dispatch_group_enter(group);
dispatch_async(queue, ^{
    NSLog(@"任务");
    dispatch_group_leave(group);
});

使用enterleave时,一定要成对出现,不然会产生crash。

信号量dispatch_semaphore_t

创建信号量 dispatch_semaphore_create
信号量等待 dispatch_semaphore_wait
信号量释放 dispatch_semaphore_signal

可以当作锁来使用,在本文一开始就使用了,还可以控制GCD最大并发数dispatch_semaphore_create(x)

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

推荐阅读更多精彩内容

  • iOS多线程编程 基本知识 1. 进程(process) 进程是指在系统中正在运行的一个应用程序,就是一段程序的执...
    陵无山阅读 6,026评论 1 14
  • 本文用来介绍 iOS 多线程中 GCD 的相关知识以及使用方法。这大概是史上最详细、清晰的关于 GCD 的详细讲...
    花花世界的孤独行者阅读 497评论 0 1
  • 柏杨长在大斜坡上 整整齐齐 碗口儿粗冲天高 十二级大风也刮不倒 黄土地表面一层沙 风吹沙动荒漠里的孤月 行色匆匆 ...
    肖魁之阅读 158评论 0 1
  • 前言 最近公司比较闲,那么自己也找点事情做。这道题呢在我写这篇文章的时候谷歌、百度上都没有答案,于是乎自己就来解答...
    Thebloodelves阅读 1,993评论 3 6
  • 今日三目标: 1.演讲练习 2.复盘总结 3.听完樊登的书《Kro工作法》 成果:完成了两个,第三个没有完成! 多...
    Ada彩英阅读 215评论 0 0