您的位置:首页 > 移动开发 > IOS开发

iOS 多线程知识梳理

2016-04-27 22:27 246 查看
#iOS多线程知识梳理

##线程进程基础概念

###进程

进程是指在系统中正在运行的一个应用程序
每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内

###线程

1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程,称为主线程)
一个进程(程序)的所有任务都在线程中执行

>比较

1.线程是CPU调用(执行任务)的最小单位。
2.进程是CPU分配资源的最小单位。
3.一个进程中至少要有一个线程。
4.同一个进程内的线程共享进程的资源。

###多线程概述
1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务
多线程技术可以提高程序的执行效率

>原理

同一时间,CPU只能处理1条线程,只有1条线程在工作(执行),多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换),如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。
那么如果线程非常非常多,会发生什么情况?
CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源,同时每条线程被调度执行的频次也会会降低(线程的执行效率降低)。
因此我们一般只开3-5条线程

>优缺点

多线程的优点
能适当提高程序的执行效率
能适当提高资源利用率(CPU、内存利用率)
多线程的缺点
创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,也可以使用-setStackSize:设置,但必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的创建时间
如果开启大量的线程,会降低程序的性能,线程越多,CPU在调度线程上的开销就越大。
程序设计更加复杂:比如线程之间的通信、多线程的数据共享等问题

>多线程应用

主线程的主要作用
显示\刷新UI界面
处理UI事件(比如点击事件、滚动事件、拖拽事件等)
主线程的使用注意
别将比较耗时的操作放到主线程中
耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验
将耗时操作放在子线程中执行,提高程序的执行效率

##iOS 开发中的几种线程知识点

###pthread(c)

POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。Windows操作系统也有其移植版pthreads-win32

>具体使用

```
//创建线程
pthread_t thread;
/*
第一个参数pthread_t *restrict:线程对象
第二个参数const pthread_attr_t *restrict:线程属性
第三个参数void *(*)(void *) :指向函数的指针
第四个参数void *restrict:函数的参数
*/
pthread_create(&thread, NULL,run ,NULL);
```

###NSThread

>相对 pthread 提供了面向对象的方法使用更加简单

```
// 获取当前线程
+ (NSThread *)currentThread;
// 创建启动线程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;
// 判断是否是多线程
+ (BOOL)isMultiThreaded;
// 线程休眠 NSDate 休眠到什么时候
+ (void)sleepUntilDate:(NSDate *)date;
// 线程休眠时间
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 结束/退出当前线程
+ (void)exit;
// 获取当前线程优先级
+ (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);
// 判断当前线程是否是主线程
+ (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); // reports whether current thread is main
// 获取主线程
+ (NSThread *)mainThread NS_AVAILABLE(10_5, 2_0);

- (id)init NS_AVAILABLE(10_5, 2_0); // designated initializer
// 创建线程
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument NS_AVAILABLE(10_5, 2_0);
// 指定线程是否在执行
- (BOOL)isExecuting NS_AVAILABLE(10_5, 2_0);
// 线程是否完成
- (BOOL)isFinished NS_AVAILABLE(10_5, 2_0);
// 线程是否被取消 (是否给当前线程发过取消信号)
- (BOOL)isCancelled 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); // thread body meth
```

##GCD

###GCD的优势

GCD是苹果公司为多核的并行运算提出的解决方案
GCD会自动利用更多的CPU内核(比如双核、四核)
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

>核心:任务和队列

####任务

任务:执行什么操作,任务有两种执行方式: 同步函数 和 异步函数,他们之间的区别是

同步:只能在当前线程中执行任务,不具备开启新线程的能力,任务立刻马上执行,会阻塞当前线程并等待 Block中的任务执行完毕,然后当前线程才会继续往下运行

异步:可以在新的线程中执行任务,具备开启新线程的能力,但不一定会开新线程,当前线程会直接往下执行,不会阻塞当前线程

* 异步操作:dispatch_async

* 同步操作:dispatch_sync

```
//async: asynchronous 将任务异步的追加到队列中

dispatch_async(dispatch_get_global_queue(0,0), ^{

NSLog(@"async");

});

//sync: synchronous将任务同步的追加到队列中(等队列中的任务执行完,再将任务追加到队列)

//是同步追加,不是任务同步执行,在串行队列中,任务才同步执行

dispatch_sync(dispatch_get_global_queue(0,0), ^{

NSLog(@"sync");

});

```

#### 队列
队列:用来存放任务,分为串行队列 和 并行队列

串行队列(Serial Dispatch Queue)
让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

并发队列(Concurrent Dispatch Queue)
可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
并发功能只有在异步(dispatch_async)函数下才有效

>队列代码

```
dispatch_queue_t queue = dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
```

* Serial Dispatch Queue --- 等待现在正在执行的任务处理结束(串行)

* Concurrent Dispatch Queue --- 不等待现在正在执行的任务处理结束(并行、并发)

>并发队列

```
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_CONCURRENT);
```

>串行队列

```
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL);
```

###优先级

