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

本节学习内容目录:
在这里插入图片描述

GCD源码

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

pthread、NSThread、GCD、NSOperation
在这里插入图片描述

GCD

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

注意:GCD的队列执行是针对的并发,而不是并行
并发与并行是不一样的

并发与并行的区别

并发(Concurrence)是指:CPU在多个任务之间进行切换,同时执行好几个任务

并行(parallelism:平行)是指:两个或两个以上事件或活动在同一时刻发生。在多道程序环境下,并行性使多个程序同一时刻可在不同CPU上同时执行。

并发是逻辑上的同时发生(simultaneous),而并行是物理上的同时发生。

并行的事件或活动一定是并发的,但反之并发的事件或活动未必是并行的


同步\异步 和 串行\并发

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

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

在这里插入图片描述

同步串行
问:以下代码能执行吗?
- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_sync(dispatch_get_main_queue(), ^{
        [self test];
    });
}

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

结果:程序崩溃
在这里插入图片描述

为什么呢?

上段代码会产生死锁

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

在这里插入图片描述

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

上图中涉及到了:
同步: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)

dispatch_queue_t dispatch_get_global_queue(long priority, unsigned long flags);
priority指定队列的优先级,一般为0或者DISPATCH_QUEUE_PRIORITY_DEFAULT
flag作为保留字段备用(一般为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任务是在主线程执行。
由于创建的队列是串行队列,没有产生由于队列产生的循环等待,因此,可以正常执行

在这里插入图片描述

问:以下代码执行结果是什么?
- (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实现多读单写?

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

在这里插入图片描述
在这里插入图片描述
使用dispatch_barrier_async()

dispatch_group_async()

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

在这里插入图片描述使用dispatch_group_async()


NSOperation

NSOperation是一个抽象类,如果需要使用的话,要用下面的子类:
NSInvocationOperation、NSBlockOperation、自定义子类

NSOperation的使用

在这里插入图片描述
上面两个的运行结果都是在当前线程中运行

在这里插入图片描述

上面的download方法,是在当前线程调用
而这三个下载图片操作都是在子线程中运行,且add Block只能是由NSBlockOperation创建出来的operation 才能调用的对象方法

在这里插入图片描述
上面的代码表面:只要加入队列中去,就是异步操作(开启子线程),且有队列的操作,不需要start操作

使用总结:

  • 无论NSInvocationOperation还是NSBlockOperation,只要是单独创建,且只调用start方法,那么就是在当前线程(不具备开启新的线程的能力=同步)
  • 由NSBlockOperation创建的operation,调用对象方法:addExecutionBlock,在子线程中运行
  • 无论NSInvocationOperation还是NSBlockOperation,只要放在queue中,都是子线程(不需要调用start方法)

NSOperationQueue

队列分为主队列和非主队列,他们的创建方式分别如下:
主队列:[NSOperationQueue mainQueue]
非主队列:[[NSOperationQueue alloc] init]

在这里插入图片描述
在这里插入图片描述

NSOperation实现子线程完成后,统一操作

在这里插入图片描述
operation的所有操作完成之后,再调用此方法
operation.completionBlock= ^{}

一个现象

- (void)operationListen
{
    //创建NSBlockOperation
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        for(int i = 0; i < 10; i++){
            NSLog(@"-----下载图片1-----%@", [NSThread currentThread]);
        }
    }];
    
    [operation addExecutionBlock:^{
        for(int i = 0; i < 10; i++){
            NSLog(@"-----下载图片2-----%@", [NSThread currentThread]);
        }
    }];
    
    [operation start];
}

打印结果:
2024-01-19 15:36:18.711525+0800 OC_test_09[35507:1171176] -----下载图片1-----<_NSMainThread: 0x60000040c540>{number = 1, name = main}
2024-01-19 15:36:18.711533+0800 OC_test_09[35507:1171302] -----下载图片2-----<NSThread: 0x60000044f780>{number = 7, name = (null)}

下载图片1全部在主线程,下载图片2全部在子线程中完成

- (void)operationListen
{
    //创建NSBlockOperation
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        for(int i = 0; i < 10; i++){
            NSLog(@"-----下载图片1-----%@", [NSThread currentThread]);
        }
    }];
    
    [operation addExecutionBlock:^{
        for(int i = 0; i < 10; i++){
            NSLog(@"-----下载图片2-----%@", [NSThread currentThread]);
        }
    }];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation];
    
    //start不能和addOperation同时用,会报错:
    //Thread 1: "*** -[NSBlockOperation start]: something is trying to start the receiver simultaneously from more than one thread"
    //[operation start];
}

打印结果:
2024-01-19 15:40:06.302320+0800 OC_test_09[35724:1178860] -----下载图片2-----<NSThread: 0x6000009a48c0>{number = 4, name = (null)}
2024-01-19 15:40:06.302349+0800 OC_test_09[35724:1178861] -----下载图片1-----<NSThread: 0x6000009a5600>{number = 3, name = (null)}

上面的情况,下载图片1和2全都在子线程上运行,也就是说,只有有队列参与了,都是子线程

下载图片2肯定是子线程,没想到下载图片1也在子线程

总结:

NSBlockOperation的addExecutionBlock操作肯定是子线程
然而,他自己的block操作,如果是调用[operation start]方法,是当前线程。
如果是放在queue中的,是子线程
只要是放在queue中的都是子线程
并且,不能同时把operation放在队列queue中,又调用start方法。会奔溃的

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

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

任务执行状态的控制

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

(需加强学习)

isReady
isExecuting
isFinished
isCancelled

在这里插入图片描述
在这里插入图片描述


NSThread(需加强)

在这里插入图片描述

问:如何通过NSThread结合RunLoop实现一个常驻线程?
问:NSThread的内部实现机制?

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


多线程和锁

iOS 多线程,自旋锁和互斥锁详解这个写的不错

问:iOS当中都有哪些锁?

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

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

锁,大致分两种:

  • 自旋锁
  • 互斥锁

自旋锁是一种“忙等”的锁
所谓忙等,是指:如果当前锁已被其他线程获取,那么,当前线程会不断的探测该锁是否已经被释放。如果释放,则第一时间获取该锁🔐
自旋锁使用于轻量访问

自旋锁包括:

atomic
OSSpinLock:Spin自旋

互斥锁包括:

dispatch_semaphore_t:semaphore:信号量
pthread_mutex
NSLock
NSConditionLock
NSRecursiveLock:(Recursive:循环)
@synchronized:同步的

在这里插入图片描述

多线程学习(二)

参考这个一起看

@synchronized同步

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

注意两者:
@synchronized同步
@synthesize合成
synthesize是给实例变量起个别名

更多学习关于@synthesize
@synthesize的作用

atomic

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

在这里插入图片描述

赋值 != 使用

OSSpinLock自旋锁

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

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

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

在这里插入图片描述
对同一把锁,重复加锁

解决方法:NSRecursiveLock

NSRecursiveLock

在这里插入图片描述

dispatch_semaphore_t信号量

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述

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;

有哥们找到了源码:
在这里插入图片描述
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

在这里插入图片描述

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

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


在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值