串行并行的定义
串行:一个线程中执行多个任务,只能一个一个的按照顺序执行这些任务。
并行:一个进程中可以开启多条线程,每条线程可以同时执行不同的任务
多线程的原理
同一时间,cpu只能处理1条线程,只有1条线程在工作,如果存在多条线程,实际上是线程间以非常非常快的速度进行切换,CPU调度线程的速度非常快,看起来就像同时进行。需要注意的是,线程不宜太多,会消耗大量CPU资源
优点
能适当提高程序的执行效率,适当的提高CPU和内存利用率
缺点:创建线程是有开销的,线程的创建需要90毫秒的时间,如果大量的开启线程,会降低程序的性能,而且线程越多,程序越复杂,比如线程之间的通信,多线程的数据共享
安全隐患
- 资源共享
一块资源可以被多个线程同时访问(例如,3个线程同时修改一个文件)
解决方案:加同步锁 @synchronized(锁对象){需要锁定的代码 }
多个线程之间锁对象必须是同一个,保证线程之间用的锁是同一个,否则锁是无效的,一般用self就可以
使用前提:多条线程抢夺同一块资源的时候,需要对数据访问修改等问题时
@synchronized (self) {
// 要锁定的操作
NSLog(@"%@", [NSThread currentThread]);
}
- 原子和非原子属性
atomic:原子属性,setter方法加锁,调用setter方法前加锁,调用之后锁打开,防止多条线程对属性值的修改,会消耗大量的资源
nonatomic:非原子属性,适合内存小的移动设备,setter方法和getter方法大部分都在主线程访问,如有在子线程访问的,可单独加锁,在写代码的过程中尽量避免多线程抢夺同一块资源
线程间通信
包括线程间数据传递、在一个线程中执行完特定任务后跳转到其他线程继续执行任务
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
_picView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
[self.view addSubview:_picView];
// 创建线程
[self performSelectorInBackground:@selector(downLoadPic) withObject:nil];
}
- (void)downLoadPic
{
NSURL *url = [NSURL URLWithString:@""];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *pic = [UIImage imageWithData:data];
// 回主线程
// [_picView performSelectorOnMainThread:@selector(setPicView:) withObject:pic waitUntilDone:YES];
[_picView performSelector:@selector(setPicView:) onThread:[NSThread mainThread] withObject:pic waitUntilDone:YES];
}
线程实现方法
1.pthread
基于C语言,需要手动管理,平时几乎不用
先导入头文件#import <pthread.h>
// 指向函数的指针
void *run(void *param)
{
return NULL;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
pthread_t thread;
pthread_create(&thread, NULL, run, NULL);
}
2.NSThread
基于OC, 需要手动管理,常用的就几个方法
+(NSThread *)mainThread; // 获取主线程
+(BOOL)isMainThread; // 是否为主线程
-(BOOL)isMainThread; // 是否为主线程
+(NSThread *)currentThread; // 获取当前线程
-(void)setName:(NSString *)name; // 设置线程名字
-(NSString *)name; // 获取线程名字
+(void)sleepUntilDate:(NSDate *)date; // 休眠到什么时间
+(void)sleepForTimeInterval:(NSTimeInterval)ti; // 休眠几秒钟
+(void)exit; // 退出当前线程,停止了就不能恢复了
三种创建方法如下
- (void)run
{
// 打印当前线程
NSLog(@"%@", [NSThread currentThread]);
}
- (void)createThread1
{
// 创建线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:@"louise"];
// 启动线程
[thread start];
}
- (void)createThread2
{
// 不需要启动,没有返回值,拿不到刚刚创建的线程,所以不能设置名字,用来处理简单的操作
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:@"louise"];
}
- (void)createThread3
{
// 隐式创建,看不出来是创建线程
[self performSelectorInBackground:@selector(run) withObject:@"louise"];
}
3.GCD
优势:自动管理线程的生命周期(创建,任务的调度,销毁线程)
核心概念:任务(执行什么操作)、队列(用来存放任务)执行任务:
同步:只能在当前线程中执行任务,不具备开启新线程的能力:dispatch_sync
异步:可以在新的线程中执行任务,具备开启新线程的能力:dispatch_async队列的类型
并发队列:可以让多个任务并发执行,并发功能只能在异步(dispatch_async)函数下才有效
串行队列:让任务一个接着一个的执行-
区分几个比较容易混淆的词语
同步和异步:在于能否开启新的线程,同步只能在当前线程执行任务,不具备开线程的能力,异步可以在新的线程中执行任务,具备开线程的能力并发和串行:并发是多个任务并发执行,串行是一个任务接一个任务的执行
并发/串行队列&同步/异步代码实现
// label相当于队列的名字
// DISPATCH_QUEUE_CONCURRENT 并发队列
// DISPATCH_QUEUE_SERIAL 串行队列
// 1.创建一个串行队列
// dispatch_queue_t queue = dispatch_queue_create("serial_chuan", DISPATCH_QUEUE_SERIAL);
// 1.创建一个并发队列
dispatch_queue_t queue = dispatch_queue_create("concurrent_bing", DISPATCH_QUEUE_CONCURRENT);
// 2.将任务同步加入队列
// dispatch_sync(queue, ^{
// 要执行的代码
// NSLog(@"1----%@", [NSThread currentThread]);
// });
// dispatch_sync(queue, ^{
// 要执行的代码
// NSLog(@"2----%@", [NSThread currentThread]);
// });
// 2.将任务异步加入队列
dispatch_async(queue, ^{
// 要执行的代码
NSLog(@"1----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
// 要执行的代码
NSLog(@"2----%@", [NSThread currentThread]);
});
- GCD其他常用函数
- 延时操作
除了NSObject的performSelector和NSTimer之外还可以使用GCD的方法 dispatch_after(dispatch_time_t when, dispatch_queue_t queue, ^(void)block)
- 延时操作
// 2.0 * NSEC_PER_SEC:两秒之后执行
// dispatch_queue_t queue:在哪个队列里执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 两秒后执行这里的代码
});
- 一次性代码
整个程序运行过程中只调用一次,不是每个对象各调用一次,这里需要注意
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 整个程序运行过程中只调用一次
});
- 快速迭代
同时遍历所有的数据 dispatch_apply(size_t iterations, dispatch_queue_t queue, ^(size_t)block)
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
// 同时遍历10个数据,比如剪切复制粘贴等 是不需要顺序的,可以同时进行操作
NSLog(@"----%zu-----%@", index, [NSThread currentThread]);
});
4.NSOperationQueue
通过NSOperation 和NSOperationQueue配合实现多线程
优点:不需要管理线程的创建和调用,只要注重执行的操作即可
原理:将操作封装到NSOperation对象中,然后将NSOperation对象添加到NSOperationQueue队列中,系统会自动将任务取出放到线程中执行
NSOperation的子类:NSOperation是个抽象类,使用它必须用它的子类: NSInvocationOperation,NSBlockOperation,自定义继承自NSOperation的子类.
NSOperation的代码实现
- (void)InvocationOperation
{
// 在当前线程执行,只有将任务添加到队列中才会开新线程
// NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
// [op start];
}
- (void)BlockOperation
{
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// 在当前线程执行
NSLog(@"1----%@",[NSThread currentThread]);
}];
// 添加额外的任务,在子线程执行
[op addExecutionBlock:^{
NSLog(@"2----%@",[NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"3----%@",[NSThread currentThread]);
}];
[op start];
}
}
- (void)run
{
NSLog(@"%@",[NSThread currentThread]);
}
- NSOperationQueue的代码实现 - 串行
// 默认的都是串行队列
- (void)NSOperationQueue
{
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 创建NSInvocationOperation任务
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
// 只要将任务添加到队列,就新开启线程,并且不需要调用start方法
[queue addOperation:op1];
[queue addOperation:op2];
// 创建NSBlockOperation任务
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3----%@",[NSThread currentThread]);
}];
// 还可再添加额外的任务,在子线程执行
[op3 addExecutionBlock:^{
NSLog(@"3-----1----%@",[NSThread currentThread]);
}];
// 只要添加到队列中,就新开线程
[queue addOperation:op3];
}
- (void)run
{
NSLog(@"%@",[NSThread currentThread]);
}
当异步执行的代码特别多特别长的时候就使用自定义NSOperation,可以将任务封装起来,使用的时候只需要调用自定义类的alloc init方法,然后把任务添加到队列中即可
- NSOperationQueue的代码实现 - 并行
只要设置最大并发数不为1就是并行
- (void)NSOperationQueue
{
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 设置最大并发操作数,如果设置最大并发数为1,就是串行队列
queue.maxConcurrentOperationCount = 3;
// 创建NSBlockOperation任务
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1----%@",[NSThread currentThread]);
}];
// 只要添加到队列中,就新开线程
[queue addOperation:op1];
// 也可以不创建NSOperation 直接创建任务
[queue addOperationWithBlock:^{
NSLog(@"2-------%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"3-------%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"4-------%@", [NSThread currentThread]);
}];
}
- 线程的暂停
需要注意的是,暂停是等当前的任务执行完成之后暂停后面的任务
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.suspended = YES;
- 取消线程任务
任务取消了之后不可恢复,同样的,也是等当前的任务执行完成之后在取消
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue cancelAllOperations];
- 线程间依赖
- (void)addDependency
{
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 创建NSBlockOperation任务
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]);
}];
// 设置依赖,op3依赖于op1和op2,需要op1和op2都执行完才能执行op3
[op3 addDependency:op1];
[op3 addDependency:op2];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
[queue addOperation:op4];
}
需要注意的是,可以跨队列依赖,但是不能互相依赖(A依赖B,B依赖A)
- 线程间通信
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 创建NSBlockOperation任务
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
// 开子线程
NSLog(@"1----%@",[NSThread currentThread]);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// 回到主线程
}];
}];
[queue addOperation:op1];
到这里多线程基本上就介绍完了,是不是很简单呢,多多实践就会发现真的很简单,有什么问题可以私信我哟,如果喜欢,感觉对你有点帮助,可以点个关注哟O(∩_∩)O哈哈