```
/**
第一个参数:优先级 也可直接填后面的数字
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台
第二个参数: 预留参数 0
*/
dispatch_queue_t quque1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
```

###任务和队列的组合

1. 任务:同步函数 异步函数

2. 队列:串行 并行

* 异步函数+并发队列:会开启新的线程,并发执行

```
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"---download1---%@",[NSThread currentThread]);
});
```

* 异步函数+串行队列:会开启一条线程,任务串行执行

```
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"---download1---%@",[NSThread currentThread]);
});
```

* 同步函数+并发队列:不会开线程,任务串行执行

```
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_sync(queue, ^{
NSLog(@"---download1---%@",[NSThread currentThread]);
});
```

* 同步函数+串行队列:不会开线程,任务串行执行

```
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"---download1---%@",[NSThread currentThread]);
});
```

* 异步函数+主队列:不会开线程,任务串行执行

```
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"---download1---%@",[NSThread currentThread]);
});
```

*同步函数+主队列:死锁

```
//1.获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
//2.同步函数
dispatch_sync(queue, ^{
NSLog(@"---download1---%@",[NSThread currentThread]);
});
```

#### 同步异步函数区别

* 同步函数:立刻马上执行,会阻塞当前线程

* 异步函数:当前线程会直接往下执行,不会阻塞当前线程

##GCD 常用函数用法

####1. dispatch_after

>延迟执行

```
/*
第一个参数:延迟时间
第二个参数:要执行的代码
如果想让延迟的代码在子线程中执行,也可以更改在哪个队列中执行 dispatch_get_main_queue() -> dispatch_get_global_queue(0, 0)
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"---%@",[NSThread currentThread]);
});
```

####2. dispatch_once

>一次性代码

```
-(void)once
{
//整个程序运行过程中只会执行一次
//onceToken用来记录该部分的代码是否被执行过
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"-----");
});
}

//创建一个单例方法
+ (ShareOnce *)shanreInstence{

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

instance = [[ShareOncealloc] init];

NSLog(@"只执行1次");

});

returninstance;

}
```

####3. dispatch_group_async & dispatch_group_notify

>组队列

```
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 创建并行队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 执行队列组任务
dispatch_group_async(group, queue, ^{
});
//队列组中的任务执行完毕之后,执行该函数
dispatch_group_notify(group, queue, ^{
});
```

#####例子

```
//并行队列执行任务,在并行队列中加入多个串行队列

//disptch_group

dispatch_group_t group =dispatch_group_create();

dispatch_queue_t queue =dispatch_get_global_queue(0,0);

dispatch_group_async(group, queue, ^{

NSLog(@"task 01");

});

dispatch_group_async(group, queue, ^{

NSLog(@"task 02");

});

dispatch_group_async(group, queue, ^{

sleep(6);//等待6秒执行任务3 目的:测试等待时间的dispatch_group_wait这个方法

NSLog(@"task 03");

});

dispatch_group_async(group, queue, ^{

sleep(2);

NSLog(@"task 04");

});

//监视函数

//监视队列中得任务结束,执行block中得任务

dispatch_group_notify(group, queue, ^{

NSLog(@"done");

});

//等待时间

dispatch_time_t time =dispatch_time(DISPATCH_TIME_NOW,5ull*NSEC_PER_SEC);

//dispatch_group_wait 指定时间后,看你一眼queue是否执行完毕

//如果执行完返回0

//没有执行完,返回非0值

long result = dispatch_group_wait(group, time);

if (result == 0) {

NSLog(@"finish");

}else{

NSLog(@"not finish");
}
}
```

####4. dispatch_barrier_async

>栅栏函数

栅栏函数可以控制任务执行的顺序,栅栏函数之前的执行完毕之后,执行栅栏函数,然后在执行栅栏函数之后的

####5. dispatch_apply

>快速迭代

```
/*
第一个参数:迭代的次数
第二个参数:在哪个队列中执行
第三个参数:block要执行的任务
*/
dispatch_apply(10, queue, ^(size_t index) {
});
```

####6. dispatch_semaphore_create & dispatch_semaphore_signal & dispatch_semaphore_wait

>信号量机制

```
//使用

dispatch_queue_t queue =dispatch_get_global_queue(0,0);

dispatch_semaphore_t dsema =dispatch_semaphore_create(1);

NSMutableArray *array = [NSMutableArrayarray];

for (int i =0; i < 1000; i++) {
dispatch_async(queue, ^{
dispatch_semaphore_wait(dsema,DISPATCH_TIME_FOREVER);
[arrayaddObject:[NSNumbernumberWithInt:i]];
dispatch_semaphore_signal(dsema);

});

}

NSLog(@"%@", array);

```

##NSOperation

NSOperation 是苹果公司对 GCD 的封装,完全面向对象,并比GCD多了一些更简单实用的功能,所以使用起来更加方便易于理解。NSOperation 和NSOperationQueue 分别对应 GCD 的 任务 和 队列。

NSOperation和NSOperationQueue实现多线程的具体步骤

1.将需要执行的操作封装到一个NSOperation对象中

