OC学习17:多线程二

张建 lol

你了解线程么?谈谈你对进程和线程的理解?

  • 进程是 系统进行资源分配和调度的基本单位,是操作系统结构的基础,进程是程序的实体。

  • 线程是 独立调度和分派的基本单位,一条线程是进程中一个单一顺序的控制流。

  • 同一进程中多条线程共享进程中的全部系统资源,一个进程有很多线程,一个进程可以并发多个线程。

iOS中,有哪些实现多线程的方式?

Pthread
特点:C语言。跨平台,可移植,使用难度大。生命周期:自己管理。
NSThread
特点:OC语言。面向对象,简单易用,可直接操作线程。生命周期:自己管理。
GCD
特点:替代 NSThread,充分利用多核的技术。生命周期:系统管理。
NSOperaton
特点:基于GCD的封装。比GCD多了一些简单实用的功能。生命周期:系统管理。

请说一下多线程中 GCD 和 NSOperation 的区别?

GCD:

  • 提供了一次性执行的代码 dispatch_once ,也就是说保证了一段代码在程序执行的过程中只被执行一次,并且是线程安全的!,实现的单例。

  • 提供了 延迟执行 的简便方法 dispatch_after

  • 提供了 调度组 的使用,监听一些列异步方法之行结束之后,我们得到统一的通知

    • dispatch_group
    • dispatch_group_async
    • dispatch_group_notify
    • dispatch_group_enter/dispatch_group_leave
  • 提供了 快速迭代的方式dispatch_apply。按照指定的次序将制定的任务追加到指定的队列中,并等待全部队列执行结束!

  • 提供了信号量 dispatch_semaphore_t,使用信号量可以实现安全的多线程!(加锁的一种方式)

    • dispatch_semaphore_wait:信号量减1,阻塞当前线程
    • dispatch_semaphore_signal:信号量加1,释放当前线程
  • 提供了栅栏函数 dispatch_barrier_async,使用栅栏函数 可以实现线程的多读单写

​​​​​​NSOpearion:

  • NSOperatoin是对GCD更高层次的封装

  • NSOperation可以设置两个操作之间的 依赖关系

  • NSOperation是个抽象类,开发中使用它的两个子类,NSBlockOperation/NSInvocationOperation。

  • 使用KVO,观察NSOperation的 各种状态(isExecuted是否正在执行,isFinished是否结束,isCancled是否取消)。无法判断GCD的状态。

  • NSOperation可以设置操作的 优先级

  • NSoperation可以方便的取消一个操作的执行

  • 可以重写NSOperation的main和start函数。

dispatch_once 是怎么保证线程安全的?

定义一个dispatch_once_t的静态变量,标识下面的diapatch_once的block是否执行过了,static修饰会默认将 onceToken其初始化为0,当值为0时才会 执行block代码块 里面的内容,此时onceToken不为0,当block执行完成,底层会将oneceToken设置为-1,以后再调用的话不会再走block代码块。

dispatch_after延迟执行,执行时间是准确的吗?

dispatch_after的延迟执行时间不是准确的,因为dispatch_after是在指定时间之后将任务添加到主队列,并不是在指定时间之后开始执行处理!

说说你对 dispatch_apply 的理解?

  • dispatch_apply 是GCD提供的一种 快速迭代的函数,按照指定的次数将指定的任务追加到指定的队列中,并等待全部任务结束。

  • 如果用在串行队列,就和for循环一样,按顺序同步执行。

  • 如果用在并发队列,追加到队列的任务会异步执行,并且等待全部任务结束!

说说你对dispatch_group的理解?

  • GCD提供的队列组,有两种使用方式 dispatch_group_asyncdispatch_group_enter/dispatch_group_leave,使用过程中要根据任务类型选择使用哪种方式。

  • 如果任务类型是同步任务:使用 dispatch_group_asyncdispatch_group_enter/dispatch_group_leave 是同样的,可以实现相同的功能。

  • 如果任务类型是异步任务:比如(AF)网络请求,使用dispatch_group_async不能等到所有异步任务执行完成,就会去执行dispatch_group_notify中的代码,使用dispatch_group_enter/dispatch_group_leave可以实现执行完添加的异步任务,最后执行dispatch_group_notify中的代码

  • 如果你要实现这样一个功能,请求网络A和B,然后根据A/B返回的内容去刷新页面,如果使用dispatch_group,那么只能使用 dispatch_grouo_enter/dispatch_grouo_leave !!!使用dispatch_group_async是不能实现这个功能的!

说说你项目中的哪些功能使用了dispatch_semaphore_t,解决了什么问题?

使用信号量 dispatch_semaphore_t 可以实现 异步任务的顺序执行(也就是 将异步任务转换为同步任务执行)不要阻塞主线程!。也是多线程加锁的一种实现方式,保证线程安全。

  • dispatch_semaphore_create(intptr_t value) 创建一个队列组,传入得值>=0,传入的值控制控制并发线程的数量!!!,如果我们传入2,那么就表示当前最多有两个线程同时执行。

  • dispatch_semaphore_signal(dispatch_semaphore_t dsema) 增加信号量,使信号量的值加1!

  • dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout) 等待当前线程,直到某个时间释放!!!

