笔记-多线程底层再探

函数与队列

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

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

你可能感兴趣的:(笔记-多线程底层再探)