多线程相关概念
进程与线程
进程概念: 进程是程序在计算机上的一次执行活动,打开一个app,就开启了一个进程,可包含多个线程。
线程概念: 独立执行的代码段,一个线程同时间只能执行一个任务,反之多线程并发就可以在同一时间执行多个任务。
iOS程序中,主线程(又叫作UI线程)主要任务是处理UI事件,显示和刷新UI,(只有主线程有直接修改UI的能力)耗时的操作放在子线程(又叫作后台线程、异步线程)。在iOS中开子线程去处理耗时的操作,可以有效提高程序的执行效率,提高资源利用率。但是开启线程会占用一定的内存,(主线程的堆栈大小是1M,第二个线程开始都是512KB,并且该值不能通过编译器开关或线程API函数来更改)降低程序的性能。所以一般不要同时开很多线程。
线程相关
同步线程:同步线程会阻塞当前线程去执行线程内的任务,执行完之后才会反回当前线程。
异步线程:异步线程不会阻塞当前线程,会开启其他线程去执行线程内的任务。
串行队列:线程任务按先后顺序逐个执行(需要等待队列里面前面的任务执行完之后再执行新的任务)。
并发队列:多个任务按添加顺序一起开始执行(不用等待前面的任务执行完再执行新的任务),但是添加间隔往往忽略不计,所以看着像是一起执行的。
并发VS并行:并行是基于多核设备的,并行一定是并发,并发不一定是并行。
多线程中会出现的问题
Critical Section(临界代码段)
指的是不能同时被两个线程访问的代码段,比如一个变量,被并发进程访问后可能会改变变量值,造成数据污染(数据共享问题)。
Race Condition (竞态条件)
当多个线程同时访问共享的数据时,会发生争用情形,第一个线程读取改变了一个变量的值,第二个线程也读取改变了这个变量的值,两个线程同时操作了该变量,此时他们会发生竞争来看哪个线程会最后写入这个变量,最后被写入的值将会被保留下来。
Deadlock (死锁)
两个(多个)线程都要等待对方完成某个操作才能进行下一步,这时就会发生死锁。
Thread Safe(线程安全)
一段线程安全的代码(对象),可以同时被多个线程或并发的任务调度,不会产生问题,非线程安全的只能按次序被访问。
所有Mutable对象都是非线程安全的,所有Immutable对象都是线程安全的,使用Mutable对象,一定要用同步锁来同步访问(@synchronized)。
互斥锁:能够防止多线程抢夺造成的数据安全问题,但是需要消耗大量的资源
原子属性(atomic)加锁
atomic: 原子属性,为setter方法加锁,将属性以atomic的形式来声明,该属性变量就能支持互斥锁了。
nonatomic: 非原子属性,不会为setter方法加锁,声明为该属性的变量,客户端应尽量避免多线程争夺同一资源。
Context Switch (上下文切换)
当一个进程中有多个线程来回切换时,context switch用来记录执行状态,这样的进程和一般的多线程进程没有太大差别,但会产生一些额外的开销。
多线程编程技术的优缺点比较
NSThread (抽象层次:低)
优点:轻量级,简单易用,可以直接操作线程对象
缺点: 需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销。
Cocoa NSOperation (抽象层次:中)
优点:不需要关心线程管理,数据同步的事情,可以把精力放在学要执行的操作上。基于GCD,是对GCD 的封装,比GCD更加面向对象
缺点: NSOperation是个抽象类,使用它必须使用它的子类,可以实现它或者使用它定义好的两个子类NSInvocationOperation、NSBlockOperation.
GCD 全称Grand Center Dispatch (抽象层次:高)
优点:是 Apple 开发的一个多核编程的解决方法,简单易用,效率高,速度快,基于C语言,更底层更高效,并且不是Cocoa框架的一部分,自动管理线程生命周期(创建线程、调度任务、销毁线程)。
缺点: 使用GCD的场景如果很复杂,就有非常大的可能遇到死锁问题。
GCD抽象层次最高,使用也简单,因此,苹果也推荐使用GCD
GCD中的队列类型
GCD编程的核心就是dispatch队列,dispatch block的执行最终都会放进某个队列中去进行。
The main queue(主线程串行队列): 与主线程功能相同,提交至Main queue的任务会在主线程中执行,
Main queue 可以通过dispatch_get_main_queue()来获取。
Global queue(全局并发队列): 全局并发队列由整个进程共享,有高、中(默认)、低、后台四个优先级别。
Global queue 可以通过调用dispatch_get_global_queue函数来获取(可以设置优先级)
Custom queue (自定义队列): 可以为串行,也可以为并发。
Custom queue 可以通过dispatch_queue_create()来获取;
Group queue (队列组):将多线程进行分组,最大的好处是可获知所有线程的完成情况。
Group queue 可以通过调用dispatch_group_create()来获取,通过dispatch_group_notify,可以直接监听组里所有线程完成情况。
重点来了哈
GCD在项目中的实际应用
GCD在项目中的实际应用
GCD在项目中的实际应用(一定要说够三遍!!!)
以下请求以afn为例
场景一
个页面多个网络请求,并且多个请求同时执行,都执行完成以后进行数据操作
实现方式1:dipatch_group
AFHTTPSessionManager *MANAGER = [AFHTTPSessionManager manager];
dispatch_group_enter(self.group);
[MANAGER POST:@"" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) {
dispatch_group_leave(self.group);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
dispatch_group_leave(self.group);
}];
dispatch_group_enter(self.group);
[MANAGER POST:@"" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) {
dispatch_group_leave(self.group);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
dispatch_group_leave(self.group);
}];
dispatch_group_notify(self.group, self.chquue, ^{
NSLog(@"11111");
});
其他类似代码实现相同效果
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t conQueue = dispatch_queue_create("fsfsdfsf2", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t seQueue = dispatch_queue_create("asdsadasdasda", NULL);
dispatch_queue_t seQueue1 = dispatch_queue_create("asdsadasasdadassda", NULL);
//
//
dispatch_group_enter(group);
dispatch_async(conQueue, ^{
sleep(6);
NSLog(@"111111");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(seQueue, ^{
sleep(2);
NSLog(@"222222");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(seQueue1, ^{
sleep(4);
NSLog(@"33333");
dispatch_group_leave(group);
});
//
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"444444");
});
//
NSLog(@"hhahahaha");
其中的队列可以是不同的队列,只要group是同一个,都会得到最后执行notify代码的效果,如果是同一个串行队列,则会按照1-2-3-4的顺序串行执行以上4个任务
实现方式2:dispatch_semaphore
dispatch_async(self.chquue, ^{
dispatch_semaphore_t SEM = dispatch_semaphore_create(0);
AFHTTPSessionManager *MANAGER = [AFHTTPSessionManager manager];
[MANAGER POST:@"" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) {
dispatch_semaphore_signal(SEM);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
dispatch_semaphore_signal(SEM);
}];
dispatch_semaphore_t SEM2 = dispatch_semaphore_create(0);
[MANAGER POST:@"" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) {
dispatch_semaphore_signal(SEM2);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
dispatch_semaphore_signal(SEM2);
}];
dispatch_semaphore_wait(SEM, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait(SEM2, DISPATCH_TIME_FOREVER);
NSLog(@"hahaha");
});
NSLog(@"hello");
此处为什么要套一个dispatch_ASYNC呢?这样做是为了防止阻塞主线程的任务执行!
场景二
一个页面多个网络请求,并且多个请求串执行,下一个请求需要等待上一个的请求结果才能继续请求
实现方式1:dipatch_group,如何使用group的方式来实现这个需求,欢迎大神提出意见
实现方式2:dispatch_semaphore
dispatch_async(self.chquue, ^{
dispatch_semaphore_t SEM = dispatch_semaphore_create(0);
AFHTTPSessionManager *MANAGER = [AFHTTPSessionManager manager];
[MANAGER POST:@"" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) {
dispatch_semaphore_signal(SEM);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
dispatch_semaphore_signal(SEM);
}];
dispatch_semaphore_wait(SEM, DISPATCH_TIME_FOREVER);
[MANAGER POST:@"" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) {
dispatch_semaphore_signal(SEM);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
dispatch_semaphore_signal(SEM);
}];
dispatch_semaphore_wait(SEM, DISPATCH_TIME_FOREVER);
NSLog(@"hahaha");
});
NSLog(@"hello");
这段代码的关键点是只使用一个sem来实现串行执行异步任务
当然如果哪位大神有更好的实现方式欢迎指点!!
场景三
死锁
dispatch_queue_t serialQueue = dispatch_queue_create("com.dullgrass.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
NSLog(@"会执行的代码");
//当嵌套的这个任务是同步且在串行队列中执行时,就会造成死锁
dispatch_sync(serialQueue, ^{
NSLog(@"代码不执行");
});
});
关于dispatc_group
dispatch_enter和dispatch_leave要成对出现,否则奔溃。
补充
关于dispatch_set_target_queue
场景一
使用dispatch_set_target_queue更改Dispatch Queue的执行优先级
dispatch_queue_create函数生成的DisPatch Queue不管是Serial DisPatch Queue还是Concurrent Dispatch Queue,执行的优先级都与默认优先级的Global Dispatch queue相同,如果需要变更生成的Dispatch Queue的执行优先级则需要使用dispatch_set_target_queue函数
- (void)testTeagerQueue1 {
2 dispatch_queue_t serialQueue = dispatch_queue_create("com.oukavip.www",NULL);
3 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
4
5 dispatch_set_target_queue(serialQueue, globalQueue);
6 // 第一个参数为要设置优先级的queue,第二个参数是参照物,既将第一个queue的优先级和第二个queue的优先级设置一样。
7 }
场景二
使用dispatch_set_target_queue将多个串行的queue指定到了同一目标,那么着多个串行queue在目标queue上就是同步执行的,不再是并行执行。
- (void)testTargetQueue {
//1.创建目标队列
dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL);
//2.创建3个串行队列
dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL);
//3.将3个串行队列分别添加到目标队列
dispatch_set_target_queue(queue1, targetQueue);
dispatch_set_target_queue(queue2, targetQueue);
dispatch_set_target_queue(queue3, targetQueue);
dispatch_async(queue1, ^{
NSLog(@"1 in");
[NSThread sleepForTimeInterval:3.f];
NSLog(@"1 out");
});
dispatch_async(queue2, ^{
NSLog(@"2 in");
[NSThread sleepForTimeInterval:2.f];
NSLog(@"2 out");
});
dispatch_async(queue3, ^{
NSLog(@"3 in");
[NSThread sleepForTimeInterval:1.f];
NSLog(@"3 out");
});
}
以上是我在工作中遇到的关于gcd的一些总结,欢迎各路大神补充,共同进步!!如果您看过文章觉得用得上,请给个赞谢谢!