iOS开发之多线程技术
本篇争取一篇讲清讲透,依然将通过四大方面清晰的对iOS开发中多线程的用法进行详尽的讲解:
一、什么是多线程
1)多线程执行原理
2)线程与进程
3)多线程的优缺点
二、我们为什么要用多线程编程技术
三、如何使用多线程技术
1)pthread技术
2)NSThread技术
2.1)线程属性
2.2)资源共享(抢夺)
3)GCD技术
4) NSOperation技术
四、线程的生命周期(线程状态)
一、什么是多线程
多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理(Chip-level multithreading)或同时多线程(Simultaneous multithreading)处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理(Multithreading)”。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程(台湾译作“执行绪”),进而提升整体处理性能。
1) 多线程执行原理
a. (单核CPU)同一时间,cpu只能处理1个线程,只有1个线程在执行
b. 多线程同时执行:是CPU快速的在多个线程之间的切换
c. cpu调度线程的时间足够快,就造成了多线程的"同时"执行
d. 如果线程数非常多,cpu会在n个线程之间切换,消耗大量的cpu资源
i. 每个线程被调度的次数会降低,线程的执行效率降低
2)线程与进程
每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。进程也可能是整个程序或者是部分程序的动态执行。线程是一组执行的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务。通常由操作系统负责多个线程的调度和执行。
线程是程序中一个单一的顺序控制流程,在单个程序中同时运行多个线程完成不同的工作,称为多线程。
线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文,多线程主要是为了节约CPU时间,发挥利用,根据具体情况而定。线程的运行中需要使用计算机的内存资源和CPU。
3)多线程的优缺点
优点:
1、使用线程可以把占据时间长的程序中的任务放到后台去处理。
2、用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。
3、程序的运行速度可能加快。
4、在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下可以释放一些珍贵的资源如内存占用等等。
5、线程上的任务执行完成后,线程会自动销毁。
缺点:
1、线程越多,cpu在调用线程上的开销就越大,如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换。
2、开启线程需要占用一定的内存空间(默认情况下,每一个线程都占512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能,更多的线程需要更多的内存空间。
3、程序设计更加复杂,比如线程间的通信、多线程的数据共享,可能会给程序带来更多的BUG,因此要小心使用。
4、线程的中止需要考虑其对程序运行的影响。
5、通常块模型数据是在多个线程间共享的,需要一个合适的锁系统替换掉数据共享。
注意:iOS 8.0后主线程的默认堆栈大小也是 512K,官方文档标注错误。
二、我们为什么要用多线程编程技术
在大多数研究领域内是要求线程调度程序要能够快速选择其中一个已就绪线程去运行,而不是一个一个运行而降低效率。所以要让调度程序去分辨线程的优先级是很重要的。在移动开发过程中,一切均已用户体验作为首要任务,这时多线程的重要性不言而喻。
一个程序运行后,默认会开启1个线程,称为“主线程”或“UI线程”,主线程一般用来刷新UI界面,处理UI事件(比如:点击、滚动、拖拽等事件)
主线程使用注意
别将耗时的操作放到主线程中
耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种卡的坏体验
三、如何使用多线程技术
ios中多线程实现的多种技术方案:
POSIX 表示可移植操作系统接口(Portable Operating System Interface )-----pthread
1)pthread技术:
pthread 是 POSIX 多线程开发框架,由于是跨平台的 C 语言框架,在苹果的头文件中并没有详细的注释要查阅 pthread 有关资料,可以访问 http://baike.baidu.com
// 创建线程,并且在线程中执行 demo 函数 - (void)pthreadDemo { /** 参数: 1> 指向线程标识符的指针,C 语言中类型的结尾通常 _t/Ref,而且不需要使用 * 2> 用来设置线程属性 3> 新建立的线程执行代码的函数 4> 运行函数的参数 返回值: - 若线程创建成功,则返回0 - 若线程创建失败,则返回出错编号 在混合开发时,如果在 C 和 OC 之间传递数据,需要使用 __bridge 进行桥接,桥接的目的就是为了告诉编译器如何管理内存 */ pthread_t threadId = NULL; NSString *str = @"Hello Pthread"; int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(str)); result ? NSLog(@"为其他任何值时代表开辟子线程失败") : NSLog(@"当result为0时表示开辟子线程成功"); } // 后台线程调用函数 void *demo(void *params) { NSString *str = (__bridge NSString *)(params); NSLog(@"%@ - %@", [NSThread currentThread], str); return NULL; }
2)NSThread技术:
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"主线程%@", [NSThread currentThread]); /** 多个线程之间的执行顺序是随机的 */ // 方式1:通过NSThread的对象方法 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"方式1"]; [thread start]; // 方式2:没有thread字眼,隐式创建并启动线程,所有 NSObject 都可以使用此方法,在其他线程执行方法 [self performSelectorInBackground:@selector(demo:) withObject:@"方式2"]; // 方式3:detachNewThreadSelector 类方法不需要启动,会自动创建线程并执行 @selector 方法 [NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"方式3"]; } - (void)demo:(NSString *)str { NSLog(@"%@, %@", str, [NSThread currentThread]); }
2.1)线程属性
1. name - 线程名称
2. threadPriority - 线程优先级
取值范围从 0~1.0
1.0表示优先级最高
0.0表示优先级最低
默认优先级是0.5
3. stackSize - 栈区大小
4. isMainThread - 是否主线程
2.2)资源共享(抢夺)
1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,比如多个线程访问同一个对象、同一个变量、同一个文件当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。如购买火车票问题:
解决方案:
#pragma mark #pragma mark - 模拟卖票系统 - (void)sellTicket { _count = 50; NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(ticket) object:nil]; thread1.name = @"t1"; [thread1 start]; NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(ticket) object:nil]; thread2.name = @"t2"; [thread2 start]; } - (void)ticket { while (YES) { // 被加锁的对象 @synchronized(self) { if (_count > 0) { _count = self.count - 1; NSLog(@"剩余票数%ld ——%@", _count, [NSThread currentThread]); } else { NSLog(@"票卖没了倒霉蛋"); break; } } } }
互斥锁
:如果发现有其他线程正在执行锁定的代码,线程会进入休眠状态
,等待其他线程执行完毕,打开锁之后,线程会被唤醒
自旋锁
:如果发现有其他线程正在执行锁定的代码,线程会以死循环
的方式,一直等待锁定代码执行完成
线程安全
多个线程进行读写操作时,仍然能够得到正确结果,被称为线程安全,要实现线程安全,必须要用到锁、
为了得到更佳的用户体验,UIKit 不是线程安全的,所以更新 UI 的操作都必须主线程上执行!因此,主线程又被称为UI 线程。
3)GCD技术
为保证篇幅不过与杂糅,请见“IOS开发之多线程技术——GCD篇”
4) NSOperation技术
为保证篇幅不过与杂糅,请见“IOS开发之多线程技术——NSOperation篇”
四、线程的生命周期(线程状态)
新建
实例化线程对象
就绪
- (void)start;
向线程对象发送 start 消息,线程对象被加入 可调度线程池 等待 CPU 调度
detachNewThreadSelector 方法和 performSelectorInBackground 方法会直接实例化一个线程对象并加入 可调度线程池
运行
CPU 负责调度可调度线程池中线程的执行
线程执行完成之前(死亡之前),状态可能会在就绪和运行之间来回切换
就绪和运行之间的状态变化由 CPU 负责,程序员不能干预
阻塞
当满足某个预定条件时,可以使用休眠或锁阻塞线程执行
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
@synchronized(self):互斥锁
死亡:+ (void)exit
正常死亡
线程执行完毕
非正常死亡
当满足某个条件后,在线程内部自己中止执行(自杀),[NSThread exit];
当满足某个条件后,在主线程给其它线程打个死亡标记(下圣旨),让子线程自行了断.(被逼着死亡)
注意:在终止线程之前,应该注意释放之前分配的对象!