09 - OC多线程之队列任务以及死锁的认识

2021-11-03  本文已影响0人  iOS之文一

OC底层原理探索文档汇总

主要内容:

1、队列的认识
2、任务的认识
3、队列和任务搭配使用的面试题
4、死锁的认识和解决

队列

队列就是管理待执行任务的等待队列,用来调度任务给线程执行,符合先进先出原则

并发和串行决定了任务执行的顺序,并发是多个任务并发执行,串行是指任务顺序执行,也就是一个任务执行完成再执行下一个任务

创建队列:

- (void)createQueueTest{
    //创建队列
    dispatch_queue_t queue = dispatch_queue_create("串行队列", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("并发队列", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue3 = dispatch_queue_create("串行队列", NULL);
    //获取已有队列
    //获取主队列
    dispatch_queue_t queue4 = dispatch_get_main_queue();
    //获取全局并发队列
    /*
     第一个参数是优先级,第二个无意义,填0
     */
    dispatch_queue_t queue5 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
}

任务

任务就是线程要执行的那段代码
执行任务有两种方式,同步和异步,同步和可以异步决定是否要开启新的线程,同步不开启,异步开启。

同步函数(sync):

异步函数(async):

提交任务的函数很多都有两个版本,一个是传两个参数,传递作为任务的block代码,一个是传三个参数,传递作为任务的方法。

- (void)createTaskTest{
    /*
     参数一:队列
     参数二:作为任务的block代码
     */
     //在调度队列上提交异步执行的块并立即返回
    dispatch_async(dispatch_queue_t  _Nonnull queue, <#^(void)block#>);
    /*
     参数一:队列
     参数二:上下文
     参数三:作为任务的函数
     */
    dispatch_async_f(<#dispatch_queue_t  _Nonnull queue#>, <#void * _Nullable context#>, <#dispatch_function_t  _Nonnull work#>);
    
    /*
     参数一:队列
     参数二:作为任务的block代码
     */
     //提交块对象以执行,并在该块完成执行后返回。 
    dispatch_sync(dispatch_queue_t  _Nonnull queue, <#^(void)block#>);
    /*
     参数一:队列
     参数二:上下文
     参数三:作为任务的函数
     */
    dispatch_sync_f(<#dispatch_queue_t  _Nonnull queue#>, <#void * _Nullable context#>, <#dispatch_function_t  _Nonnull work#>);
}

注意:

主队列

主队列是一种特殊的串行队列,特殊在于只使用在主线程和主Runloop中,并且是在启动APP时自动创建的。
主队列是指主线程的队列,是一个串行队列,在主线程去执行其实就是放入了主队列

全局并发队列

全局并发队列是系统提供的,开发者可以直接使用的一个并发队列,没有其他的特殊之处。

在获取时可以给定优先级

总结

任务队列线程的关系.png
  • 线程有两种方式执行任务,同步和异步,分别通过同步函数和异步函数来实现
  • 同步函数添加的任务,该线程执行完这个任务才可以执行其他任务,异步函数添加的任务,线程可以并发执行该任务
  • 任务存放在队列中,队列有两种调度方式,串行和并发,串行只能顺序处理任务,并发可以同时处理多个任务
  • 线程想要并发执行任务,需要异步函数添加任务给并发队列
  • 如果队列是串行的,即使线程可以并发执行任务也不行,因为队列是串行调度给线程的。
  • 同步函数需要等待block执行完成才可以返回
  • 异步函数不需要等待block执行完成就可以返回

队列和任务的搭配使用

任务和队列以及线程的执行关系.png

说明:

  • 同步不开启新线程,异步会开启新线程
  • 并发队列可以并发调度任务,串行队列只能顺序调度任务
  • 只有并发队列提交给线程异步执行的任务才可以异步执行

总结:

执行类型.png

队列和任务的常见类型代码演示

1、基础写法

给一个串行队列添加了一个任务,该任务需要异步执行

- (void)syncTest{
    // 把任务添加到队列 --> 函数
    // 任务 _t ref c对象
    dispatch_block_t block = ^{
        NSLog(@"hello GCD");
    };
    //串行队列
    dispatch_queue_t queue = dispatch_queue_create("wy", NULL);
    // 函数
    dispatch_async(queue, block);
    
}

2、主队列同步

- (void)mainSyncTest{
    
    NSLog(@"0");
    // 等
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"1");
    });
    NSLog(@"2");
}

执行结果: 0,之后死锁

死锁.png

说明:

注意:造成死锁是同步任务块和任务1的相互等待,与任务2没关系,就算删掉任务2,也会死锁

3、 主队列异步

- (void)mainAsyncTest{
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"1");
    });
    NSLog(@"2");
}