说说你对NSOperation/NSOperationQueue的使用和理解?

  1. NSOperation/NSOperationQueue 是系统提供的一套多线程实现方案。实际上NSOperation/NSOperationQueue是基于GCD更高层次的封装,完全面向对象,比GCD简单易用,代码可读性更高。

  2. 使用步骤:

  • 创建操作,将操作封装到NSOperation对象中,执行的顺序取决于操作之间的相对优先级,操作执行结束的顺序,取决于操作本身!

  • 创建队列 NSOperationQueue,将操作添加到队列中,一个队列中同时能并发执行的最大操作数由maxConcurrentOperationCount 决定,也就是一个操作队列中的操作是串行还是并发执行,由maxConcurrentOperationCount它决定!

    • maxConcurrentOperationCount = -1,默认,并发执行
    • maxConcurrentOperationCount = 1,串行执行
    • maxConcurrentOperationCount = 3,并发执行
  • 系统会将队列中的操作取出,在新线程中执行操作。

  • 操作有几种状态

    • op1.isReady; 是否准备就绪
    • op1.isExecuting; 是否正在执行
    • op1.isCancelled; 是否已经取消
    • op1.isFinished; 是否执行完成
  • 取消操作和队列

    • [op1 cancel]; 取消操作,实际上是标记isCancelled状态
    • [queue cancelAllOperations]; 取消队列

你是否在定义过NSOperation?

自定义NSOperation可以通过重写main或者start方法。重写main方法,不需要管理操作的状态属性isExecuting和isFinished。重写start方法需要管理操作的状态属性。

【面试题】:如果在工作中有这样一个需求,使用AFNetworking请求A接口拿到A接口返回的id_a,用id_a作为参数去请求B接口,拿到B网络返回的name_b去查数据库,然后刷新页面。该怎么实现呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@property (nonatomic, assign) dispatch_semaphore_t semaphore;
@property (nonatomic, assign) dispatch_queue_t queue;

- (void)viewDidLoad{
[super viewDidLoad];
//:测试
[self semaphoreSync];
}

- (void)semaphoreSync{
// 创建信号量,传入参数0
self.semaphore = dispatch_semaphore_create(0);
// 创建队列,这里串行和并发并无区别
self.queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
// 开启一个新线程,
// 这里之所以要创建一个新线程,而不是在当前(主线程)执行,是因为,AF的网络请求返回默认是在主线程中执行,如果我们在当前线程执行一下操作,会发生线程死锁的现象,
dispatch_async(self.queue, ^{
// 任务A
int ida = [self requestA];
// 任务B
NSString *name = [self requestB:ida];
// 任务C
NSDictionary *res = [self queryDB:name];
NSLog(@"%@", res);
dispatch_async(dispatch_get_main_queue(), ^{
// 刷新页面
});
});
}

- (int)requestA{
__block int ida = 0;
// AF
NSArray *paths = @[@(self.currentPage), @(pageNum), @(100)];
[[ZJNetWorkManager shareManager] GetWithUrlString:TC_API(GetCourseList) paths:paths successedBlock:^(BOOL successed, id _Nonnull jsonObject) {
ida = 1;
// 释放信号量,信号量加1,释放当前线程,然后执行return操作
dispatch_semaphore_signal(self.semaphore);
} failedBlock:^(NSError * _Nonnull error) {
dispatch_semaphore_signal(self.semaphore);
}];
// 信号量减1,阻塞当前线程
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);

return ida;
}

- (NSString *)requestB:(int)ida{
__block NSString *name;
NSArray *paths = @[@(self.currentPage), @(pageNum), @(100), @(ida)];
[[ZJNetWorkManager shareManager] GetWithUrlString:TC_API(GetCourseList) paths:paths successedBlock:^(BOOL successed, id _Nonnull jsonObject) {
name = @"你好👋";
dispatch_semaphore_signal(self.semaphore);
} failedBlock:^(NSError * _Nonnull error) {
dispatch_semaphore_signal(self.semaphore);
}];

dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);

return name;
}
- (NSDictionary *)queryDB:(NSString *)name{
//查询数据库,返回结果
return @{@"name":@"name"};
}

【面试题】多个网络请求异步完成-执行最后一个?

  • dispatch_group_t + dispatch_group_enter() + dispatch_group_leave() + dispatch_group_notify
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//资源
NSString *str = @"http://www.jianshu.com/p/6930f335adba";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];

// 1.GCD线程组
dispatch_group_t downloadGroup = dispatch_group_create();
for (int i=0; i<10; i++) {
dispatch_group_enter(downloadGroup);
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

NSLog(@"%d---%d",i,i);
dispatch_group_leave(downloadGroup);

}];

[task resume];
}
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
NSLog(@"end");
});

