欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

iOS课程观看笔记(六)---多线程

程序员文章站 2022-06-14 19:57:43
...

本节学习内容目录:
iOS课程观看笔记(六)---多线程

问:iOS提供了哪些多线程技术方案?

pthread、NSThread、GCD、NSOperation
iOS课程观看笔记(六)---多线程

GCD

同步\异步 和 串行\并行
dispatch_barrier_async(barrier栅栏)
异步栅栏调用,可以很好的解决异步多读单写问题
dispatch_group

同步\异步 和 串行\并行

sync:同步
async:异步
serial_queue:串行队列
concurrent_queue:并行队列

两两组合,可以由四种不同的组合:
同步串行
异步串行
同步并行
异步并行

iOS课程观看笔记(六)---多线程

同步串行

问:以下代码能执行吗?

- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_sync(dispatch_get_main_queue(), ^{
        [self test];
    });
}

- (void)test
{
    NSLog(@"test-[NSThread currentThread] - %@", [NSThread currentThread]);
}

结果:程序崩溃
iOS课程观看笔记(六)---多线程

为什么呢?

上段代码会产生死锁

那么,死锁产生的原因是什么呢?
可以概括为一句话:队列引起的循环等待

iOS课程观看笔记(六)---多线程

知识点:
主队列的任务要在主线程中执行

上图中涉及到了:
同步:sync
队列:主队列(串行)
线程:主线程
任务:viewDidLoad、Block

执行过程是:
主队列中有一个ViewDidLoad任务
通过
dispatch_sync(dispatch_get_main_queue(), ^{
[self test];
});
在主队列中添加新的任务Block

主队列是串行队列,因此按照队列先进先出的特性,先将主队列中的ViewDidLoad任务取出来放进主线程,执行任务。
由于ViewDidLoad任务中,同步调用了Block任务,会先将Block任务执行完毕后,继续执行ViewDidLoad任务。
换句话说,ViewDidLoad任务想要执行完,需要依赖Block任务执行完。也就是图中主队列左边的黑色箭头。
而Block任务想执行完,根据队列先进先出的特性,需要依赖ViewDidLoad任务执行完。也就是图中主队列右边的黑色箭头。
从而,两个任务形成相互等待,产生死锁。

问:异步分派到主队列,其任务执行在主线程还是子线程?

答:主线程

主队列的任务在主线程中执行

问:以下代码分别执行在哪个线程?

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1-[NSThread currentThread] - %@", [NSThread currentThread]);
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"2-[NSThread currentThread] - %@", [NSThread currentThread]);
        });
        
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"3-[NSThread currentThread] - %@", [NSThread currentThread]);
        });
    });
    
    
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"4-[NSThread currentThread] - %@", [NSThread currentThread]);
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"5-[NSThread currentThread] - %@", [NSThread currentThread]);
        });
    });
}

运行结果:
4-[NSThread currentThread] - <NSThread: 0x600002355040>{number = 1, name = main}
1-[NSThread currentThread] - <NSThread: 0x6000023286c0>{number = 6, name = (null)}
5-[NSThread currentThread] - <NSThread: 0x600002355040>{number = 1, name = main}
2-[NSThread currentThread] - <NSThread: 0x600002355040>{number = 1, name = main}
3-[NSThread currentThread] - <NSThread: 0x600002355040>{number = 1, name = main}

1是在子线程,原因是异步并发执行任务
2是主线程,虽然2整体执行在线程6中,是子线程,且是async异步,具有开启子线程的能力,但是由于是主队列,所以任务执行在主线程
3是主线程,虽然3整体执行在线程6中,是子线程,且是sync同步,没有开启线程的能力,但是由于是主队列,所以任务执行在主线程
4是主线程,sync是同步,没有开启线程的能力,任务执行在当前线程,当前线程是主线程,因此任务执行在主线程
5是主线程,5整体执行在主线程,async是异步,有开启线程的能力,但是由于是主队列,所以任务执行在主线程

知识点:

Blocks submitted to the main queue MUST be run on the main thread
主队列的任务要在主线程中执行

同步异步主要影响:能不能开启新的线程
同步:在当前线程中执行任务,不具备开启新线程的能力
异步:在新的线程中执行任务,具备开启新线程的能力

创建队列的方式:

//创建队列(方法一):串行队列
dispatch_queue_t queue = dispatch_queue_create(“myQueue”, NULL);//第一个参数是字符串,是队列的名称。第二个是队列属性,NULL代表串行

//创建队列(方法二):串行队列
dispatch_queue_t queue = dispatch_queue_create(“myQueue”, DISPATCH_QUEUE_SERIAL);

//创建队列(方法三):并发队列
dispatch_queue_t queue = dispatch_queue_create(“myQueue”, DISPATCH_QUEUE_CONCURRENT);

//创建队列(方法四):主队列(特殊的串行队列)
dispatch_queue_t queue = dispatch_get_main_queue();

