在这里给大家介绍一些多线程的知识,以及应用,希望能给一些需要的朋友学习学习,如果有错误的地方,请帮忙指出,非常感谢。
那么先介绍多线程前,先说一下什么是线程,什么是进程?
进程:{ 1.正在运行的一个应用程序就叫进程。
2.每个进程之间都是相互独立的,每个进程都运行在自己独立的专用的且受保护的内存空间内。
}
线程:{ 1.线程是进程的基本执行单元。
2.每一个进程都默认开启一条线程,我们称之为主线程。(一个进程至少有一条线程)
}
多线程:多线程就是一个进程可以开辟多线线程(子线程),同时执行不同的任务。(多线程可以解决线程阻塞的问题, 并且提高了程序的运行效率)
多线程的执行原理:{ 1.(单核CUP),同一时间,CUP只能处理一条线程, 只有一条线程在执行。
2.多线程的同时执行:是CUP在多个线程中快速切换。
3.CUP调度线程的时间够快,就产生了多线程的"同时"执行。
}
多线程的优缺点:优点:{ 1. 提高了程序的执行效率。
2. 提高了CUP和内存的资源利用率。
3.当子线程执行完任务后,会自动销毁。
}
缺点:{ 1.每条线程都占用512KB的内存空间。
2.开辟大量的线程,增加了CUP在调度上的开销。
3.开辟大量的线程,消耗了大量内存,降低的程序的性能。
4.增加的程序的设计难度,比如多线程访问共享资源数据安全问题,以及线程间的通讯。
}
主线程:{主线程以一个程序运行后,默认开辟的一条线程,我们称之为主线程,也叫UI线程。
主线程一般用来刷新UI界面,处理UI事件。
}
注意:不要将耗时操作放在主线程上执行,耗时操作会卡住主线程,造成UI界面卡顿,影响界面流畅度。
什么是耗时操作? I/O操作就是耗时操作。
iOS中多线程的设计方案:
1. pthread的使用
导入头文件 : #import <pthread.h>
pthread_t pthread;
第一个参数:&pthread 线程编号地址
第二个参数:线程的属性
第三个参数:要执行的方法
第四个参数:要执行的方法的参数
如果创建子线程成功,将会返回0,其他则失败。
int result = pthread_create(&pthread, NULL, demo, NULL);
2.NSThread的使用
方式1:
[NSThread * thread = [NSThread alloc]initWithTarget:self selector: @selector(demo) object:nil] ;
[thread start];
方式2:
[NSThread detachNewThreadSelector:@selector: (demo) toTarget: self withObject :nil];
方式3:
[self performSelectorInBackground:@selector:(demo) withObject:nil];
线程的状态:新建,就绪,运行,阻塞,死亡(正常死亡,非正常死亡)。
控制线程的状态:
1.启动线程:
-(void)start; 让线程进入就绪状态,线程执行完毕后(若一直处于空闲)进入死亡状态。
2.暂停(阻塞)线程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti; 让线程进入阻塞状态,时间到时进入就绪状态。
3.停止线程
+ (void)exit;
线程进入死亡状态 , 注意一旦线程死亡,就不会再开启任务。
线程的属性:{ 线程的名称:设置线程的名称可以当线程执行方法内部出现异常的时候记录异常和当前线程。(根据线程的name属性,设置)
线程的优先级:内核调度算法在决定该运行哪个线程时,会把线程的优先级作为考量因素,较高优先级的线程会比较低优先级的线程具有更多的运行机会。较高优先级不保证你的线 程具体执行的时间,只是相比较低优先级的线程,它更有可能被调度器选择执行而已。(thread setThreadPriority:<#(double)#>设置优先级)
多线程访问共享资源的问题:
共享资源:一块资源被多个线程共享,也就是多个线程可以访问这个资源。(一个文件, 一个变量,一个对象)
当多个线程访问同一块资源时,很容易造成数据错乱和数据安全问题。
比如: 同时访问同一个变量,导致如下问题。
为了解决多线程访问共享资源数据安全为题,于是引入了互斥锁。
互斥锁使用:
@synchronized(锁对象) { // 需要锁定的代码 }
互斥锁
- 能有效防止因多线程抢夺资源造成的数据安全问题
相关专业术语:线程同步
- 线程同步的意思是:多条线程按顺序地执行任务
- 互斥锁,就是使用了线程同步技术
互斥锁原理
- 每一个对象(NSObject)内部都有一个锁(变量),当有线程要进入synchronized到代码块中会先检查对象的锁是打开还是关闭状态,默认锁是打开状态(1),如果是线程执行到代码块内部 会先上锁(0)。如果锁被关闭,再有线程要执行代码块就先等待,直到锁打开才可以进入。
互斥锁执行的五个步骤:
线程执行到synchronized
i. 检查锁状态 如果是开锁状态(1)转到ii 如果上锁(0)转到v
ii. 上锁(0)
iii.执行代码块
iv. 执行完毕 开锁(1)
v.线程等待(就绪状态)
加锁后程序执行的效率比不加锁的时候要低,因为要线程要等待锁,但是锁保证了多个线程同时操作全局变量的安全性
属性中的修饰符
- nonatomic非原子属性
- atomic 原子属性(线程安全),针对多线程设计的,默认值
保证同一时间只有一个线程能够写入(但是同一个时间多个线程都可以取值)
atomic 本身就有一把锁(自旋锁)
单写多读:单个线程写入,多个线程可以读取
- nonatomic和atomic对比
- atomic:线程安全,需要消耗大量的资源
- nonatomic:非线程安全,适合内存小的移动设备
- iOS开发的建议
- 所有属性都声明为nonatomic
- 尽量避免多线程抢夺同一块资源
- 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力
- 互斥锁
- 如果发现其他线程正在执行锁定代码,线程会进入休眠(就绪状态),等其它线程时间片到打开锁后,线程会被唤醒(执行)
- 自旋锁("轮询")
- 如果发现有其它线程正在锁定代码,线程会用死循环的方式,一直等待锁定的代码执行完成 自旋锁更适合执行不耗时的代码
- 线程安全
- 线程同时操作是不安全的,多个线程同时操作一个全局变量
- 线程安全:在多个线程进行读写操作时,仍然能够保证数据的正确
- 主线程(UI线程)
- 几乎所有UIKit????提供的类都是线程不安全的,所有更新UI的操作都在主线程上执行
GCD:
- 什么是GCD
- 全称是Grand Central Dispatch
- 纯C语言,提供了非常多强大的函数
- GCD的优势
- GCD是苹果公司为多核的并行运算提出的解决方案
- GCD会自动利用更多的CPU内核(比如双核、四核)
- GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
- GCD的两个核心
- 任务:执行什么操作
- 队列:用来存放任务
- GCD使用的两个步骤
- 创建任务:确定要做的事情
- 将任务添加到队列中
- GCD会自动将队列中的任务取出,放到对应的线程中执行
- 任务的取出遵循队列的FIFO原则:先进先出,后进后出
执行任务的方式(同步, 异步)
第一个参数:队列
都二个参数:任务,操作
dispatch_sync(<#dispatch_queue_t queue#>, <#^(void)block#>)
dispatch_async(<#dispatch_queue_t queue#>, <#^(void)block#>)
队列的类型(串行队列 , 并发队列 )
并发队列: 可以让多个任务并发执行, (自动开启了多个线程)
并发功能只在异步函数中有效
串行队列:让任务一个接一个完成(一个任务完毕后,再执行下一个任务)
总结:
同步异步决定是否开启新的线程:{ 1, 同步:在当前线程中执行任务,不具备开辟新的线程的能力
2. 异步:在新的线程中执行任务,具备有开辟新线程的能力 }
并发和串行决定执行任务的方式 : { 1, 并发:多个任务同时执行
2.一个任务执行完毕后,再执行下个任务}
串行队列,同步执行
不开线程,同步执行(在当前线程执行)
#define DISPATCH_QUEUE_SERIAL NULL
//串行队列
//dispatch_queue_t q = dispatch_queue_create("test", NULL);
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10; i++) {
//同步执行
dispatch_sync(q, ^{
NSLog(@"%@ -- %d",[NSThread currentThread],i);
});
}
串行队列,异步执行
开一个线程,顺序执行
//只有一个线程,因为是串行队列,只有一个线程就可以按顺序执行队列中的所有任务
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10; i++) {
//异步执行
dispatch_async(q, ^{
NSLog(@"%@ -- %d",[NSThread currentThread],i);
});
}
并行队列,异步执行
开多个线程,异步执行
开多个线程,异步执行,每次开启多少个线程是不固定的(线程数,不由我们控制),线程数是由gcd来决定的
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
//异步执行
dispatch_async(q, ^{
NSLog(@"%@ -- %d",[NSThread currentThread],i);
});
}
并行队列,同步执行
不开线程,顺序执行
主队列:
主队列,异步执行
- 不开线程,同步执行
- 主队列特点:如果主线程正在执行代码暂时不调度任务,等主线程执行结束后在执行任务
- 主队列又叫 全局串行队列
主队列,同步执行
- 程序执行不出来(死锁)
- 死锁的原因,当程序执行到下面这段代码的时候
- 主队列:如果主线程正在执行代码,就不调度任务
- 同步执行:如果第一个任务没有执行,就继续等待第一个任务执行完成,再执行下一个任务此时互相等待,程序无法往下执行(死锁)
//创建队列
dispatch_queue_t mainQueue = dispatch_get_main_queue(); //2.任务 dispatch_block_t task1 = ^ { [NSThread sleepForTimeInterval:1.0]; NSLog(@"task1 %@",[NSThread currentThread]); }; dispatch_block_t task2 = ^ { NSLog(@"task2 %@",[NSThread currentThread]); }; //同步 dispatch_sync(mainQueue, task1); dispatch_sync(mainQueue, task2);
如何解决死锁?(将主队列操作放入子线程中执行)
//异步 dispatch_async(dispatch_get_global_queue(0, 0), ^{ //主队列 主队列执行任务只有当主线程空闲的时候才能够执行 dispatch_queue_t mainQueue = dispatch_get_main_queue(); //2.任务 dispatch_block_t task1 = ^ { [NSThread sleepForTimeInterval:1.0]; NSLog(@"task1 %@",[NSThread currentThread]); }; dispatch_block_t task2 = ^ { NSLog(@"task2 %@",[NSThread currentThread]); }; //同步 dispatch_sync(mainQueue, task1); dispatch_sync(mainQueue, task2); });
全局队列: 全局队列本质就是并发队列.(通常使用全局队列, 容易获取)
全局队列和并发队列的区别: {1.全部队列就只有一个, 并发队列可以有多个. 在MRC下,并发队列creat,就需要release , 而全局队列不需要release.
2. 并发队列有名称, 可以跟踪错误,全局队列没有
}
各种队列的执行效果:
GCD的其他用法:
延迟操作(异步)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
<#code to be executed after a specified delay#>
});
一次性执行: (业内称一次性执行是为单例而生, 并且一次性执行是线程安全的)
//如果onceToken为0的话就可以执行代码块,如果为-1则直接跳过代码块
static dispatch_once_t onceToken;
NSLog(@"%ld",onceToken);
dispatch_once(&onceToken, ^{
});
NSLog(@"end%ld",onceToken);
调度组:应用场景:当多个任务同时无序执行,并且当这些任务完成后,提示用户执行完成.
//调度组 dispatch_group_t group = dispatch_group_create(); /* 参数1 :调度组 参数2:队列 参数3:任务 */ // 第一首歌曲下载 dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ [NSThread sleepForTimeInterval:1.0]; NSLog(@"第一首歌曲下载"); }); // 第二首歌曲下载 dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ [NSThread sleepForTimeInterval:1.0]; NSLog(@"第二首歌曲下载"); }); // 第三首歌曲下载 dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ [NSThread sleepForTimeInterval:1.0]; NSLog(@"第三首歌曲下载"); }); //如果组里面的任务执行完毕,通知执行下面的代码 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"%@",[NSThread currentThread]); NSLog(@"歌曲下载完成"); });
NSOperation:
- NSOperation的作用
- 是OC语言中基于GCD的面向对象的封装
- 使用起来比GCD更加简单(面向对象)
- 提供了一些用GCD不好实现的功能
- 苹果推荐使用,使用NSOperation不用关心线程以及线程的生命周期
- NSOperation是一个抽象类
- 不能直接使用(方法没有实现)
- 约束子类都具有共同的属性和方法
- NSOperation的子类:NSInvocationOperation , NSBlockOperation , 自定义operation
- NSOperationQueue队列
使用步骤:与GCD类似
- NSOperation和NSOperationQueue实现多线程的具体步骤
- 先将需要执行的操作封装到一个NSOperation对象中
- 然后将NSOperation对象添加到NSOperationQueue队列中
- 系统会自动将NSOperationQueue中的NSOperation取出来
- 将取出的NSOperation封装的操作放到一条新线程中执行
NSInvocationOperation:
创建NSInvocationOperation对象
- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
- 调用start方法开始执行操作
- (void)start;
一旦执行操作,就会调用target的sel方法
注意:
默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作
只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作。
NSBlockOperation:
- 创建NSBlockOperation对象
+ (id)blockOperationWithBlock:(void (^)(void))block;
- 通过addExecutionBlock:方法添加更多的操作
- (void)addExecutionBlock:(void (^)(void))block;
注意:只要NSBlockOperation封装的操作数 > 1,就会异步执行操作
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"%@",[NSThread currentThread]); }]; //操作添加额外的任务 [op addExecutionBlock:^{ NSLog(@"Execution %@",[NSThread currentThread]); }]; [op start]; //如果NSBlockOperation的操作数>1 开启新的线程 2016-03-06 18:53:58.719 15-NSBlockOperation[9384:1039444] <NSThread: 0x7fdb60e02e90>{number = 1, name = main} 2016-03-06 18:53:58.720 15-NSBlockOperation[9384:1039488] Execution <NSThread: 0x7fdb60c0f620>{number = 2, name = (null)}
NSOperationQueue:
- NSOperationQueue的作用
NSOperation可以调用start方法来执行任务,但默认是同步执行的
如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作
- 添加操作到NSOperationQueue中
- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;
线程间通信:
主队列: 添加到主队列的操作,最终都执行在主线程上.
[NSOperationQueue mainQueue];
当前队列: 获取当前队列 [SNOperationQueue currentQueue];
NSOperation 和GCD 的差异:
- GCD
- GCD是iOS4.0 推出的,主要针对多核cpu做了优化,是C语言的技术
- GCD是将任务(block)添加到队列(串行/并行/全局/主队列),并且以同步/异步的方式执行任务的函数
- GCD提供了一些NSOperation不具备的功能
- 一次性执行
- 延迟执行
- 调度组
- NSOperation (NSOperation基于GCD的)
- NSOperation是iOS2.0推出的,iOS4之后重写了NSOperation
- NSOperation将操作(异步的任务)添加到队列(并发队列),就会执行指定操作的函数
- NSOperation里提供的方便的操作
- 最大并发数
- 队列的暂定/继续
- 取消所有的操作
- 指定操作之间的依赖关系(GCD可以用同步实现)
- 什么是并发数(同时执行的任务数)
- 最大并发数的相关方法
- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
- 取消队列的所有操作
- (void)cancelAllOperations;
提示:也可以调用NSOperation的- (void)cancel方法取消单个操作 (取消还没执行的操作, 正在执行的操作继续执行,直到完成 )
- 暂停和恢复队列
- (void)setSuspended:(BOOL)b; // YES代表暂停队列,NO代表恢复队列 (正在执行的操作完毕后, 暂停)
- (BOOL)isSuspended;
操作的优先级: 操作的优先级并不能控制操作的执行时间,只是相对与优先级低的操作, 线程调用的频率高罢了.
- 设置NSOperation在queue中的优先级,可以改变操作的执行优先级
- (NSOperationQueuePriority)queuePriority;
- (void)setQueuePriority:(NSOperationQueuePriority)p;
- iOS8以后推荐使用服务质量 qualityOfService
监听操作:
- 可以监听一个操作的执行完毕 ( 给一个操作添加监听, 当这个操作执行完毕后, 操作执行对应监听里的操作)
- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;
操作依赖:
- NSOperation之间可以设置依赖来保证执行顺序
比如一定要让操作A执行完后,才能执行操作B,可以这么写
[operationB addDependency:operationA]; // 操作B依赖于操作A
可以在不同queue的NSOperation之间创建依赖关系
注意:操作之间,不能相互依赖.
这些就是多线程的基础知识, 若有问题或不全, 请大神指出, 感谢.