多线程的基本知识
先补一发基础知识
什么是线程
线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。
线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。
线程与进程的关系
线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
线程与进程的区别
进程
进程是资源分配的基本单位。每个进程拥有自己独立的进程控制块(PCB,Process Control Block),不同的进程拥有不同的虚拟地址空间。
进程间通信,常用方法为:
- 管道(pipe)
- 消息队列(message queue)
- 信号(sinal)
- 信号量(semophore)
- 共用内存(shared memory)
- 套接字(socket)
线程
线程是被系统独立调度和分派的基本单位。线程只由相关堆栈(系统栈或用户栈)寄存器和线程控制表(TCB,Thread Control Table)组成,同一进程内的不同线程共享同一地址空间。
线程间通信,常用方法为:
- 锁(lock):互斥锁、条件变量、读写锁等
- 信号量(semophore)
- 信号(signal):用于线程同步
线程的状态
线程的状态iOS中的多线程技术
pthread
简介
POSIX线程(英语:POSIX Threads,常被缩写为Pthreads)是POSIX的线程标准,定义了创建和操纵线程的一套API。
实现POSIX 线程标准的库常被称作Pthreads,一般用于Unix-like POSIX 系统,如Linux、Solaris。但是Microsoft Windows上的实现也存在,例如直接使用Windows API实现的第三方库pthreads-w32;而利用Windows的SFU/SUA子系统,则可以使用微软提供的一部分原生POSIX API。
使用
-
包含头文件
#import <pthread.h>
-
创建线程,并且开启线程执行任务
// 创建线程——定义一个pthread_t类型变量 pthread_t thread; // 开启线程——执行任务 pthread_create(&thread, NULL, run, NULL); // 新线程调用方法,里边为需要执行的任务 void * run(void *param) { NSLog(@"%@", [NSThread currentThread]); return NULL; }
NSThread
简介
NSThread
是苹果官方提供的,基于c语言封装,使用起来比pthread
更加面向对象,简单易用,可以直接操作线程对象。不过也需要需要我们自己管理线程的生命周期(主要是创建),我们在开发的过程中偶尔使用NSThread
。比如我们会经常调用[NSThread currentThread]
来显示当前的进程信息。
使用
启动
-
创建线程,并手动启动
// 创建 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil]; // 启动 [thread start];
-
创建线程并启动
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
-
使用NSObject的方法创建并启动
[self performSelectorInBackground:@selector(run:) withObject:nil];
其他方法
//取消线程
- (void)cancel;
//启动线程
- (void)start;
//强制停止线程
+ (void)exit;
//判断某个线程的状态的属性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;
//设置和获取线程名字
-(void)setName:(NSString *)n;
-(NSString *)name;
//获取当前线程信息
+ (NSThread *)currentThread;
//获取主线程信息
+ (NSThread *)mainThread;
//使当前线程暂停(阻塞)一段时间,或者暂停到某个时刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;
GCD
简介
Grand Central Dispatch (GCD) 是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并行任务。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用。
使用
启动
-
创建队列
使用
dispatch_queue_create
来创建队列- 第一个参数表示队列的唯一标识符,用于DEBUG,可为空
- 第二个参数用来识别是串行队列还是并发队列。
DISPATCH_QUEUE_SERIAL
表示串行队列,DISPATCH_QUEUE_CONCURRENT
表示并发队列。- 这里的
同步(sync)
和异步(async)
的主要区别在于会不会阻塞当前线程,直到Block
中的任务执行完毕
- 这里的
使用dispatch_get_xxx来获取队列
-
dispatch_get_global_queue
会获取一个全局队列 -
dispatch_get_main_queue
会获取主队列,也就是UI队列// 串行队列的创建方法 dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL); // 并发队列的创建方法 dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
-
创建任务
// 同步执行任务创建方法 dispatch_sync(queue, ^{ NSLog(@"%@",[NSThread currentThread]); // 这里放任务代码 }); // 异步执行任务创建方法 dispatch_async(queue, ^{ NSLog(@"%@",[NSThread currentThread]); // 这里放任务代码 });
管理
-
栅栏方法
dispatch_barrier_async
第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于栅栏一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到
dispatch_barrier_async
方法在两个操作组间形成栅栏。dispatch_async(queue, ^{...}); dispatch_async(queue, ^{...}); // 当上面两个异步任务执行完后才执行下面的异步任务 dispatch_barrier_async(queue, ^{...}); dispatch_async(queue, ^{...}); dispatch_async(queue, ^{...});
-
延时执行方法
dispatch_after
当我们需要延迟执行一段代码时,就需要用到GCD的
dispatch_after
方法。dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 2秒后异步执行这里的代码 // ... });
-
一次性代码(只执行一次)
dispatch_once
在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了GCD的
dispatch_once
方法。使用dispatch_once
函数能保证某段代码在程序运行过程中只被执行1次。static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // 只执行1次的代码(这里面默认是线程安全的) });
-
快速迭代方法
dispatch_apply
GCD给我们提供了快速迭代的方法
dispatch_apply
,使我们可以同时遍历。比如说遍历0~5这6个数字,for循环的做法是每次取出一个元素,逐个遍历。dispatch_apply
可以同时遍历多个数字。dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply(6, queue, ^(size_t index) { NSLog(@"%zd------%@",index, [NSThread currentThread]); });
-
队列组
dispatch_group
分别异步执行2个耗时操作,然后当2个耗时操作都执行完毕后再回到主线程执行操作。这时候我们可以用到GCD的队列组。
- 我们可以先把任务放到队列中,然后将队列放入队列组中。
- 调用队列组的
dispatch_group_notify
回到主线程执行操作。
dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 执行1个耗时的异步操作 }); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 执行1个耗时的异步操作 }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // 等前面的异步操作都执行完毕后,回到主线程... });
NSOperation
简介
NSOperation
是苹果提供给我们的一套多线程解决方案。实际上NSOperation
是基于GCD
更高一层的封装,但是比GCD
更简单易用、代码可读性也更高。
使用
启动
-
创建任务
不使用
NSOperationQueue
,单独使用NSOperation
的情况下,系统同步执行操作-
使用子类
NSInvocationOperation
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil]; [op start]; - (void)run { // do sth }
-
使用子类
NSBlockOperation
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ // do sth }]; // 添加额外的任务(在子线程执行) [op addExecutionBlock:^{ // do sth }]; [op addExecutionBlock:^{ // do sth }]; [op addExecutionBlock:^{ // do sth }]; [op start];
-
定义继承自
NSOperation
的子类,通过实现内部相应的方法来封装任务- 定义一个继承自
NSOperation
的子类,重写main
方法
- 定义一个继承自
-
-
创建队列
-
主队列
-
凡是添加到主队列中的任务(
NSOperation
),都会放到主线程中执行NSOperationQueue *queue = [NSOperationQueue mainQueue];
-
-
其他队列(非主队列)
-
添加到这种队列中的任务(
NSOperation
),就会自动放到子线程中执行 -
同时包含了:串行、并发功能
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
-
-
-
把任务加入队列
-
(void)addOperation:(NSOperation *)operation;
// 1.创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 2. 创建操作 // 创建NSInvocationOperation NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil]; // 创建NSBlockOperation NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ // do sth }]; // 3. 添加操作到队列中:addOperation: [queue addOperation:op1]; // [op1 start] [queue addOperation:op2]; // [op2 start]
-
(void)addOperationWithBlock:(void (^)(void))block;
// 1. 创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 2. 添加操作到队列中:addOperationWithBlock: [queue addOperationWithBlock:^{ for (int i = 0; i < 2; ++i) { NSLog(@"-----%@", [NSThread currentThread]); } }];
-
管理
-
控制并发和串行
通过
NSOperationQueue
最大并发数:maxConcurrentOperationCount
,实现串行和并发的控制-
maxConcurrentOperationCount
默认情况下为-1,表示不进行限制,默认为并发执行。 - 当
maxConcurrentOperationCount
为1时,进行串行执行。 - 当
maxConcurrentOperationCount
大于1时,进行并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整。
// 创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 设置最大并发操作数 // queue.maxConcurrentOperationCount = 2; queue.maxConcurrentOperationCount = 1; // 就变成了串行队列
-
-
操作依赖
NSOperation
和NSOperationQueue
最吸引人的地方是它能添加操作之间的依赖关系。比如说有A、B两个操作,其中A执行完操作,B才能执行操作,那么就需要让B依赖于A。NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"1-----%@", [NSThread currentThread]); }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"2-----%@", [NSThread currentThread]); }]; [op2 addDependency:op1]; // 让op2 依赖于 op1,则先执行op1,在执行op2 [queue addOperation:op1]; [queue addOperation:op2];
-
其他方法
// 提供的方法,可取消单个操作 - (void)cancel; NSOperation // NSOperationQueue提供的方法,可以取消队列的所有操作 - (void)cancelAllOperations; // 可设置任务的暂停和恢复,YES代表暂停队列,NO代表恢复队列 - (void)setSuspended:(BOOL)bool; // 判断暂停状态 - (BOOL)isSuspended;
- 这里的暂停和取消并不代表可以将当前的操作立即取消,而是当当前的操作执行完毕之后不再执行新的操作。
- 暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。
对比
- pthread
- 优点
- 接近底层
- 缺点
- 难于操作,需要自己管理线程的生命周期
- 优点
- NSThread
- 优点
- 比
pthread
更加的面向对象 - 轻量级最低,相对简单
- 比
- 缺点
- 需要自己管理线程的生命周期,如生命周期、线程同步、睡眠等
- 优点
- GCD
- 优点
- 可用于多核的并行运算
- 自动利用更多的CPU内核(比如双核、四核)
- 自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
- 缺点
- 不可取消
- 优点
- NSOperation
- 优点
- 比GCD简单易用,代码可读性更高
- 自带线程周期管理,操作上可更注重自己逻辑
- 能添加操作之间的依赖关系
- 可以指定最大并发数
- 可取消
- 缺点
- 没有GCD简洁
- 优点
网友评论