一、线程与进程
线程的定义:
- 线程是进行的基本执行单元, 一个进程的所有任务都在线程中执行。
- 进行想要执行任务,必须要有线程,进程至少要有一条线程。
- 程序启动会默认开启一条线程, 这条线程被称为主线程或UI线程。
进程的定义:
- 进程是指在系统中正在运行的一个程序。
- 每个进程之间都是独立的, 每个进程均运行在其专用的且受保护的内存。
线程和进程的关系和区别:
- 地址空间: 同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
- 资源拥有: 同一进程内的线程共享本进程的资源, 如内存、I/O、cpu等, 但是进程之间的资源是独立的。
- 一个进程崩溃后,在保护模式下不会对其他进程产生影响。但是一个线程崩溃,整个进程都死掉。所以多进程哟啊比多线程稳健。进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样,如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。
- 执行过程:每个独立的进程都有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
- 线程是处理器的基本调度单位,但是进程不是。
二、线程的意义
先来看一段代码:
- (void)viewDidLoad {
[super viewDidLoad];
[self threadTest];
}
- (void)threadTest{
for (NSInteger i = 0; i < 10000000; i++) {
NSLog(@"%d", i);
}
}
当我们在开发中要进行某个数据的遍历的时候,把它放到主线程去做事,运行起来后肯定就会发现主界面无法进行任何操作。 这是因为这段代码的运行消耗时间较长,堵塞住了主线程的向下运行。 为了解决这种问题, 所以就需要使用多线程这个概念。
我们通过NSThread、GDC、NSOperation等来进行多线程的实现。 例如:
//1: NSThread
[NSThread detachNewThreadSelector:@selector(threadTest) toTarget:self withObject:nil];
// 2: GCD
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self threadTest];
});
//3: NSOperation
[[[NSOperationQueue alloc] init] addOperationWithBlock:^{
[self threadTest];
}];
多线程的优点就在于:
- 能适当提高程序的执行效率
- 能适当提高资源的利用率(CPU、内存)
- 线程上的任务执行完成后能自动销毁
同样也会有一些缺点:
- 开启线程需要占用一定的内存空间(默认情况下,每一个线程占用512KB)
- 如果开启大量线程,会占用大量的内存空间,降低程序的性能
- 线程越多,CPU在调用线程上的开销就越大
- 程序设计更加复杂,比如线程之间的通信、多线程的数据共享等。
三、多线程的原理
多线程对于我们来说貌似就是多条线程处理任务。多线程这个命名主要来源于单核CPU, 同一时间内CPU只能处理一个线程,但是多个线程怎么处理呢? 这就有一个叫做 时间片 的概念, CPU在多个线程中来回切换执行任务。这个时间片小到我们根本就察觉不到,所以能给我们造成一种多个线程都在同时运行的错觉。
真正意义上的多线程来源于多核CPU, 多核CPU主要就是为了来加快执行的效率
3.1、多线程的生命周期
例如NSThread,当我们去创建一个线程之后, 我们下一步就是通过start来启动它。当我执行了start后, 并不代表着这个线程开始执行,而是这个线程进入一个Runnable就绪状态,它告诉计算机这个线程已经准备好了可以运行了。此时就会等待CPU的资源调度来执行这个线程。当程序执行完毕就会强制退出,销毁线程。
当线程开始运行,有某种堵塞导致线程无法进行的情况(例如加锁), 导致线程会重新进入Runnable就绪状态, 等待调起。
3.2、线程池
当系统需要调用线程的时候,它就会来到一个地方,就是线程池。
首先会判断一下线程池的状态,也就是防止线程池装满了。线程池没有满就去判断工作队列是否满,工作队列满了就去看看有没有线程闲置状态, 都是工作状态就会进入饱和策略。
饱和策略分为四种策略:
- AbortPolicy 直接抛出RejectedExecutionExeception异常来阻止系统正常运行
- CallerRunsPolicy 将任务回退到调用者
- DisOldestPolicy 丢掉等待最久的任务
- DisCardPolicy 直接丢弃任务
这四种策略均实现的RejectedExecutionHandler接口。
四、线程安全
先来看一份代码:
// 1. 开启一条售票线程
NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
t1.name = @"售票 A";
[t1 start];
// 2. 再开启一条售票线程
NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
t2.name = @"售票 B";
[t2 start];
加入有两个售票窗口同时售票,那么就会产生一个问题,总票数量是不变的,很有可能当A正在出售的时候B已经售出最后一张票。就会导致A访问到一个错误的剩余票数。
为了避免这种问题的产生,就需要引入一个锁。 对于实现方法:
- (void)saleTickets {
while(YES) {
@synchroized(self) {
//售票操作
}
}
}
这样我们就可以简单的为共享的资源加上一把锁,当A在进行数据操作的时候,B正好过来执行这个操作,就会被锁拒之门外,只有A执行完毕后,B才能进行数据的操作。
后续会详情分析一下锁。
五、线程通讯
我们一般的线程通讯就是线程一向线程二传值发送消息等。这里演示 一下基于端的线程通讯,就是NSPort。
@interface PortViewController ()<NSMachPortDelegate>
@property (nonatomic, strong) NSPort *myPort;
@property (nonatomic, strong) Person *person;
@end
@implementation PortViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"Port线程通讯";
self.view.backgroundColor = [UIColor whiteColor];
//1. 创建主线程的port
// 子线程通过此端口发送消息给主线程
self.myPort = [NSMachPort port];
//2. 设置port的代理回调对象
self.myPort.delegate = self;
//3. 把port加入runloop,接收port消息
[[NSRunLoop currentRunLoop] addPort:self.myPort forMode:NSDefaultRunLoopMode];
self.person = [[Person alloc] init];
[NSThread detachNewThreadSelector:@selector(personLaunchThreadWithPort:)
toTarget:self.person
withObject:self.myPort];
}
#pragma mark - NSMachPortDelegate
- (void)handlePortMessage:(NSPortMessage *)message{
NSLog(@"从person 传过来的信息:");
NSArray *messageArr = [message valueForKey:@"components"];
NSString *dataStr = [[NSString alloc] initWithData:messageArr.firstObject encoding:NSUTF8StringEncoding];
NSLog(@"传过来的信息 :%@",dataStr);
NSPort *destinPort = [message valueForKey:@"remotePort"];
if(!destinPort || ![destinPort isKindOfClass:[NSPort class]]){
NSLog(@"传过来的数据有误");
return;
}
NSData *data = [@"VC收到!!!" dataUsingEncoding:NSUTF8StringEncoding];
NSMutableArray *array =[[NSMutableArray alloc]initWithArray:@[data,self.myPort]];
// 非常重要,如果你想在Person的port接受信息,必须加入到当前主线程的runloop
[[NSRunLoop currentRunLoop] addPort:destinPort forMode:NSDefaultRunLoopMode];
BOOL success = [destinPort sendBeforeDate:[NSDate date]
msgid:10010
components:array
from:self.myPort
reserved:0];
NSLog(@"%d",success);
}
- (void)getAllProperties:(id)somebody{
u_int count = 0;
objc_property_t *properties = class_copyPropertyList([somebody class], &count);
for (int i = 0; i < count; i++) {
const char *propertyName = property_getName(properties[i]);
NSLog(@"%@",[NSString stringWithUTF8String:propertyName]);
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
/*
#pragma mark - Navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
}
*/
@end
@interface Person()<NSMachPortDelegate>
@property (nonatomic, strong) NSPort *vcPort;
@property (nonatomic, strong) NSPort *myPort;
@end
@implementation Person
- (void)personLaunchThreadWithPort:(NSPort *)port{
NSLog(@"VC 响应了Person里面");
@autoreleasepool {
//1. 保存主线程传入的port
self.vcPort = port;
//2. 设置子线程名字
[[NSThread currentThread] setName:@"PersonThread"];
//3. 开启runloop
[[NSRunLoop currentRunLoop] run];
//4. 创建自己port
self.myPort = [NSMachPort port];
//5. 设置port的代理回调对象
self.myPort.delegate = self;
//6. 完成向主线程port发送消息
[self sendPortMessage];
}
}
/**
* 完成向主线程发送port消息
*/
- (void)sendPortMessage {
NSData *data1 = [@"data1" dataUsingEncoding:NSUTF8StringEncoding];
NSData *data2 = [@"data2" dataUsingEncoding:NSUTF8StringEncoding];
NSMutableArray *array =[[NSMutableArray alloc]initWithArray:@[data1,self.myPort]];
// 发送消息到VC的主线程
// 第一个参数:发送时间。
// msgid 消息标识。
// components,发送消息附带参数。
// reserved:为头部预留的字节数
[self.vcPort sendBeforeDate:[NSDate date]
msgid:10086
components:array
from:self.myPort
reserved:0];
}
#pragma mark - NSMachPortDelegate
- (void)handlePortMessage:(NSPortMessage *)message{
NSLog(@"从VC 传过来一些信息:");
//通过message的KVC取数据
}
@end