1.线程和进程的定义
1.线程
- 线程是进城的基本执行单元,一个进程的所有任务都在线程中执行。
- 1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程,称为主线程)。
- 程序启动会默认开启一条线程,这条线程被称为主线程或 UI 线程。
2.进程
- 进程是指在系统中正在运行的一个应用程序。
- 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。
3. 进程和线程的关系
- 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
- 资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。
- 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
- 进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程
执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 - 线程是处理器调度的基本单位,但是进程不是。
4.多线程
- 定义:1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务。
- 原理:同一时间,CPU只能处理1条线程,只有1条线程在工作(执行),多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换),如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。
那么如果线程非常非常多,会发生什么情况?
CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源,同时每条线程被调度执行的频次也会会降低(线程的执行效率降低)。因此我们一般只开3-5条线程。 - 优缺点:
优点:1. 能适当提高程序的执行效率。2. 能适当提高资源利用率(CPU、内存利用率)。
缺点:创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,也可以使用-setStackSize:设置,但必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的创建时间,如果开启大量的线程,会降低程序的性能,线程越多,CPU在调度线程上的开销就越大。程序设计更加复杂:比如线程之间的通信、多线程的数据共享等问题。
2.多线程的实现方案
1.pthread的简单使用
/**
pthread_create 创建线程
参数:
1. pthread_t:要创建线程的结构体指针,通常开发的时候,如果遇到 C 语言的结构体,类型后缀 `_t / Ref` 结尾
同时不需要 `*`
2. 线程的属性,nil(空对象 - OC 使用的) / NULL(空地址,0 C 使用的)
3. 线程要执行的`函数地址`
void *: 返回类型,表示指向任意对象的指针,和 OC 中的 id 类似
(*): 函数名
(void *): 参数类型,void *
4. 传递给第三个参数(函数)的`参数`
返回值:C 语言框架中非常常见
int
0 创建线程成功!成功只有一种可能
非 0 创建线程失败的错误码,失败有多种可能!
*/
// 1: pthread
pthread_t threadId = NULL;
//c字符串
char *cString = "HelloCode";
// NSString *ocString = @"Gavin";
//延伸到: OC--C的混编 尤其在智能家居,SDK封装
//抛出一个问题: 在ARC需要这样操作,在MRC不需要
// OC prethread -- 跨平台
// 锁
int result = pthread_create(&threadId, NULL, pthreadTest, cString);
if (result == 0) {
NSLog(@"成功");
} else {
NSLog(@"失败");
}
void *pthreadTest(void *para){
// 接 C 语言的字符串
// NSLog(@"===> %@ %s", [NSThread currentThread], para);
// __bridge 将 C 语言的类型桥接到 OC 的类型
// NSString *name = (__bridge NSString *)(para);
NSLog(@"===>%@", [NSThread currentThread]);
return NULL;
}
2.NSThread的使用
2.1 NSTread有3种创建方式,分别是
- init方式 :
/** 方法一,需要start */
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething1:) object:@"NSThread1"];
// 线程加入线程池等待CPU调度,时间很快,几乎是立刻执行
[thread1 start];
- (void)doSomething1:(NSObject *)object {
// 传递过来的参数
NSLog(@"%@",object);
NSLog(@"doSomething1:%@",[NSThread currentThread]);
}
- detachNewThreadSelector创建好之后自动启动
/** 方法二,创建好之后自动启动 */
[NSThread detachNewThreadSelector:@selector(doSomething2:) toTarget:self withObject:@"NSThread2"];
- (void)doSomething2:(NSObject *)object {
NSLog(@"%@",object);
NSLog(@"doSomething2:%@",[NSThread currentThread]);
}
- performSelectorInBackground创建好之后也是直接启动
/** 方法三,隐式创建,直接启动 */
[self performSelectorInBackground:@selector(doSomething3:) withObject:@"NSThread3"];
- (void)doSomething3:(NSObject *)object {
NSLog(@"%@",object);
NSLog(@"doSomething3:%@",[NSThread currentThread]);
}
- 后面两种方法都不用我们开启线程,相对方便快捷,但是没有办法拿到子线程对象,没有办法对子线程进行更详细的设置,例如线程名字和优先级等。
2.2 NSThread的方法和属性
// 当前线程
[NSThread currentThread];
NSLog(@"%@",[NSThread currentThread]);
// 如果number=1,则表示在主线程,否则是子线程
打印结果:{number = 1, name = main}
//休眠多久
[NSThread sleepForTimeInterval:2];
//休眠到指定时间
[NSThread sleepUntilDate:[NSDate date]];
//退出线程
[NSThread exit];
//判断当前线程是否是多线程
[NSThread isMultiThreaded];
//主线程的对象
NSThread *mainThread = [NSThread mainThread];
//线程是否在执行
thread.isExecuting;
//线程是否被取消
thread.isCancelled;
//线程是否完成
thread.isFinished;
//是否是主线程
thread.isMainThread;
// 获取当前线程优先级
+ (double)threadPriority;
// 设置线程优先级 默认为0.5 取值范围为0.0 - 1.0
// 1.0优先级最高
// 设置优先级
+ (BOOL)setThreadPriority:(double)p;
// 获取指定线程的优先级
- (double)threadPriority NS_AVAILABLE(10_6, 4_0);
- (void)setThreadPriority:(double)p NS_AVAILABLE(10_6, 4_0);
// 设置线程的名字
- (void)setName:(NSString *)n NS_AVAILABLE(10_5, 2_0);
- (NSString *)name NS_AVAILABLE(10_5, 2_0);
// 判断指定的线程是否是 主线程
- (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0);
// 获取主线程
+ (NSThread *)mainThread NS_AVAILABLE(10_5, 2_0);
- (void)cancel NS_AVAILABLE(10_5, 2_0);
// 启动线程
- (void)start NS_AVAILABLE(10_5, 2_0);
// 线程主函数 在线程中执行的函数 都要在-main函数中调用,自定义线程中重写-main方法
- (void)main NS_AVAILABLE(10_5, 2_0);
2.3 NSThread线程的生命周期
启动线程
- (void)start;
// 进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态
阻塞(暂停)线程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 进入阻塞状态
强制停止线程
+ (void)exit;
// 进入死亡状态
2.4线程的安全隐患
- 多线程安全隐患的原因:1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,比如多个线程访问同一个对象、同一个变量、同一个文件。
那么当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。 - 安全隐患解决
我们可以看出,当线程A访问数据并对数据进行操作的时候,数据被加上一把锁,这个时候其他线程都无法访问数据,知道线程A结束返回数据,线程B此时在访问数据并修改,就不会造成数据错乱了。
下面我们来看一下互斥锁的使用:
互斥锁使用格式
@synchronized(锁对象) {
// 需要锁定的代码
}
互斥锁的使用前提:多条线程抢夺同一块资源时.
作用:
- 保证锁内的代码,同一时间,只有一条线程能够执行!
- 互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差!
- 注意:
1.锁定1份代码只用1把锁,用多把锁是无效的。
2.能够加锁的任意 NSObject 对象。
3.锁对象一定要保证所有的线程都能够访问。
4.如果代码中只有一个地方需要加锁,大多都使用 self,这样 可以避免单独再创建一个锁对象。
2.5 NSThread线程之间的通信
定义:在同一个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信,例如我们在子线程完成下载图片后,回到主线程刷新UI显示图片。
体现: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;
举个栗子:
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[NSThread detachNewThreadSelector:@selector(donwLoadImage) toTarget:self withObject:nil];
}
-(void)donwLoadImage
{
// 获取图片url地址 http://www.itunes123.com/uploadfile/2016/0421/20160421014340186.jpg
NSURL *url = [NSURL URLWithString:@"http://www.itunes123.com/uploadfile/2016/0421/20160421014340186.jpg"];
// 下载图片二进制文件
NSData *data = [NSData dataWithContentsOfURL:url];
// 将图片二进制文件转化为image;
UIImage *image = [UIImage imageWithData:data];
// 参数 waitUntilDone 是否等@selector(showImage:) 执行完毕以后再执行下面的操作 YES :等 NO:不等
// 返回主线程显示图片
// [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];
// self.imageView 也可以直接调用这个方法 直接选择 setImage方法,传入参数image即可
// [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
// 返回特定的线程,[NSThread mainThread] 获得主线程
[self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
}
-(void)showImage:(UIImage *)image
{
self.imageView.image = image;
}
@end