1. 多线程基本概念


1.1 进程

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

1.2 线程

基本概念:一个进程要想执行任务,必须得有线程(每一个进程至少要有一条线程),线程是进程的基本执行单元,一个进程(程序)的所有任务都在线程中执行。

线程的串行:一条线程中任务的执行是串行的,如果要在一条线程中执行多个任务,那么只能一个一个地按顺序执行这些任务。也就是说,在同一时间内,一条线程只能执行一个任务。

1.3 多线程

基本概念:即一个进程中可以开启多个线程,每条线程可以并行(同时)执行不同的任务。

线程的并行:并行即同时执行。比如同时开启3条线程分别下载3个文件(分别是文件A、文件B、文件C)。

多线程并发执行的原理:在同一时间里,CPU只能处理1条线程,只有1条线程在工作(执行)。多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换),如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。

多线程优缺点

优点:能适当提高程序的执行效率、能适当提高资源利用率(CPU、内存利用率)

缺点:

1)开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能。

2)线程越多,CPU在调度线程上的开销就越大。

3)程序设计更加复杂:比如线程之间的通信、多线程的数据共享

1.4 多线程在iOS开发中的应用

1.4.1 主线程

​ 1)一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”。

​ 2)作用。刷新显示UI,处理UI事件。

1.4.2 使用注意

​ 1)不要将耗时操作放到主线程中去处理,会卡住线程。

​ 2)和UI相关的刷新操作必须放到主线程中进行处理。

2. 多线程实现方案

技术方案 简介 语言 线程生命周期管理 使用频率
pthread —> 一套通用的多线程API
—> 适用于Unix\Linux\ Windows等系统
—> 跨平台\可植入
—> 使用难度比较大
C 手动 极低
NSThread —> 使用更加面向对象
—> 简单易用,可以直接操作线程对象
Objective-C 手动
GCD —> 旨在替代NSThread等线程技术
—> 充分利用设备的多核
C 自动
NSOperation —> 基于GCD (底层是GCD)
—> 比GCD多了一些更简单实用的功能
—> 使用更加面向对象
Objective-C 自动

2.1 pthread

//pthread需要包含头文件
//1.创建线程对象
pthread_t thread;
NSString *name = @"线程";
//2.使用pthread创建线程
//第一个参数:线程对象地址
//第二个参数:线程属性
//第三个参数:指向函数的指针
//第四个参数:传递给该函数的参数
pthread_create(&thread, NULL, run, (__bridge void *)(name));

2.2 NSThread

2.2.1 基本使用

// 第一种:alloc init,需要手动开启线程,可以拿到线程对象进行详细设置
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"线程"];
[thread start]; // 第二种:分离(detach)出一条子线程,自动启动线程,无法对线程进行更详细的设置
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"分离出的子线程"]; // 第三种:后台线程,自启动,不能详细设置
[self performSelectorInBackground:@selector(run:) withObject:@"后台线程"];

2.2.2 设置线程属性

thread.name = @"线程A"; // 线程名称
thread.threadPriority = 1.0; // 线程的优先级,取值范围0.0~1.0,1.0的优先级最高,默认0.5

2.2.3 线程的状态

// 线程的各种状态:新建-就绪-运行-阻塞-死亡
// 常用的控制线程状态的方法
[NSThread exit]; // 退出当前线程
[NSThread sleepForTimeInterval:2.0]; // 阻塞线程
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];//阻塞线程
// 注意:线程死了不能复生

2.2.4 线程安全

01 前提:多个线程访问同一块资源会发生数据安全问题
02 解决方案:加互斥锁
03 相关代码:@synchronized(self){}
04 专业术语-线程同步
05 原子和非原子属性(是否对setter方法加锁)

2.2.5线程间通信