=========
023-03-18 00:00:29.418934+0800 线程等待[9494:456991] 1---1
2023-03-18 00:00:29.434940+0800 线程等待[9494:456990] 7---7
2023-03-18 00:00:29.435649+0800 线程等待[9494:456989] 8---8
2023-03-18 00:00:29.437444+0800 线程等待[9494:456988] 5---5
2023-03-18 00:00:29.437915+0800 线程等待[9494:456988] 9---9
2023-03-18 00:00:29.439086+0800 线程等待[9494:456988] 2---2
2023-03-18 00:00:29.439832+0800 线程等待[9494:456988] 3---3
2023-03-18 00:00:29.447357+0800 线程等待[9494:457208] 4---4
2023-03-18 00:00:29.449366+0800 线程等待[9494:457208] 0---0
2023-03-18 00:00:29.454452+0800 线程等待[9494:456988] 6---6
2023-03-18 00:00:29.454704+0800 线程等待[9494:456826] end

【面试题】多个网络请求同步步完成-执行最后一个?

  • dispatch_semphore_t + dispatch_semphore_signal(semphore) + dispatch_semphore_wait(semphore)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 NSString *str = @"http://www.jianshu.com/p/6930f335adba";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];

// 信号量 < 0 阻塞
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
for (int i=0; i<10; i++) {
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

NSLog(@"%d---%d",i,i);
// 信号量 +1
dispatch_semaphore_signal(sem);
}];

[task resume];
// 信号量 -1
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}

dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"end");
});

【面试题】请实现一个多读单写的功能?

我们可以用 dispatch_barrier_async 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@interface TKReadWhiteSafeDic() {
// 定义一个并发队列
dispatch_queue_t concurrent_queue;

// 用户数据中心, 可能多个线程需要数据访问
NSMutableDictionary *userCenterDic;
}

@end

// 多读单写模型
@implementation TKReadWhiteSafeDic

- (id)init {
self = [super init];
if (self) {
// 通过宏定义 DISPATCH_QUEUE_CONCURRENT 创建一个并发队列
concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
// 创建数据容器
userCenterDic = [NSMutableDictionary dictionary];
}
return self;
}

- (id)objectForKey:(NSString *)key {
__block id obj;
// 同步读取指定数据
dispatch_sync(concurrent_queue, ^{
obj = [userCenterDic objectForKey:key];
});
return obj;
}

- (void)setObject:(id)obj forKey:(NSString *)key {
// 异步栅栏调用设置数据
dispatch_barrier_async(concurrent_queue, ^{
[userCenterDic setObject:obj forKey:key];
});
}

  • 读操作为啥同步 dispatch_sync

    读的话通常都是直接想要结果,需要同步返回结果,如果是异步获取的话就根网络请求一样了。

  • 写操作为啥异步dispatch_barrier_async

    写操作是因为不需要等待写操作完成,所以用异步。

线程死锁

  1. 同步主线程死锁
1
2
3
4
// 1.同步-主线程会造成死锁 :输出结果只有1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"a");
});

造成 死锁

  1. 异步主线程不死锁
1
2
3
4
5
6
7
8
9
10
// 2.异步-主线程不会造成死锁:
NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"4");
});
NSLog(@"5");
});
NSLog(@"3");

结论:1、3、2、4、5 或 1、2、3、4、5
分析:1、3在主线程先打印;异步开启 子线程,2 打印;同步不开启 子线程,先打印 4,在打印 5

  • 换一种写法
1
2
3
4
5
6
7
8
9
NSLog(@"1");
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"4");
});
NSLog(@"5");
});
NSLog(@"3");

结论:1、2、5、3、4
分析:1 在主线程先打印;同步不开启 子线程,2、5 先打印;异步开启 子线程,打印 3,在打印 4

  1. 串行队列-同步主线程-死锁
1
2
3
4
5
6
7
8
9
10
11
12
// 3.主线程死锁
// 创建一个串行队列
dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); // 任务1
dispatch_async(queue, ^{ //block1
NSLog(@"2"); // 任务2
dispatch_sync(queue, ^{ //block2
NSLog(@"3"); // 任务3
});
NSLog(@"4"); // 任务4
});
NSLog(@"5"); // 任务5

结论:1、5、2
分析:1、5先打印,异步开启子线程 2 打印,之后由于串行队列 3 与 4 相互等待造成 死锁

  1. 串行-同步-死锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 4.主线程死锁
dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); // 任务1
dispatch_async(queue, ^{ //block1
NSLog(@"2"); // 任务2
dispatch_async(queue, ^{ //block2
NSLog(@"3"); // 任务3
});
dispatch_sync(queue, ^{ //block3
NSLog(@"4"); // 任务3
});
NSLog(@"5"); // 任务4
});
NSLog(@"6"); // 任务5

结论:1、6、2
分析:1、6先打印,异步开启子线程 2 打印,又开启子线程由于串行队列 4 与 5 相互等待造成 死锁

  • Post title:OC学习17:多线程二
  • Post author:张建
  • Create time:2023-03-10 02:22:02
  • Post link:https://redefine.ohevan.com/2023/03/10/OC/OC学习17:多线程二/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.