2.将NSOperation对象添加到NSOperationQueue中
系统会自动将NSOperationQueue中的NSOperation取出来,并将取出的NSOperation封装的操作放到一条新线程中执行

###NSOperation

>NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类

####使用NSOperation子类的方式有3种

#####1.NSInvocationOperation

```
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download) object:nil];
//启动操作
[op start];
```

#####2.NSBlockOperation 常用的一种

```
//1.封装操作
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
//要执行的操作,在主线程中执行
NSLog(@"1------%@",[NSThread currentThread]);
}];
//2.追加操作,追加的操作在子线程中执行,可以追加多条操作
[op addExecutionBlock:^{
NSLog(@"---download2--%@",[NSThread currentThread]);
}];
[op start];
```

#####3.自定义类继承NSOperation,实现内部方法,复用性强

```
// 重写自定义类的main方法实现封装操作
-(void)main
{
// 要执行的操作
}

// 实例化一个自定义对象,并执行操作
CustomOperation *op = [[CustomOperation alloc]init];
[op start];
```

##NSOperationQueue队列的使用

###两种队列

主队列:通过mainQueue获得,凡是放到主队列中的任务都将在主线程执行

非主队列:直接alloc init出来的队列。非主队列同时具备了并发和串行的功能,通过设置最大并发数属性来控制任务是并发执行还是串行执行

>作用

NSOperation可以调用start方法来执行任务,但默认是同步执行的
如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作

####两者结合的方法使用 NSOperation和NSOperationQueue结合使用创建多线程

```
注:这里使用NSBlockOperation示例,其他两种方法一样
// 1. 创建非主队列 同时具备并发和串行的功能,默认是并发队列
NSOperationQueue *queue =[[NSOperationQueue alloc]init];
//NSBlockOperation 不论封装操作还是追加操作都是异步并发执行
// 2. 封装操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download1 -- %@",[NSThread currentThread]);
}];
// 3. 将封装操作加入主队列
// 也可以不获取封装操作对象 直接添加操作到队列中
//[queue addOperationWithBlock:^{
// 操作
//}];
[queue addOperation:op1];

```

###NSOperation和NSOperationQueue的重要属性和方法

* NSOperation的依赖

* NSOperation监听操作

* NSOperationQueue

* maxConcurrentOperationCount 并发数

* suspended 是否暂停

* cancelAllOperations 取消所有任务,不再执行,不可逆

>关键点:暂停和取消只能暂停或取消处于等待状态的任务,不能暂停或取消正在执行中的任务,必须等正在执行的任务执行完毕之后才会暂停,如果想要暂停或者取消正在执行的任务,可以在每个任务之间即每当执行完一段耗时操作之后,判断是否任务是否被取消或者暂停。如果想要精确的控制,则需要将判断代码放在任务之中,但是不建议这么做,频繁的判断会消耗太多时间

##一个例子

```
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property(nonatomic,strong)UIImage *image1;
@property(nonatomic,strong)UIImage *image2;
@end
@implementation ViewController

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 创建非住队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// 下载第一张图片
NSBlockOperation *download1 = [NSBlockOperation blockOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:@"http://img2.3lian.com/2014/c7/12/d/77.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
self.image1 = [UIImage imageWithData:data];
}];
// 下载第二张图片
NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:@"http://img2.3lian.com/2014/c7/12/d/77.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
self.image2 = [UIImage imageWithData:data];
}];
// 合成操作
NSBlockOperation *combie = [NSBlockOperation blockOperationWithBlock:^{
// 开启图形上下文
UIGraphicsBeginImageContext(CGSizeMake(375, 667));
// 绘制图片1
[self.image1 drawInRect:CGRectMake(0, 0, 375, 333)];
// 绘制图片2
[self.image2 drawInRect:CGRectMake(0, 334, 375, 333)];
// 获取合成图片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// 关闭图形上下文
UIGraphicsEndImageContext();
// 回到主线程刷新UI
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
self.imageView.image = image;
}];
}];
// 添加依赖,合成图片需要等图片1,图片2都下载完毕之后合成
[combie addDependency:download1];
[combie addDependency:download2];
// 添加操作到队列
[queue addOperation:download1];
[queue addOperation:download2];
[queue addOperation:combie];
}
@end
```

##多线程共享读写资源隐患问题



>容易死锁

```
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1========%@",[NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2========%@",[NSThread currentThread]);
});
NSLog(@"3========%@",[NSThread currentThread]);
}

ThreadDemo[5615:874679] 1========{number = 1, name = main}

```
>原因:任务A等待任务B完成才能继续执行,但作为串行队列的主队列又不能让任务B在任务A未完成之前开始执行,所以任务A等着任务B完成,任务B等着任务A完成
>

###解决方案:

1.普通执行
2.同步队列
3.异步队列

>多线程安全隐患的原因:1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,比如多个线程访问同一个对象、同一个变量、同一个文件。
那么当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。

> 可以使用互斥锁解决

几种加锁方法

* synchronized

@synchronized(锁对象) {
// 需要锁定的代码
}

* NSLock
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: