目录
一、进程、线程、多线程
二、iOS中的多线程实现方案
1、pthread
2、NSThread
3、GCD
4、NSOperation
多线程:iOS中的多线程实现方案
多线程:GCD
多线程:数据竞争问题与线程同步方案
一、进程、线程、多线程
进程就是指操作系统上正在运行的应用程序。
线程就是指一段代码从头到尾的执行路径,具体地说我们编写的OC代码最终都会被编译成二进制代码供CPU执行,那么CPU在执行这些二进制代码时是从上往下一行一行串行执行的,当遇见if
语句或for
语句等控制语句时,CPU会偏离当前地址处的二进制代码去执行其它地址处的二进制代码,但执行完后又会返回来执行当前地址处的二进制代码,直到二进制代码执行完毕,这样一段代码从头到尾的执行路径就被称为一个线程。
而开辟一个新线程就是指把某段代码的执行路径和另一段代码的执行路径给完全独立开来,成为CPU的一个单独调度单位,所以当一个进程中有多个这样独立的代码的执行路径时,就是多线程。使用多线程的好处是可以提高程序的执行效率,使用多线程的坏处是开辟过多的线程会占用大量内存和CPU资源,而且还会存在数据竞争问题,因此通常开三到五个线程就差不多了。我们都知道一个CPU一次只能调度一个线程,那苹果是怎么实现多线程并发的呢?原来苹果会让CPU一会儿执行线程1,一会执行线程2,当线程之间的切换时间足够短时,就让我们感觉CPU是在一次执行多个线程,而现在设备都是多核的了,就不仅仅是“感觉”了,而是真得可以利用多个CPU来实现多线程并发。主线程主要用来显示/刷新UI界面和处理UI事件(如点击事件、滚动事件等),子线程主要用来做耗时操作。
二、iOS中的多线程实现方案
实现方案 | 简介 | 语言 | 线程生命周期 | 使用频率 |
---|---|---|---|---|
pthread | 是一套跨平台的多线程API,但使用难度较大 | C | 程序员管理 | 几乎不用 |
NSThread | 是对pthread的OC封装,使用更加面向对象,轻量级、灵活 | OC | 程序员管理 | 偶尔使用 |
GCD | 充分利用设备的多核,旨在替代NSThread等实现方案 | C | 自动管理 | 经常使用 |
NSOperation | 是对GCD的OC封装,使用更加面向对象,并且提供了非常简单易用的API来实现设置线程最大并发数和设置任务之间的依赖关系 | OC | 自动管理 | 经常使用 |
1、pthread
#import "PthreadViewController.h"
#import <pthread.h>
@interface PthreadViewController ()
@end
@implementation PthreadViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"pthread";
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// // ❌主线程做耗时操作...
// for (int i = 0; i <= 10000; i++) {
// NSLog(@"%d---%@", i, [NSThread currentThread]);
// }
// 1、子线程对象
pthread_t pthread;
// 2、创建子线程
//
// 第一个参数:子线程对象的地址
// 第二个参数:子线程的属性,可以传个nil
// 第三个参数:子线程对应函数的指针
// 第四个参数:子线程对应函数的参数,可以传个nil
pthread_create(&pthread, nil, subThreadAction, nil);
}
void *subThreadAction(void *params) {
// ✅子线程做耗时操作...
for (int i = 0; i <= 10000; i++) {
NSLog(@"%d---%@", i, [NSThread currentThread]);
}
return nil;
}
@end
2、NSThread
#import "NSThreadViewController.h"
@interface NSThreadViewController ()
@end
@implementation NSThreadViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"NSThread";
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// // ❌主线程做耗时操作...
// for (int i = 0; i <= 10000; i++) {
// NSLog(@"%d---%@", i, [NSThread currentThread]);
// }
[self createSubThread1];
// [self createSubThread2];
// [self createSubThread3];
}
/// 创建子线程的方式1
- (void)createSubThread1 {
// 1、创建子线程(此时线程处于新建状态)
//
// NSThread的生命周期是由我们程序员管理的,但我们也只需要负责创建线程就可以了
// 至于线程的销毁时机,它有点特别,你别看这里subThreadA是个局部变量,出了createSubThread1的作用域就会销毁,但是它指向的线程对象却不会销毁,线程的销毁时机是它内部的任务执行完后(此时线程处于销毁状态),也就是subThreadAction这个方法执行完后,系统会做这件事,我们不用管
// 当然我们也可以通过[NSThread exit]手动退出这个线程,也就是把这个线程销毁掉
NSThread *subThreadA = [[NSThread alloc] initWithTarget:self selector:@selector(subThreadAction) object:nil];
// 设置子线程的名字
subThreadA.name = @"我是线程A";
// 设置子线程的优先级(0~1,默认值0.5),可以改变CPU调度该子线程的概率
subThreadA.threadPriority = 1;
// 2、启动子线程(此时线程会被放入线程池,处于运行状态)
[subThreadA start];
NSThread *subThreadB = [[NSThread alloc] initWithTarget:self selector:@selector(subThreadAction) object:nil];
subThreadB.name = @"我是线程B";
subThreadB.threadPriority = 0.5;
[subThreadB start];
NSThread *subThreadC = [[NSThread alloc] initWithTarget:self selector:@selector(subThreadAction) object:nil];
subThreadC.name = @"我是线程C";
subThreadC.threadPriority = 0.25;
[subThreadC start];
}
/// 创建子线程的方式2
- (void)createSubThread2 {
// 创建子线程,会自动启动
[NSThread detachNewThreadSelector:@selector(subThreadAction) toTarget:self withObject:nil];
}
/// 创建子线程的方式3
- (void)createSubThread3 {
// 创建子线程,会自动启动
[self performSelectorInBackground:@selector(subThreadAction) withObject:nil];
}
- (void)subThreadAction {
// ✅子线程做耗时操作...
for (int i = 0; i <= 10000; i++) {
NSLog(@"%d---%@", i, [NSThread currentThread]);
}
}
@end
3、GCD
4、NSOperation
GCD里有两对儿重要的概念是同步/异步追加任务和队列,NSOperation是对GCD的OC封装,所以它也有两对儿重要的概念是同步/异步追加任务和队列。
4.1 异步追加
GCD里有dispatch_sync
和dispatch_async
之分,要想实现多线程开发就必须得用dispatch_async
,dispatch_sync
使用不当还会造成死锁。而NSOperation里没有同步追加,只有异步追加,只要我们创建一个任务,把任务追加到队列里时就是异步追加。
- 通过
selector
创建一个任务
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(threadAction) object:nil];
- 通过
block
创建一个任务
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
// 任务...
}];
- 把任务追加到队列中
[queue addOperation:operation];
4.2 并发队列和主队列
GCD里有串行队列和并发队列之分,如果想让多个任务并发执行,就必须得用并发队列,而如果想让多个任务串行执行,就必须得用串行队列。而NSOperation
里没有串行队列(当然主队列是个串行队列),只有并发队列(当然我们把并发队列的线程最大并发数设置为1时也能实现串行队列的效果),只要我们创建一个队列,它就是并发队列。
- 并发队列
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
- 主队列
// 获取主队列
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
4.3 简单使用
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i = 0; i < 10; i++) {
// 创建任务
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(threadAction) object:nil];
// 把任务追加到队列中
[queue addOperation:operation];
}
}
- (void)threadAction {
NSLog(@"执行任务,%@", [NSThread currentThread]);
}
@end
// 控制台打印:
执行任务,<NSThread: 0x600001909a00>{number = 6, name = (null)}
执行任务,<NSThread: 0x600001916080>{number = 7, name = (null)}
执行任务,<NSThread: 0x600001927000>{number = 5, name = (null)}
执行任务,<NSThread: 0x600001909a00>{number = 6, name = (null)}
执行任务,<NSThread: 0x600001927000>{number = 5, name = (null)}
执行任务,<NSThread: 0x600001916080>{number = 7, name = (null)}
执行任务,<NSThread: 0x600001925780>{number = 4, name = (null)}
执行任务,<NSThread: 0x600001909a00>{number = 6, name = (null)}
执行任务,<NSThread: 0x600001927000>{number = 5, name = (null)}
执行任务,<NSThread: 0x600001916080>{number = 7, name = (null)}
4.4 其它常用API
- 设置线程最大并发数(等同于GCD里使用信号量设置线程最大并发数)
当我们设置NSOperationQueue的线程最大并发数为1时,这个队列就实现串行队列的效果了,需要注意的是设置线程最大并发数为1 != 只开一条线程,而是指线程同步。
#import "NSOperationViewController.h"
@interface NSOperationViewController ()
@end
@implementation NSOperationViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"NSOperation";
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 设置线程最大并发数
queue.maxConcurrentOperationCount = 1;
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1---%@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2---%@", [NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3---%@", [NSThread currentThread]);
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"4---%@", [NSThread currentThread]);
}];
NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"5---%@", [NSThread currentThread]);
}];
NSBlockOperation *op6 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"6---%@", [NSThread currentThread]);
}];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
[queue addOperation:op4];
[queue addOperation:op5];
[queue addOperation:op6];
}
@end
// 控制台打印(可见开了两条线程,但线程是同步执行的):
1---<NSThread: 0x6000033382c0>{number = 3, name = (null)}
2---<NSThread: 0x60000332f000>{number = 5, name = (null)}
3---<NSThread: 0x6000033382c0>{number = 3, name = (null)}
4---<NSThread: 0x6000033382c0>{number = 3, name = (null)}
5---<NSThread: 0x6000033382c0>{number = 3, name = (null)}
6---<NSThread: 0x6000033382c0>{number = 3, name = (null)}
- 设置任务之间的依赖关系(等同于GCD里使用队列组来控制任务的执行顺序,但要比GCD队列组强大得多得多)
NSOperation设置任务之间的依赖关系功能真得很强大,即便是不同队列里的任务也可以设置依赖关系,需要注意的是不要设置循环依赖就可以了。
#import "NSOperationViewController.h"
@interface NSOperationViewController ()
@end
@implementation NSOperationViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"NSOperation";
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1---%@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2---%@", [NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3---%@", [NSThread currentThread]);
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"4---%@", [NSThread currentThread]);
}];
NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"5---%@", [NSThread currentThread]);
}];
NSBlockOperation *op6 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"6---%@", [NSThread currentThread]);
}];
// 比如我们希望任务的执行顺序为:6、3、5、2、4、1,那么可以设置任务之间的依赖关系
[op3 addDependency:op6];
[op5 addDependency:op3];
[op2 addDependency:op5];
[op4 addDependency:op2];
[op1 addDependency:op4];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
[queue addOperation:op4];
[queue addOperation:op5];
[queue addOperation:op6];
}
@end
// 控制台打印(可见任务是按我们指定的顺序执行的):
6---<NSThread: 0x6000011dd5c0>{number = 3, name = (null)}
3---<NSThread: 0x6000011de240>{number = 2, name = (null)}
5---<NSThread: 0x6000011dd5c0>{number = 3, name = (null)}
2---<NSThread: 0x6000011dd5c0>{number = 3, name = (null)}
4---<NSThread: 0x6000011de240>{number = 2, name = (null)}
1---<NSThread: 0x6000011dd5c0>{number = 3, name = (null)}
网友评论