//创建队列(方法五):全局队列(并发队列)
dispatch_queue_t queue = dispatch_get_global_queue(0, 0)

问:下列代码执行有什么问题?

 - (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_sync(dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL), ^{
        NSLog(@"1-[NSThread currentThread] - %@", [NSThread currentThread]);
    });
}

结果:

1-[NSThread currentThread] - <NSThread: 0x600001ead040>{number = 1, name = main}

正常执行,没有问题

dispatch_sync是同步执行,没有开启线程的能力,也就是Block任务是在主线程执行。

iOS课程观看笔记(六)---多线程

问:以下代码执行结果是什么?

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1-[NSThread currentThread] - %@", [NSThread currentThread]);
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"2-[NSThread currentThread] - %@", [NSThread currentThread]);
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"3-[NSThread currentThread] - %@", [NSThread currentThread]);
        });
        NSLog(@"4-[NSThread currentThread] - %@", [NSThread currentThread]);
    });
    NSLog(@"5-[NSThread currentThread] - %@", [NSThread currentThread]);
}

打印结果:

1-[NSThread currentThread] - <NSThread: 0x6000014ead00>{number = 1, name = main}
2-[NSThread currentThread] - <NSThread: 0x6000014ead00>{number = 1, name = main}
3-[NSThread currentThread] - <NSThread: 0x6000014ead00>{number = 1, name = main}
4-[NSThread currentThread] - <NSThread: 0x6000014ead00>{number = 1, name = main}
5-[NSThread currentThread] - <NSThread: 0x6000014ead00>{number = 1, name = main}

结果分析:
1没问题
2,由于是同步,是立即在当前线程执行,当前线程是主线程,且是全局并发队列,因此,可以执行
其余结果一样。


问:以下代码执行结果是什么?

- (void)viewDidLoad {
   	[super viewDidLoad];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1-[NSThread currentThread] - %@", [NSThread currentThread]);
        [self performSelector:@selector(test) withObject:nil afterDelay:0.0];
        NSLog(@"2-[NSThread currentThread] - %@", [NSThread currentThread]);
    });
}

- (void)test
{
    NSLog(@"test-[NSThread currentThread] - %@", [NSThread currentThread]);
}

打印结果:

1-[NSThread currentThread] - <NSThread: 0x600002baa780>{number = 3, name = (null)}
2-[NSThread currentThread] - <NSThread: 0x600002baa780>{number = 3, name = (null)}

可以发现,1和2的结果可以正常打印,而test不能够执行打印,为什么呢?

asyn异步,dispatch_get_global_queue(0, 0)全局并发,所以,block会在子线程执行,通过打印结果3也可以看出,确实是在子线程执行的任务。

Block会在GCD底层所维护的线程池当中的某一线程去执行,而GCD底层分派的线程,默认情况下是没有开启对应的RunLoop的。即使是0.0秒,也需要创建任务提交到RunLoop上面。由于RunLoop没有开启,因此,test方法不能够执行。

dispatch_barrier_async()

异步栅栏调用

问:如何利用GCD实现多读单写?

或者,如何实现一个多读单写?

iOS课程观看笔记(六)---多线程
iOS课程观看笔记(六)---多线程
使用dispatch_barrier_async()

dispatch_group_async()

问:如何实现这个需求:A、B、C三个任务并发,完成后执行任务D?

iOS课程观看笔记(六)---多线程使用dispatch_group_async()


NSOperation

需要和NSOperationQueue配合使用来实现多线程方案

问:NSOperation相比其他多线程技术方案有哪些优势和特点?

1 添加任务依赖
2 任务执行状态的控制
3 最大并发量的控制

iOS课程观看笔记(六)---多线程

任务执行状态的控制

问:可以控制NSOperation哪些状态?或者,关于NSOperation的任务状态都有哪些?

(需加强学习)

isReady
isExecuting
isFinished
isCancelled

iOS课程观看笔记(六)---多线程
iOS课程观看笔记(六)---多线程


NSThread(需加强)

iOS课程观看笔记(六)---多线程

问:如何通过NSThread结合RunLoop实现一个常驻线程?

问:NSThread的内部实现机制?

可通过GunStep查看和分享源码
内部创建了一个pthread线程


多线程和锁

问:iOS当中都有哪些锁?

或者,你在项目中,都使用过哪些锁?

这个问题第一种问法,你可以说你知道的,还好回答
第二种问法,就有点坑了,基本上都没用过。。。以后被问到用过哪些锁等同于介绍你知道的哪些锁

具体有:
@synchronized:同步的
atomic
OSSpinLock:Spin自旋
NSLock
NSRecursiveLock:(Recursive:循环)
dispatch_semaphore_t:semaphore:信号量

@synchronized

一般在创建单例对象的时候使用

atomic

修饰属性的关键字
对被修饰对象进行原子操作(不负责使用)

iOS课程观看笔记(六)---多线程

赋值 != 使用

OSSpinLock自旋锁

循环等待询问,不释放当前资源

