1 操作系统、进程、线程简单介绍
现在的程序都是在操作系统上跑,很少有裸机的,而且大部分的嵌入式应用也都支持操作系统,当然还有一些很低端的嵌入式设备没有操作系统。
iPhone手机跑的是iOS操作系统(基于Unix改造),操作系统用来管理设备的硬件设备,提供接口API(驱动程序)供不同的APP来调用,从而实现操作硬件,比如通过网卡实现网络访问、相机实现拍照、集成声卡实现发音、蓝牙实现无线传输等等。这里的APP可以简单的理解为一个进程,比如qq、微信、淘宝等等。
在iOS系统中,一个应用程序只允许有一个进程。不过,大家可能见过下图,下拉菜单的"今天",以今日头条为例(啥时间也开发个)。"今天"这里的今日头条和我们home页上的是不是一个应用开启的两个进程呢?而且在AppStore下载的时候,只下载了一次,然后这里就有了,难道不是吗?
首先,"今天"里面显示的也是今日头条,也是随AppStore下载时一并下载下来的,不过它只是一个widget,可以理解为它是一个运行在"下拉菜单"这个进程里面的一个线程,如果"今天"被关闭了,那么widget也随之停止运行。
这个是iOS8之后才有的特性,官方叫做扩展(Extension)
每个进程都拥有独立的存储空间沙盒和内存空间,不同的进程之间的内存肯定是不能相互访问,而且存储空间也是不能相互访问的。不过,iOS8之后,苹果推出来App Groups,可以是应用和widget还有Applewatch直接通过[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.xxx"]
函数访问group的内存空间,还需要做一些设置
那我们面试的时候,或者平时提到的进程间通信是怎么回事呢?这个是苹果提供的scheme协议,如下:
[[UIApplication sharedApplication] openURL:urlStr];
另外,安卓操作系统是允许多个进程的。
一个进程可以拥有多个线程,线程是真正执行程序流的执行单元。iOS中有主线程(UI线程)的概念,主线程和非主线程的默认栈大小是不一样的。主线程1M,非主线程512KB。但是它们消耗的内核空间大小都是一样的1KB(因为管理线程的数据结构是一样的)
2 pthread
一套通用的多线程API,采用C语言编写,跨平台/可移植,适用于Unix、Linux、Windows
等系统,但是使用起来难度比较大,生命周期也是由程序员来管理。在iOS编程中很少使用这种方法。
举例如下:
#import
/// 点击屏幕创建一个线程
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
pthread_t thread; //创建线程
NSString *str = @"helloWorld"; //创建参数
// 参数1 线程编号的地址
// 参数2 线程的属性
// 参数3 线程要执行的函数(函数指针)(第三个参数可以,demo,*demo, 一般用&demo)
// 参数4 线程要执行的函数的参数
int result = pthread_create(&thread, NULL, &demo, (__bridge void *)(str));
// __bridge oc类型到c语言类型的一个转换
// void *p = (__bridge void *)(str);
NSLog(@"over %d",result);
}
/// 线程要执行的函数 传参数
void *(demo)(void *param){
NSString *str = (__bridge NSString *)(param);
NSLog(@"%@",str);
return NULL;
}
3 NSThread
NSThread是轻量级的多线程开发,OC语言编写,更加面向对象,使用起来也并不复杂,但是使用NSThread需要自己管理线程生命周期。在iOS开发中很少使用它来创建一个线程,但是经常使用它做一些延时操作,获取当前线程,线程间通讯等等。
但是,在线程同步方面,控制线程执行顺序比较麻烦,线程同步对数据的加锁会有一定的系统开销,且创建线程也会增加系统的开销。
- 创建方法
有多种创建方法,代码如下,其中- (void)runDemo:(NSString *)param;
为要执行的示例方法。
- (void)runDemo:(NSString *)param {
NSThread *current = [NSThread currentThread];
NSLog(@"%@---%@ is running", param, current);
}
/// 方式1 自动创建线程, 并且自动启动
- (void)threadCreateOne {
// 在另一个线程执行 runDemo:
[self performSelectorInBackground:@selector(runDemo:) withObject:@"One"];
}
/// 方式2 创建完线程直接(自动)启动
- (void)threadCreateTwo {
[NSThread detachNewThreadSelector:@selector(runDemo:) toTarget:self withObject:@"Two"];
}
/// 方式3 先创建初始化线程,然后start开启线程
- (void)threadCreateThree {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(runDemo:) object:@"Three"];
// 可以设置线程名字
thread.name = @"名字";
// 开启线程
[thread start];
}
下面为测试代码,以及打印结果,我们调用的顺序是One->Two->Three,但是打印结果是Two->Three->One,因为线程启动后仅仅处于就绪状态,实际是否执行要由CPU根据当前状态调度,即执行顺序是无序的,这也是多线程的特点。
/// 点击屏幕后创建线程
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[self threadCreateOne];
[self threadCreateTwo];
[self threadCreateThree];
}
打印结果:
2015-08-27 16:27:34.974 01test[1183:76667] Two---{number = 3, name = (null)} is running
2015-08-27 16:27:34.974 01test[1183:76668] Three---{number = 4, name = 名字} is running
2015-08-27 16:27:34.974 01test[1183:76666] One---{number = 2, name = (null)} is running
- 常用函数
获取当前线程,获取主线程,判断当前线程是否为主线程。
// 获取当前线程
NSThread *current = [NSThread currentThread];
// 获取主线程
current = [NSThread mainThread];
// 判断当前线程是否为主线程
BOOL isMain = [current isMainThread];
暂停线程,下面代码为2种方法均让当前线程睡5s
[NSThread sleepForTimeInterval:5];
NSDate *date = [NSDate dateWithTimeInterval:5 sinceDate:[NSDate date]];
[NSThread sleepUntilDate:date];
获取线程的状态,分别为:正在执行、已经完成、已经取消。
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;
在指定的线程(已存在的线程)、主线程、当前线程上执行方法。这种比较常用,通常用于线程间通讯,且它们是NSObject的扩展方法,使用起来很方便。
// 在指定的线程执行runDemo:方法,最后的YES代表:下面的代码会阻塞,等runDemo:方法在thread线程执行完毕后,才会执行下面代码的下一行代码,设为NO则不阻塞。那么runDemo:与下一行代码的执行顺序不确定
[self performSelector:@selector(runDemo:) onThread:thread withObject:nil waitUntilDone:YES];
// 在主线程执行runDemo:方法,YES参数同上
[self performSelectorOnMainThread:@selector(runDemo:) withObject:nil waitUntilDone:YES];
// 在当前线程执行方法
[self performSelector:@selector(run) withObject:nil];
退出线程
杀死线程,会阻碍线程内部自身的清理工作,所以最好让线程自己正常退出,而不是类似exit()这样的强制退出,(虽然很方便)
+ (void)exit;
线程优先级相关,优先级范围是0.0 ~ 1.0,默认0.5,值越大,优先级越高。开发时,很少使用优先级,如果设置优先级且使用线程锁会造成优先级翻转,需要特别注意。
+ (double)threadPriority;
+ (BOOL)setThreadPriority:(double)p;
最后
关于自动释放池,系统会帮我们自动创建,不需要我们自己处理
参考:http://www.cnblogs.com/mddblog/p/4763511.html
http://www.cnblogs.com/mddblog/p/4763925.html