在说多线程之前,首先要明白线程,进程
线程和进程
进程:
- 系统中正在运行的程序,称为一个进程
- 每个进程,都在自己独立的内存空间运行,并且进程之间互不影响。
线程:
- 进程的所有任务,都是在线程执行的,可以说它是进程的基本执行单元,是程序中独立运行的代码段。
- 主线程:每个正在运行的程序(即进程),至少包含一个线程,这个线程叫做主线程
- 单线程:只有一个主线程的程序,称作单线程程序
多线程
顾名思义,多线程就是拥有多个线程的程序,iOS用户可以根据需要在一段进程中开辟新的线程,这些线程相对于主线程来说被称为子线程,子线程和主线程可以并行(同时)执行不同的任务。
原理:
1.同一时间,CPU只能处理一条线程,只有一条线程在工作(执行)
2.多线程并发(同时执行,其实是CPU快速地在多条线程之间)调度(切换)
3.如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
4.多核CPU,每个核心都可以同时处理不同任务,从而真正达到了多线程并发执行任务
5.线程非常多:
1> CPU会在N多条线程之间调度,CPU会累死,消耗大量的CPU资源
2> 每条线程被调度执行的频次会降低(线程的执行效率降低)
多线程优点
1.能适当提高程序的执行效率
2.能适当提高资源利用率(CPU、内存利用率)
多线程缺点
1.开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
2.线程越多,CPU在调度线程上的开销就越大
3.程序设计更加复杂:比如线程之间的通信,多线程的数据共享
多线程在iOS开发中的应用
1.什么是主线程
1> 一个iOS程序运行后,默认会开启一条线程,称为"主线程"或"UI线程"
2> 每一个进程都有一个独立的主线程
2.主线程的主要作用
1> 显示\刷新UI界面
2> 处理UI事件 (比如点击事件、滚动事件、拖拽事件等)
3.主线程的使用注意
1> 别将比较耗时的操作放到主线程中
2> 耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种"卡"的坏体验
例如:首先随便在viewController上拖一个textView或者是写一个button的点击事件,然后在viewDidLoad里写上一个稍微耗时的操作:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (int i = 0; i<10000; i++) {
NSLog(@"---------%d", i);
}
}
那么当点击页面后快速的滑动textView,这时会发现,它是没反应的
一直等到for循环走完之后,才会有反应,继续执行操作,这就是阻塞主线程。
多线程的实现方案
技术方案 | 简介 | 语言 | 线程声明周期 | 使用频率 |
---|---|---|---|---|
pathread | 一套通用的多线程API 适用于Unix\Linux\Windows等系统跨平台\可移植使用难度大 | C | 程序员管理 | 几乎不用 |
NSThread | 使用更加面向对象简单易用,可直接操作线程对象 | OC | 程序员管理 | 偶尔使用 |
GCD | 旨在替代NSThread等线程技术充分利用设备的多核 | C | 自动管理 | 经常使用 |
NSOperation | 基于GCD(底层是GCD)比GCD多了一些简单实用的功能实用更加面向对象 | OC | 自动管理 | 经常使用 |
pthread的创建:
//创建
pthread_t myRestrict;
pthread_create(&myRestrict, NULL, run, NULL);
void *run(void *data){
NSLog(@"*******%@",[NSThread currentThread]);
return NULL;
}
因为不常用,不做多解释,从打印结果可以看出,开辟了子线程
NSThread创建和启动线程的3种方式:
#pragma mark ---- 1.使用NSThread手动开辟子线程 ----
// 1、创建线程(NSThread)
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(downLoad:) object:"http//:abcde.png"];
//线程的名字
thread.name = @"下载线程";
// 2、开启线程
[thread start];
// 3、关闭线程(可写可不写)
[NSThread exit];
// 4、取消线程(实际上就是做了个标记,表示被取消了)
[thread cancel];
#pragma mark ---- 2.使用NSThread自动开辟子线程(无需手动) ----
[NSThread detachNewThreadSelector:@selector(downLoad:) toTarget:self withObject:"http//:abcde.png"];
[thread start];
#pragma mark ---- 3.使用NSObject开辟子线程(在后台执行某个方法),也叫隐式创建(无需手动) ----
/*
[self performSelector:@selector(downLoad:) withObject:@"http//:abcde.png"];
//相当于下面这段代码
[self downLoad:@"http//:abcde.png"];
#因此,这个方法是不开辟子线程的
*/
[self performSelectorInBackground:@selector(downLoad:) withObject:@"http//:abcde.png"];
//这个方法开辟子线程
常见的方法:
1> 获得当前线程
+ (NSThread *)currentThread;
2> 获得主线程
+ (NSThread *)mainThread;
3>睡眠(暂停)线程
+(void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti
------
//回到主线程刷新数据
[self performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES];
-(void)reloadData{
//刷新UI
if ([NSThread isMainThread]) {
//这里做一些需要的操作
}
}
利用上面的一些方法,可以做一些简单的多线程之间的通信,例如开辟子线程下载图片,回到主线程刷新,这里不再操作
多线程的安全隐患
1.资源共享
1> 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
2> 比如多个线程访问同一个对象、同一个变量、同一个文件
2.当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题
安全隐患解决--加锁(互斥锁)
1.互斥锁的优缺点
1> 优点:能有效的防止多线程抢夺资源造成的数据安全问题
2> 缺点:需要消耗大量的CPU资源
2.互斥锁的使用前提:多条线程抢夺同一块资源
线程同步的意思是:多条线程实在同一条线上执行(按顺序的执行任务)
互斥锁,就是使用了线程同步技术
单纯的靠文字叙述可能会比较难理解,可以参考以下例子:写一个卖票的简单程序
- (void)viewDidLoad
{
[super viewDidLoad];
self.leftTicketCount = 50;
self.thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread1.name = @"1号窗口";
self.thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread2.name = @"2号窗口";
self.thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread3.name = @"3号窗口";
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.thread1 start];
[self.thread2 start];
[self.thread3 start];
}
/**
* 卖票
*/
- (void)saleTicket
{
while (1) {
int count = self.leftTicketCount;
if (count > 0) {
[NSThread sleepForTimeInterval:0.05];
self.leftTicketCount = count - 1;
NSLog(@"%@卖了一张票, 剩余%d张票", [NSThread currentThread].name, self.leftTicketCount);
} else {
return; // 退出循环
}
}
}
运行结果:
这就是多线程访问同一块资源造成的
加锁:在刚才的方法里加上@synchronized
- (void)saleTicket
{
while (1) {
// ()小括号里面放的是锁对象
@synchronized(self) { // 开始加锁
int count = self.leftTicketCount;
if (count > 0) {
[NSThread sleepForTimeInterval:0.05];
self.leftTicketCount = count - 1;
NSLog(@"%@卖了一张票, 剩余%d张票", [NSThread currentThread].name, self.leftTicketCount);
} else {
return; // 退出循环
}
} // 解锁
}
}
GCD的内容较多,我会在下篇文章里详细介绍
NSOperation
1.首先,它是一个抽象类,所以执行任务的是它的子类:NSInvocationOperation和NSBlockOperation(还有:自定义子类继承NSOperation,实现内部相应的⽅法)。这两个子类,相当于一个方法选择器“prefromSelector()”,由它俩本身发起的任务,并不是在子线程中执行。
2.NSOperation和它的子类,本身并不会进行线程的创建,所以,在他们的任务方法中打印当前线程,显示为主线程
3.NSOperation和它的子类,只是一个操作,本身没有主线程、子线程之分,可以在任何线程中使用,通常和NSOperationQueue结合使用。
NSOperationQueue
1、一个NSOperationQueue操作队列,就相当于一个线程管理器,将NSOperation和子类的对象放入队列中,然后由队列负责派发任务,所以NSOperationQueue并不是一个线程。但是,你可以设置队列中运行的线程的数量
---- 优点 ----
不需要手动关联线程,只需要把精力放在自己要执行的操作上面。
---- 缺点 ----
它是基于OC对象的,那么相对于C函数来说效率要低
配合使用NSOperation和NSOperationQueue也能实现多线程编程
//子类一:NSInvocationOperation
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(hehehe) object:nil];
//operation在单独使用的时候,需要手动调用开启方法
[operation start];
#operation直接调用start,是同步执行(在当前线程中执行操作),说白了就相当于[self hehehe];并没有什么卵用
//配合NSOperationQueue使用:
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(hehehe) object:nil];
[queue addOperation:operation];//********注意:如果搭配了NSOperationQueue中的add方法创建多线程的话,就不需要使用satrt方法,否则会崩溃
//子类二:NSBlockOperation
//任务数量 > 1,才会开始异步执行
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block:%@",[NSThread currentThread]);
NSLog(@"block:%@",[NSThread mainThread]);
NSLog(@"block:%d",[NSThread isMainThread]);
NSLog(@"*********");
}];
//开启
[blockOperation start];
//配合NSOperationQueue使用:
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block:%@",[NSThread currentThread]);
NSLog(@"block:%@",[NSThread mainThread]);
NSLog(@"block:%d",[NSThread isMainThread]);
NSLog(@"*********");
}];
[queue addOperation:blockOperation];
//简单的做法:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
NSLog(@"*******%@", [NSThread currentThread]);
}];
#------设置最大并发数-----
//当设置最大并发数为1时 :也可叫做串行,顺序执行
//当设置最大并发数大于1时:叫并行,多条通道同时进行各自的任务,互不影响。
queue.maxConcurrentOperationCount = 3;
除了GCD,其他两种主要的创建多线程的方法基本已经介绍完了,如果觉得有什么问题或者错误,欢迎留言或发简信!!