-(void)touchesBegan:(nonnull NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
// 开启子线程下载图片
[NSThread detachNewThreadSelector:@selector(downloadImage) toTarget:self withObject:nil];
} -(void)downloadImage { NSURL *url = [NSURL URLWithString:@"http://p6.qhimg.com/t01d2954e2799c461ab.jpg"];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]]; // 回到主线程刷新UI,以下三种任选其一,第二种方面些
[self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
[self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
}

2.2.6 如何计算代码段的执行时间

//第一种方法
NSDate *start = [NSDate date];
NSData *data = [NSData dataWithContentsOfURL:url];
NSDate *end = [NSDate date];
NSLog(@"耗时%f",[end timeIntervalSinceDate:start]); //第二种方法
CFTimeInterval start = CFAbsoluteTimeGetCurrent();
NSData *data = [NSData dataWithContentsOfURL:url];
CFTimeInterval end = CFAbsoluteTimeGetCurrent();
NSLog(@"耗时%f",end - start);

2.3 GCD

2.3.1 GCD基本知识

  • 两个核心概念 队列和任务
  • 同步函数和异步函数

2.3.2 GCD基本使用

主队列:dispatch_get_main_queue()

并发队列:dispatch_get_global_queue(0, 0),dispatch_queue_create("com.test", DISPATCH_QUEUE_CONCURRENT)

手动创建串行队列:dispatch_queue_create("com.test", DISPATCH_QUEUE_SERIAL)

并发队列 手动创建的串行队列 主队列
dispatch_sync 没有开启新的线程,串行执行 同← 同←
dispatch_async 开启新的线程,并发执行 开启新的线程,串行执行 没有开启新的线程,串行执行

2.3.3 GCD线程间通信

// 获取一个全局的队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 先开启一个线程,把下载图片的操作放在子线程中处理
dispatch_async(queue, ^{
// 下载图片
NSString *urlString = @"http://h.hiphotos.baidu.com/zhidao/pic/item/6a63f6246b600c3320b14bb3184c510fd8f9a185.jpg";
NSURL *url = [NSURL URLWithString:urlString];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
// 回到主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});

2.3.4 GCD其它常用函数

—> 栅栏函数(控制任务的执行顺序)

以下实例,会保证Task One和Task Two全部完成后执行之后的Task Three和Task Four

dispatch_queue_t queue = dispatch_queue_create("com.example.myqueue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"Task One");
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"Task Two");
}
}); // The queue you specify should be a concurrent queue that you create yourself using the dispatch_queue_create function. If the queue you pass to this function is a serial queue or one of the global concurrent queues, this function behaves like the dispatch_async function.
// 使用 dispatch_queue_create 函数创建并发队列(使用DISPATCH_QUEUE_CONCURRENT, 而不是DISPATCH_QUEUE_SERIAL), 不要使用dispatch_get_global_queue, 否则和dispatch_async一样效果
dispatch_barrier_async(queue, ^{
NSLog(@"++++++++++");
}); dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"Task Three");
}
}); dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"Task Four");
}
});

—> 延迟执行(延迟并且可以控制在哪个线程执行)

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// code to be executed after a specified delay
});

—> 一次性代码

-(void)once {
// 整个程序运行过程中只会执行一次(注意区分一次性代码和懒加载)
// onceToken用来记录该部分的代码是否被执行过
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
}

—> 快速迭代(开多个线程并发完成迭代操作)

dispatch_apply(12, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"SuperLog------ %zd",index);
});

—> 队列组(同栅栏函数)

dispatch_queue_t queue = dispatch_queue_create("com.example", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ });
// 队列组中的任务执行完毕之后,执行该函数
dispatch_group_notify(group, queue, ^{ });

2.4.NSOperation

2.4.1 概念

  • NSOperation是对GCD的包装
  • 两个核心概念【队列+操作】

2.4.2 使用

  • NSOperation本身是抽象类,只能使用它的子类
  • 三个子类分别是:NSBlockOperation、NSInvocationOperation以及自定义继承自NSOperation的类
  • NSOperation和NSOperationQueue结合使用实现多线程并发

2.4.3 代码

