多线程编程-001-NSThread
1.什么是进程:
进程: 系统中正在运行的一个应用程序。每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。
2.什么是线程:
一个进程要想执行任务,必须得有线程(每个进程至少要有一个线程), 一个进程的所有任务都在线程中执行。
阻塞线程的任务(网络加载)一般放在子线程,线程不能随便开,也会损耗性能
3.线程的串行:
一个线程中任务的执行是串行的:那么只能一个一个按顺序执行这些任务。也就是说,在同一时间内,一个线程只能执行一个任务。
4.多线程:CPU不同调度
一个进程可以开启多条线程,每条线程可以并行执行不同的任务。例如:进程——》车间,线程——》车间工人
【多线程技术可以提高程序的执行效率,提高资源利用率】 比如可以同时开启三个线程分别下载三个文件(文件A、B、C)【并行、同时进行】
5.多线程的原理:同一时间,CPU只能从事处理1条线程,只有1条线程在工作。
多线程并发(同时)执行,其实就是CPU快速地在多条线程之间调度(切换)
如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。
如果线程非常多,会发生什么情况?
线程开销非常大,CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源,每条线程被调度的频次会降低(线程的执行效率降低)。
6.多线程的优缺点:
优点:能适当提高程序的执行效率,提高资源利用率。
缺点:A.线程是有开销的,创建线程大约需要90毫秒的创建时间。
B.如果开启大量线程,会降低程序的性能。
C.线程越多,CPU调度开销越大;
D.程序设计更加复杂(线程通信、数据共享)
7.多线程在iOS开发中的应用:
/* 主线程:一个iOS程序运行后,默认会开启一条线程,称为“主线程”或“UI线程”。线程的主要作用:显示/刷新UI;处理UI事件(比如点击、滚动、拖拽事件等。) 使用注意:别将比较耗时的操作放到主线程中;耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验。 比如说:一个操作需要耗时10秒,但是用户在第5秒时点击了按钮就会很卡 5秒后才能执行。 */
耗时操作的执行:将耗时操作放在子线程,当用户第五秒点击按钮那一刻就有反应,能同时处理耗时操作和UI控件的事件。
iOS中多线程的实现方案:
/* pthread 一套通用的多线程API;适用于Unix\Linux\Windows等系统;跨平台\可移植;使用难度大 C语言 NSThread 使用更加面向对象 ;简单易用,可直接操作线程对象 OC语言 手动管理 GCD 旨在替代NSThread等线程技术;充分利用设备的多核 C语言 自动管理 NSOperation 基于GCD(底层是GCD)比GCD多了一些更简单实用的功能,使用更加面向对象 OC语言 自动管理 */
①NSThread创建和启动线程
//一个NSThread对象就代表一条线程 //创建、启动线程: NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; [thread start];// 线程一启动,就会在线程thread中执行self的run方法
主线程相关用法:
+ (NSThread *)mainThread; // 获得主线程 - (BOOL)isMainThread; // 是否为主线程 + (BOOL)isMainThread; // 是否为主线程 //number 1 主线程 ; != 1 其他线程 NSLog(@"%d == %@",i,[NSThread currentThread]); //获得当前线程 NSThread *current = [NSThread currentThread]; //线程的名字 - (void)setName:(NSString *)n; - (NSString *)name; //项目开发中可以根据这个来查找崩溃原因 //线程的优先级 //current.threadPriority :优先级别 : 0.0 - 1.0; 默认0.5 只是保证CPU调度的可能性 数值越大,优先级越高,1.0最高. //多线程目的:将耗时操作放在后台执行;建议:不要在开发过程中,修改优先级
其他创建线程的方式
//①创建线程后自动启动线程 [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; //②隐式创建并启动线程 [self performSelectorInBackground:@selector(run) withObject:nil];//所有继承NSObject的类都可以调用 上述2种创建线程方式的优缺点 优点:简单快捷 缺点:无法对线程进行更详细的设置
控制线程的状态:NSThread 线程状态:阻塞,就绪,死亡
//①启动线程 - (void)start; //将线程加入到线程池里面-》CPU调度线程 运行状态由CPU控制 //②进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态 阻塞(暂停)线程 + (void)sleepUntilDate:(NSDate *)date; + (void)sleepForTimeInterval:(NSTimeInterval)ti;// ③进入阻塞状态 时间到了从阻塞到就绪状态 强制停止线程 + (void)exit;//④进入死亡状态 从线程池中拿出来,销毁 注意:一旦线程停止(死亡)了,就不能再次开启任务
②多线程的安全隐患:资源共享
一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源。(很容易引发数据错乱和数据安全问题)
安全隐患解决方法:互斥锁@synchronized(锁对象🔐) 互斥锁:保证锁内的代码,同一时间只有1个线程执行
Thread A : 17 + 1 = 18;
Thread B : 17 + 1 = 18;
当Thread A 执行完+1操作后,就把线程锁住,ThreadB无法访问,等到Thread A 用完之后解锁,此时ThreadB才能访问。
互斥锁使用格式:
@synchronized(锁对象) { // 需要锁定的代码 } 注意:锁定1份代码只用1把锁,用多把锁是无效的
//互斥锁的优缺点: //优点:能有效防止因多线程抢夺资源造成的数据安全问题 // 缺点:需要消耗大量的CPU资源 互斥锁的使用前提:多条线程抢夺同一块资源 //线程同步 //意思是:多条线程在同一条线上执行(按顺序地执行任务)互斥锁,就是使用了线程同步技术 @property (atomic, assign) int tickets;//票数 @property (nonatomic, strong) NSObject *lockObj ; self.lockObj = [NSObject new]; self.tickets = 20; NSThread * t1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil]; t1.name = @"售票员A"; [t1 start]; NSThread * t2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil]; t2.name = @"售票员B"; [t2 start]; - (void)saleTickets { while (YES) { [NSThread sleepForTimeInterval:1.0]; /* 互斥锁:保证锁内的代码,同一时间只有1个线程执行; 互斥锁的范围:尽量要小,范围大,效率就会差 只在核心代码里面加锁 */ // NSObject *lock = [NSObject new]; 不能用局部变量 @synchronized (self.lockObj) { if (self.tickets > 0) { self.tickets--; NSLog(@"剩下%d张票 %@",self.tickets,[NSThread currentThread]); }else { NSLog(@"卖完了,回家吧!"); break; } } } }
③原子性和非原子性
互斥锁:如果发现其他线程正在执行锁定的代码,线程就会进入休眠状态,等待线程完成,就唤醒
自旋锁:如果发现其他线程正在执行锁定的代码,线程就会死循环模式,等待线程完成,就执行锁定代码;
//原子性:1.保证属性的线程间安全(自旋锁) //2.消耗性能:当下载文件时,如果没下载完成时,会把文件放在一个临时文件,当完全下载完成时才会加入后缀名。没下载成功不能打开 如果不写atomic,多线程写入属性时,保证同一时间只有一个线程能够写入操作;。 //3.线程安全:多个线程操作数据,任然保持数据正确。主线程不考虑线程安全问题 //atomic:线程安全,需要消耗大量的资源 //nonatomic:非线程安全,适合内存小的移动设备 所有属性都声明为nonatomic 尽量避免多线程抢夺同一块资源 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力
@interface ViewController () @property(atomic,strong)NSObject *myAtomic; @end /* 原子属性: 如果不写atomic,多线程写入属性时,保证同一时间只有一个线程能够写入操作; 单(线程)写,多(线程)读技术。 线程安全:多个线程操作数据,仍然能保证数据正确 实际上,原子属性内部会有一把锁;自旋锁 共同点: 同一时间,只有线程访问; 不同点: 互斥锁:如果发现其他线程正在执行锁定的代码,线程就会进入休眠状态,等待线程完成,就唤醒 自旋锁:如果发现其他线程正在执行锁定的代码,线程就会死循环模式,等待线程完成,就执行锁定代码; */ @implementation ViewController //@synthesize 属性自动合成 创造一个带下划线前缀的实例变量名,同时使用这个属性生成getter 和 setter 方法。 @synthesize myAtomic = _myAomic; -(NSObject *)myAtomic { return _myAomic; } -(void)setMyAtomic:(NSObject *)myAtomic { @synchronized (self) { _myAomic = myAtomic; } }
④线程间通信
线程间通信:在一个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信。
//线程间通信的体现 //1个线程传递数据给另1个线程; 在1个线程中执行完特定任务后,转到另1个线程继续执行任务
线程间通信常用方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait; - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait; //①开启子线程: NSThread * t1 = [[NSThread alloc] initWithTarget:self selector:@selector(downImage) object:nil];[t1 start]; //②回到主线程: [self performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];//YES 相当于给Timer加了一个Runloop