笔记-多线程底层再探

函数与队列

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

  • 任务使用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);
复制代码

队列: 数组

特殊的两种队列:安全

主队列 dispatch_get_main_queue()bash

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

全局队列 dispatch_get_global_queue()多线程

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

队列与函数:并发

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

问题一
- (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;里面嵌套的异步操做和外面的分析如出一辙,即整个的输出顺序为一、五、二、四、3async

问题二
- (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,而后奔溃。函数

死锁的产生url

  • 主线程由于同步函数的缘由等着先执行任务
  • 主队列等着主线程的任务执行完毕在执行本身的任务
  • 主队列和主线程相互等待会形成死锁

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

下面看一个面试题

- (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)

相关文章
相关标签/搜索