—> NSInvocationOperation

NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];
[operation start];

—> NSBlockOperation

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
// 在主线程中执行
NSLog(@"downloadOne--%@",[NSThread currentThread]);
}]; // 增加操作,增加的操作在子线程中执行
[operation addExecutionBlock:^{
NSLog(@"downloadTwo--%@",[NSThread currentThread]);
}]; [operation addExecutionBlock:^{
NSLog(@"downloadThree--%@",[NSThread currentThread]);
}]; [operation start];

—> 自定义NSOperation

// 自定义的NSOperation, 通过重写内部的main方法实现封装操作
-(void)main {
NSLog(@"--main--%@",[NSThread currentThread]);
}
// 使用
SPOperation *operation = [[SPOperation alloc] init];
[operation start];

2.5 NSOperationQueue

2.5.1 NSOperation中的两种队列

  • 主队列 通过mainQueue获得,凡是放到主队列中的任务都将在主线程执行
  • 非主队列 直接alloc init出来的队列。非主队列同时具备了并发和串行的功能,通过设置最大并发数属性来控制任务是并发执行还是串行执行

2.5.2 代码

GCD中的队列

串行队列:dispatch_queue_create("com.test", DISPATCH_QUEUE_SERIAL),dispatch_get_main_queue()

并发队列:dispatch_queue_create("com.test", DISPATCH_QUEUE_CONCURRENT),dispatch_get_global_queue(0, 0)

NSOperationQueue

主队列:[NSOperationQueue mainqueue] 放在主队列中的操作都在主线程中执行

非主队列:[[NSOperationQueue alloc] init] 并发和串行,默认是并发执行的

// 自定义NSOperation
-(void)customOperation {
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 创建操作
SPOperation *operationOne = [[SPOperation alloc] init];
SPOperation *operationTwo = [[SPOperation alloc] init];
// 添加操作到队列中
[queue addOperation:operationOne];
[queue addOperation:operationTwo]; // You can call this method explicitly if you want to execute your operations manually. However, it is a programmer error to call this method on an operation object that is already in an operation queue or to queue the operation after calling this method. Once you add an operation object to a queue, the queue assumes all responsibility for it.
// 有队列管理,不要手动调用 start
// [operationOne start]
} //NSBlockOperation
- (void)block {
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 封装操作
NSBlockOperation *operationOne = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operationOne--%@",[NSThread currentThread]);
}];
NSBlockOperation *operationTwo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operationTwo--%@",[NSThread currentThread]); }];
[operationTwo addExecutionBlock:^{
NSLog(@"operationThree--%@",[NSThread currentThread]);
}]; // 添加操作到队列中
[queue addOperation:operationOne];
[queue addOperation:operationTwo]; // 开发直接使用该方法即可,简单方便
[queue addOperationWithBlock:^{
NSLog(@"operationFour--%@",[NSThread currentThread]);
}];
} // NSInvocationOperation
- (void)invocation {
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 封装操作
NSInvocationOperation *operationOne = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(downloadOne) object:nil];
NSInvocationOperation *operationTwo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(downloadTwo) object:nil]; // 把封装好的操作添加到队列中
[queue addOperation:operationOne];
[queue addOperation:operationTwo];
}

2.5.3 NSOperation其它用法

—> 设置最大并发数

NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// 设置最大并发数, 该属性需要在任务添加到队列中之前进行设置
// 该属性控制队列是串行执行还是并发执行, 1 串行 >1 并行
// 系统的最大并发数有个默认的值,为-1,如果该属性设置为0,那么不会执行任何任务
queue.maxConcurrentOperationCount = 2;

—> 暂停和恢复以及取消