执行结果: 21
说明:

4、 全局并发队列异步

并发执行任务

- (void)globalAsyncTest{
    
    for (int i = 0; i<20; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"hello queue");
}

结果:

2021-10-31 14:58:16.038522+0800 多线程使用[11062:5684222] hello queue
2021-10-31 14:58:16.038559+0800 多线程使用[11062:5684360] 0-<NSThread: 0x600003d438c0>{number = 6, name = (null)}
2021-10-31 14:58:16.038564+0800 多线程使用[11062:5684368] 2-<NSThread: 0x600003d34440>{number = 7, name = (null)}
2021-10-31 14:58:16.038567+0800 多线程使用[11062:5684359] 3-<NSThread: 0x600003d28780>{number = 4, name = (null)}
2021-10-31 14:58:16.038571+0800 多线程使用[11062:5684358] 1-<NSThread: 0x600003d282c0>{number = 8, name = (null)}
2021-10-31 14:58:16.038585+0800 多线程使用[11062:5684361] 4-<NSThread: 0x600003d31b80>{number = 3, name = (null)}
2021-10-31 14:58:16.038606+0800 多线程使用[11062:5684362] 5-<NSThread: 0x600003d43e00>{number = 5, name = (null)}
2021-10-31 14:58:16.038642+0800 多线程使用[11062:5684360] 7-<NSThread: 0x600003d438c0>{number = 6, name = (null)}

说明:

5、 全局并发队列同步

串行执行任务

- (void)globalSyncTest{
    for (int i = 0; i<20; i++) {
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"hello queue");
}

结果:

2021-10-31 14:55:53.552472+0800 多线程使用[10971:5681912] 13-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.552541+0800 多线程使用[10971:5681912] 14-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.552635+0800 多线程使用[10971:5681912] 15-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.552729+0800 多线程使用[10971:5681912] 16-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.552829+0800 多线程使用[10971:5681912] 17-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.559220+0800 多线程使用[10971:5681912] 18-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.559332+0800 多线程使用[10971:5681912] 19-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.559401+0800 多线程使用[10971:5681912] hello queue

说明:

简单面试题分析

面试题1

- (void)textDemo1{
    
    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");

}

结果: 15243

说明:

面试题2

- (void)textDemo{
    
    dispatch_queue_t queue = dispatch_queue_create("cooci", NULL);
    NSLog(@"1");
    // 耗时
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

结果: 1、5、2,崩溃

20-面试题解析2.png

说明:

注意:
这里虽然是主队列,任务块在任务5的前面,但是在执行时我们看到先执行了任务5后执行了任务2,
这是因为异步操作并不需要等待函数的执行完成而完成,

面试题3

- (void)wbinterDemo{
    dispatch_queue_t queue11 = dispatch_queue_create("wy", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue11, ^{
        NSLog(@"1");
    });
    dispatch_async(queue11, ^{
        NSLog(@"2");
    });
    dispatch_sync(queue11, ^{
        NSLog(@"3");
    });
    NSLog(@"0");
    dispatch_async(queue11, ^{
        NSLog(@"1");
    });
    dispatch_async(queue11, ^{
        NSLog(@"7");
    });
    dispatch_async(queue11, ^{
        NSLog(@"8");
    });
    dispatch_async(queue11, ^{
        NSLog(@"9");
    }); 
}

请选择:
A:1230789
B:1237890
C:3120798
D:2137890

答案为AC

说明:

死锁的认识

死锁就是同一个串行队列中两个任务相互等待引起死锁.

网上面很多说是不同的线程相互等待引起的,是解释不通的,应该是队列,因为队列是先进先出原则导致,而后面进入的任务的结束又依赖于前面进的任务。
一个串行队列的线程池只维护了一个线程。

死锁的场景

主队列同步执行

- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任务1");
});
}

说明:

同步串行队列的嵌套场景也是一样的,不再多言

- (void)textDemo{
    
    dispatch_queue_t queue = dispatch_queue_create("cooci", NULL);
    NSLog(@"1");
    // 耗时
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_async(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

死锁的解决

上面知道死锁是一个串行队列中有两个任务相互等待导致的,所以有两个解决办法,

所以可以有两个做法,

将队列改为并发

- (void)textDemo1{
    
    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");

}

将任务改为异步任务,及不会相互等待

- (void)textDemo{
    
    dispatch_queue_t queue = dispatch_queue_create("cooci", NULL);
    NSLog(@"1");
    // 耗时
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_async(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}
上一篇 下一篇

猜你喜欢

热点阅读