1、前言:
一个应用程序就是一个进程,一个进程至少包含一个线程,程序启动会自动创建一个主线程,负责UI界面的现实和控件事件的监控。多线程可以更充分的利用系统CPU资源,一定程度上提升程序的性能。1个进程可以开启多条线程,每条线程可以并行(同时)执行不同的任务。在一个线程内可以包含多个事物(要干的活),在线程内依据先进先出的特性串行执行……
2、NSThread
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"main thread is %@",[NSThread mainThread]);//打印主线程(UI线程)
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
//新开辟一条子线程,启动子线程的时候调用downLoad方法,object是传递给子线程调用方法的参数
NSThread *thread=[[NSThread alloc] initWithTarget:self selector:@selector(downLoad:) object:@"luseike"];
//给子线程起一个名字
thread.name=@"thread one";
//开始子线程
[thread start];
}
-(void)downLoad:(NSString *)param{
//打印当前线程(子线程)及其名字和传递过来的参数
NSLog(@"begin downLoad--param is %@--current thread is %@ current name is %@",param,[NSThread currentThread],[NSThread currentThread].name);
}
打印结果如下:
2014-06-26 23:03:05.724 NSThread[50292:60b] main thread is <NSThread: 0x8c421c0>{name = (null), num = 1}
2014-06-26 23:03:06.567 NSThread[50292:3807] begin downLoad--param is luseike--current thread is <NSThread: 0xa33c350>{name = thread one, num = 2} current name is thread one
可以看到主线程的num=1,子线程的num=2,证明确实是新开辟了一条线程来执行downLoad操作。
还可以通过isMainThread判断是否是主线程,setThreadPriority:(double)p来设置子线程的优先级,优先级的取值范围在0.0~1.0之间,默认是0.5,值越大,优先级越高,被执行的几率越大
还有两种比较便捷的方式来创建子线程
1、[NSThread detachNewThreadSelector:@selector(downLoad:) toTarget:self withObject:@"luseike"];// 附加一个线程
2、[self performSelectorInBackground:@selector(downLoad:) withObject:@"haha"]; //在后台执行一个线程
这两种方式创建的子线程都不需要调用start方法,系统会自动执行对应的方法,都也都没有机会设置优先级和咸线程名等更详细的设置了,不过这个一般不重要
3、线程的状态
线程从生到死大致有下面几种状态:新建(new)、就绪(runnable)、运行(running)、阻塞(blocked)、死亡(dead)
一个线程被创建之后会放到一个叫做可调度线程池内,等待被CPU调度,当该线程获得CPU的执行权时,就进入到running状态。running状态的线程如果调用sleep方法或者在等待同步锁,就会进入阻塞状态,进入阻塞状态的线程会重新被放到可调度线程池内,等待被重新调度。线程任务执行完毕,或者被强制退出,会进入dead状态,注意:进入dead状态的线程并没有被释放内存,只是不能用了而已,还存在内存中。
4、控制线程的状态
启动线程:之前介绍过,调用start方法
阻塞(暂停)线程:
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
强制停止线程:+(void)exit;
多线程的安全隐患:当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。解决的机制就是使用锁(互斥锁),让多条线程同步执行,这就是传说的线程同步技术。值得注意的是:锁定一份代码只用1把锁,用多把锁是没有意义的;线程同步的前提是多条线程抢占同一块资源
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic,strong)NSThread *thread1;
@property(nonatomic,strong)NSThread *thread2;
@property(nonatomic,strong)NSThread *thread3;
@property(nonatomic,assign)int totalCount;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.totalCount=100;
self.thread1=[[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread2=[[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread3=[[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[self.thread1 start];
[self.thread2 start];
[self.thread3 start];
}
-(void)saleTicket{
int leftCount=self.totalCount;
while (leftCount>0) {
self.totalCount=leftCount--;
NSLog(@"%@ sale one ticket,left %d",[NSThread currentThread],leftCount);
}
}
@end
如上代码,模拟3个子线程同时访问公共资源totalCount,每次操作减一,多线程访问同一资源容易引起的数据安全问题已经说明了,打印部分结果如下
2014-06-26 23:51:33.639 NSThread[63065:360b] <NSThread: 0xa06dd50>{name = (null), num = 3} sale one ticket,left 99
2014-06-26 23:51:33.639 NSThread[63065:3707] <NSThread: 0xa0706e0>{name = (null), num = 2} sale one ticket,left 99
2014-06-26 23:51:33.639 NSThread[63065:3f03] <NSThread: 0xa06ddf0>{name = (null), num = 4} sale one ticket,left 99
可以看到三个子线程都操作了totalCount变量,每次操作之后的值却没有变化……
互斥锁的使用格式:
@synchronized(锁对象) { // 需要锁定的代码 }
-(void)saleTicket{
while (1) {
@synchronized(self) { // 加锁(只能用一把锁)
// 1.先检查票数
int count = self.totalCount;
if (count > 0) {
self.totalCount = count - 1;
NSThread *current = [NSThread currentThread];
NSLog(@"%@ sale one ticket, left %d tickets", current, self.totalCount);
} else {
[NSThread exit];
}
}
}
}
打印结果就可以正常显示了……
5、GCD简介
GCD是苹果为了多核的并行运算提出的解决方案,会自动管理线程的生命周期,程序员只需要告诉GCD要执行什么任务,不需要编写任何线程管理代码。GCD中两个核心概念:任务、队列,将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行,任务的取出遵循队列FIFO原则:先进先出,后进后出。
GCD的队列可以分为2大类型:并发队列(可以让多个任务并发执行)、串行队列