//设置暂停和恢复
//暂停表示不继续执行队列中的下一个任务,暂停操作是可以恢复的
if (self.queue.isSuspended) {
self.queue.suspended = NO;
}else {
self.queue.suspended = YES;
} // 取消队列里面的所有操作
// 取消之后,当前正在执行的操作的下一个操作将不再执行,而且永远都不在执行,就像后面的所有任务都从队列里面移除了一样
// 取消操作是不可以恢复的
[self.queue cancelAllOperations]; // 自定义NSOperation取消操作
-(void)main {
//耗时操作
for (int i = 0; i<1000; i++) {
NSLog(@"任务1-%d--%@",i,[NSThread currentThread]);
}
NSLog(@"+++++++++++++++++++++++++++++++++");
//苹果官方建议,每当执行完一次耗时操作之后,就查看一下当前队列是否为取消状态,如果是,那么就直接退出(为了提高程序的性能)
if (self.isCancelled) {
return;
}
}

2.5.4 NSOperation实现线程间通信

—> 开子线程下载图片

NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:@"http://h.hiphotos.baidu.com/zhidao/pic/item/6a63f6246b600c3320b14bb3184c510fd8f9a185.jpg"];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
// 主线程刷新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = image;
}];
}];

—> 操作依赖(operationThree 依赖operationOne和operationTwo)

NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSBlockOperation *operationOne = [NSBlockOperation blockOperationWithBlock:^{
// Task One
}]; NSBlockOperation *operationTwo = [NSBlockOperation blockOperationWithBlock:^{
// Task Two
}]; NSBlockOperation *operationThree = [NSBlockOperation blockOperationWithBlock:^{
// Task Three
}]; // 操作依赖
[operationThree addDependency:operationOne];
[operationThree addDependency:operationTwo]; // 添加操作到队列中执行
[queue addOperation:operationOne];
[queue addOperation:operationTwo];
[queue addOperation:operationThree];

补充

使用create函数创建的并发队列和全局并发队列的主要区别:

  1. 全局并发队列在整个应用程序中本身是默认存在的,并且对应有高优先级、默认优先级、低优先级和后台优先级一共四个并发队列,我们只是选择其中的一个直接拿来用。而create函数是从头开始去创建一个队列。
  2. 在iOS6.0之前,在GCD中凡是使用了带create和retain的函数在最后都需要做一次release操作。而主队列和全局并发队列不需要我们手动release。当然了,在iOS6.0之后GCD已经被纳入到了ARC的内存管理范畴中,即便是使用retain或者create函数创建的对象也不再需要开发人员手动释放,我们像对待普通OC对象一样对待GCD就可以。
  3. 在使用栅栏函数的时候,苹果官方明确规定栅栏函数只有在和使用create函数自己的创建的并发队列一起使用的时候才有效(没有给出具体原因)
  4. 其它区别涉及到XNU内核的系统级线程编程,不一一列举。
  5. 给出一些参考资料(可以自行研究):

GCDAPI

Libdispatch版本源码