用于轻量级数据访问,简单的int值+1,-1操作
例如:sidetable表中,有spinLock的使用

NSLock

问:下面代码会产生什么问题?为什么?

iOS课程观看笔记(六)---多线程
对同一把锁,重复加锁

解决方法:NSRecursiveLock

NSRecursiveLock

iOS课程观看笔记(六)---多线程

dispatch_semaphore_t信号量

iOS课程观看笔记(六)---多线程
iOS课程观看笔记(六)---多线程
iOS课程观看笔记(六)---多线程

Decrement the counting semaphore. If the resulting value is less than zero, this function waits for a signal to occur before returning.
计数信号量递减。如果结果值小于零,则该函数在返回之前等待出现信号。

iOS课程观看笔记(六)---多线程

Increment the counting semaphore. If the previous value was less than zero, this function wakes a waiting thread before returning.
增加计数信号量。如果前一个值小于零,该函数将在返回前唤醒一个等待的线程。

这个翻译,明明是 less than zero,小于0,怎么老是讲课是 <= 0?
仔细看,英文是the previous value
而,上一个英文是the resulting value
我理解的是:the resulting value 是结果值,也就是value,the previous value 是指的value前的值,由于value做了 value = value + 1;操作,也就是the resulting value = the previous value + 1;
the previous value < 0;
the resulting value - 1 < 0;
the resulting value < 1;
the resulting value <= 0;

有哥们找到了源码:
iOS课程观看笔记(六)---多线程
value > 0,则return 0
value <= 0则唤醒等待的线程。

信号量执行过程


假如有5个任务并发执行,信号量值value = 2;

dispatch_semaphore_wait信号量值value-1(2-1 = 1),结果1 >=0则进入执行任务1
dispatch_semaphore_wait信号量值value-1(1-1 = 0),结果0 = 0则进入执行任务2
dispatch_semaphore_wait信号量值value-1(0-1 = -1),结果-1 < 0,则主动阻塞等待任务3
dispatch_semaphore_wait信号量值value-1(-1-1 = -2),结果-2 < 0,则主动阻塞等待任务4
dispatch_semaphore_wait信号量值value-1(-2-1 = -3),结果-3 < 0,则主动阻塞等待任务5

任务1执行完毕

dispatch_semaphore_signal信号量值value + 1(-3 + 1 = -2),结果-2 < 0,则唤醒一个等待的任务3

任务2执行完毕
任务3执行完毕

dispatch_semaphore_signal信号量值value + 1(-2 + 1 = -1),结果-1 < 0,则唤醒一个等待的任务4
dispatch_semaphore_signal信号量值value + 1(-1 + 1 = 0),结果0 = 0,则唤醒一个等待的任务5

任务4执行完毕
任务5执行完毕

dispatch_semaphore_signal信号量值value + 1(0 + 1 = 1),结果1 > 0,return 0;
dispatch_semaphore_signal信号量值value + 1(1 + 1 = 2),结果2 > 0,return 0;


@property (strong, nonatomic) dispatch_semaphore_t semaphore;


- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.semaphore = dispatch_semaphore_create(2);
    
    for (int i = 0; i<5; i++) {
        [[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
    }
}

- (void)test
{
    NSLog(@"dispatch_semaphore_wait");
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    sleep(2);
    
    NSLog(@"dispatch_semaphore_signal");
    dispatch_semaphore_signal(self.semaphore);
}

执行结果:

2020-07-31 19:18:58.821671+0800 block2[37362:733466] dispatch_semaphore_wait
2020-07-31 19:18:58.821472+0800 block2[37362:733464] dispatch_semaphore_wait
2020-07-31 19:18:58.821586+0800 block2[37362:733465] dispatch_semaphore_wait
2020-07-31 19:18:58.822075+0800 block2[37362:733467] dispatch_semaphore_wait
2020-07-31 19:18:58.822167+0800 block2[37362:733468] dispatch_semaphore_wait
2020-07-31 19:19:00.826471+0800 block2[37362:733466] dispatch_semaphore_signal
2020-07-31 19:19:00.845063+0800 block2[37362:733464] dispatch_semaphore_signal
2020-07-31 19:19:02.826900+0800 block2[37362:733465] dispatch_semaphore_signal
2020-07-31 19:19:02.848695+0800 block2[37362:733467] dispatch_semaphore_signal
2020-07-31 19:19:04.827253+0800 block2[37362:733468] dispatch_semaphore_signal

从结果时间来看,dispatch_semaphore_wait是一次性执行
dispatch_semaphore_signal是两秒执行两秒执行

由于进口处是2,则,最大并发数为2

iOS课程观看笔记(六)---多线程

dispatch_semaphore_wait若为负数,则执行_dispatch_semaphore_wait_slow进入等待。
dispatch_semaphore_signal能够唤醒一个在dispatch_semaphore_wait中等待的线程

参考文章:
iOS源码解析: GCD的信号量semaphore


iOS课程观看笔记(六)---多线程

相关标签: iOS课程观看笔记