美文网首页
笔记-多线程底层再探

笔记-多线程底层再探

作者: 佐_笾 | 来源:发表于2019-03-30 10:18 被阅读0次

    函数与队列

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

    • 任务使用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)

    相关文章

      网友评论

          本文标题:笔记-多线程底层再探

          本文链接:https://www.haomeiwen.com/subject/wdldbqtx.html