多线程在iOS开发中的应用的更多相关文章

  1. 在iOS开发中使用FMDB

    在iOS开发中使用FMDB 前言 SQLite (http://www.sqlite.org/docs.html) 是一个轻量级的关系数据库.iOS SDK 很早就支持了 SQLite,在使用时,只需 ...

  2. IOS开发中单例模式使用详解

    第一.基本概念 单例模式是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例类的特殊类.通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问. 第二.在IOS中使用单例模式的情 ...

  3. iOS开发中文件的上传和下载功能的基本实现-备用

    感谢大神分享 这篇文章主要介绍了iOS开发中文件的上传和下载功能的基本实现,并且下载方面讲到了大文件的多线程断点下载,需要的朋友可以参考下 文件的上传 说明:文件上传使用的时POST请求,通常把要上传 ...

  4. 【转】在iOS开发中使用FMDB

    本文转载自:唐巧的博客 在iOS开发中使用FMDB APR 22ND, 2012 前言 SQLite (http://www.sqlite.org/docs.html) 是一个轻量级的关系数据库.iO ...

  5. iOS开发中各种关键字的区别

    1.一些概念 1.浅Copy:指针的复制,只是多了一个指向这块内存的指针,共用一块内存. 深Copy:内存的复制,两块内存是完全不同的, 也就是两个对象指针分别指向不同的内存,互不干涉. 2.atom ...

  6. 总结iOS开发中的断点续传那些事儿

    前言 断点续传概述 断点续传就是从文件赏赐中断的地方重新开始下载或者上传数据,而不是从头文件开始.当下载大文件的时候,如果没有实现断点续传功能,那么每次出现异常或者用户主动的暂停,都会从头下载,这样很 ...

  7. iOS开发中静态库之".framework静态库"的制作及使用篇

    iOS开发中静态库之".framework静态库"的制作及使用篇 .framework静态库支持OC和swift .a静态库如何制作可参照上一篇: iOS开发中静态库之" ...

  8. iOS开发中静态库制作 之.a静态库制作及使用篇

    iOS开发中静态库之".a静态库"的制作及使用篇 一.库的简介 1.什么是库? 库是程序代码的集合,是共享程序代码的一种方式 2.库的类型? 根据源代码的公开情况,库可以分为2种类 ...

  9. ios开发中的小技巧

    在这里总结一些iOS开发中的小技巧,能大大方便我们的开发,持续更新. UITableView的Group样式下顶部空白处理 //分组列表头部空白处理 UIView *view = [[UIViewal ...

随机推荐

  1. codeforce A. Design Tutorial: Learn from Math

    题意:将一个数拆成两个合数的和, 输出这两个数!(这道题做的真是TMD水啊)开始的时候不知道composite numbers是啥意思,看了3遍才看懂.... 看懂之后又想用素数筛选法来做,后来决定单 ...

  2. 初探KMP算法

            数据结构上老师也没讲这个,平常ACM比赛时我也没怎么理解,只是背会了代码--前天在博客园上看见了一篇介绍KMP的,不经意间就勾起了我的回忆,写下来吧,记得更牢. 一.理论准备      ...

  3. 分享一下我封装iOS自定义控件的体会,附上三个好用的控件Demo <时间选择器&多行输入框&日期选择器>

    前段时间有小伙伴问到我:"这样的控件该怎么做呢?",我感觉是个比较简单的控件,可能对于入行不久的同志思路没有很清晰吧.趁着最近工作不忙,就来这里分享一下我封装自定义控件的几点体会吧 ...

  4. linux内核分析课程笔记(二)

    运行一个精简的操作系统内核 存储程序计算机是几乎所有计算机的基础逻辑框架. 堆栈是计算机中非常基础的东西,在最早计算机没有高级语言时,在高级语言出现之前,我们没有函数的概念.但高级语言出现后有了函数调 ...

  5. czxt

    实验三 进程调度模拟程序 1.    目的和要求 1.1.           实验目的 用高级语言完成一个进程调度程序,以加深对进程的概念及进程调度算法的理解. 1.2.           实验要 ...

  6. Python基础:新式类的属性访问

    一.概述 二.准备工作 1.讨论对象 2.名词解释 三.实例绑定的属性访问 1.获取属性 一般规则 参考源码 示例验证 2.设置属性 一般规则 参考源码 示例验证 3.删除属性 一般规则 参考源码 示 ...

  7. MySQL数据库 安装图解

    下面的是MySQL安装的图解,用的可执行文件:下载地址:http://www.jinhusns.com/Products/Download/?type=xcj相关下载 mysql安装向导启动,按“Ne ...

  8. ASP.NET 取得 Uri 各项属性值

    Uri uri = new Uri("http://www.yoercn.com/aboutus/idea.html");string Host = uri.Host;       ...

  9. hadoop pipes wordcount compile

    http://devel.cs.stolaf.edu/projects/bw/wiki.real/index.php/Hadoop_Reference,_January_2011 http://guo ...

  10. wrong requestcode when using startActivityForResult

    You are calling startActivityForResult() from your Fragment. When you do this, the requestCode is ch ...