×

iOS 多线程NSThread,GCD,NSOperation

96
Lee坚武
2016.01.19 00:05* 字数 3727

单例模式例子:

https://github.com/XiaoRuiZuo/Singleton

多线程:
多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。

3.多线程

多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。

原理:

同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)

多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)

如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象

注意:多线程并发,并不是cpu在同一时刻同时执行多个任务,只是CPU调度足够快,造成的假象。

优点:

能适当提高程序的执行效率

能适当提高资源利用率(CPU、内存利用率)

缺点:

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

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

二、iOS开发中的应用

1.主线程

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

作用:

显示\刷新UI界面

处理UI事件(比如点击事件、滚动事件、拖拽事件等)

注意:

刷新UI必须放在主线程

别将比较耗时的操作放到主线程中

耗时操作会卡住主线程,严重影响UI的流畅度

2.实现方案

NSThread

一个NSThread对象控制执行的线程。当你想在自己的执行线程的Objective-C的方法运行使用这个类。当你需要执行一个漫长的任务线程是特别有用的,但不希望它阻止应用程序的其余部分的执行。特别是,您可以使用线程来避免阻塞应用程序,它处理的用户界面和事件相关的操作的主线。线程也可以用来将一个大的工作分成几个较小的作业,这可能会导致在多核计算机性能的提高。

一、创建和启动线程

// 1.创建线程NSThread*thread = [[NSThreadalloc] initWithTarget:selfselector:@selector(run) object:nil];// 2.启动线程[thread start];// 线程一启动,就会在线程thread中执行self的run方法// 创建线程后自动启动线程[NSThreaddetachNewThreadSelector:@selector(run) toTarget:selfwithObject:nil];// 隐式创建并启动线程[selfperformSelectorInBackground:@selector(run) withObject:nil];上述2种创建线程方式的优缺点- 优点:简单快捷- 缺点:无法对线程进行更详细的设置

二、主线程相关用法

// 返回主线程+ (NSThread*)mainThread;// 是否为主线程(类方法)+ (BOOL)isMainThread;// 是否为主线程(对象方法)- (BOOL)isMainThread;

三、其他用法

// 线程通知NSDidBecomeSingleThreadedNotificationNSThreadWillExitNotificationNSWillBecomeMultiThreadedNotification// 获得当前线程NSThread*current = [NSThreadcurrentThread];// 线程的名字- (void)setName:(NSString*)n; - (NSString*)name;

线程的状态

线程的状态

// 进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态- (void)start;// 阻塞(暂停)线程->进入阻塞状态+ (void)sleepUntilDate:(NSDate*)date;+ (void)sleepForTimeInterval:(NSTimeInterval)ti;// 强制停止线程-> 进入死亡状态+ (void)exit;注意:一旦线程停止(死亡)了,就不能再次开启任务

多线程的安全隐患

一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源。

当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。

解决办法:(互斥锁)

// 注意:锁定1份代码只用1把锁,用多把锁是无效的@synchronized(锁对象) {// 需要锁定的代码  }

优点与缺点

能有效防止因多线程抢夺资源造成的数据安全问题

需要消耗大量的CPU资源

atomic与nonatomic

OC在定义属性时有nonatomic和atomic两种选择

@property(nonatomic,copy)NSString*name;@property(atomic,copy)NSString*name;

atomic:原子属性,为setter方法加锁(默认就是atomic)

线程安全,需要消耗大量的资源

nonatomic:非原子属性,不会为setter方法加锁

非线程安全,适合内存小的移动设备

开发建议

所有属性都声明为nonatomic

尽量避免多线程抢夺同一块资源

尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力

线程间通信

在1个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信

例如在子线程下载图片,在主线程刷新UI显示图片。

线程间通信常用方法

// 1.在主线程上执行操作- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;// 2.在指定线程上执行操作- (void)performSelector:(SEL)aSelector onThread:(NSThread*)thr withObject:(id)arg waitUnti


GCD

全称是Grand Central Dispatch,“伟大的中枢调度器”

GCD是苹果公司为多核的并行运算提出的解决方案

纯C语言,提供了非常多强大的函数

优势

GCD会自动利用更多的CPU内核(比如双核、四核)

GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)

只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

基本概念

任务和队列

GCD中有2个核心概念

1.任务:执行什么操作

2.队列:用来存放任务

// 1.定制任务:确定想做的事情// 2.将任务添加到队列中:GCD会自动将队列中的任务取出,放到对应的线程中执行。Tips:任务的取出遵循队列的FIFO原则:先进先出,后进后出

任务

一、执行任务

-queue:队列 - block:任务// 1.用同步的方式执行任务dispatch_sync(dispatch_queue_tqueue,dispatch_block_tblock);// 2.用异步的方式执行任务dispatch_async(dispatch_queue_tqueue,dispatch_block_tblock);// 3.GCD中还有个用来执行任务的函数// 在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行dispatch_barrier_async(dispatch_queue_tqueue,dispatch_block_tblock);

注意:

同步:只能在当前线程中执行任务,不具备开启新线程的能力

异步:可以在新的线程中执行任务,具备开启新线程的能力

队列

一、并发队列(Concurrent Dispatch Queue)

可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)

并发功能只有在异步(dispatch_async)函数下才有效

// 1.使用dispatch_queue_create函数创建队列dispatch_queue_tdispatch_queue_create(constchar*label,// 队列名称dispatch_queue_attr_tattr);// 队列的类型// 2.创建并发队列dispatch_queue_tqueue= dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);// 3.使用dispatch_get_global_queue函数获得全局的并发队列dispatch_queue_tdispatch_get_global_queue(dispatch_queue_priority_tpriority,unsignedlongflags);// dispatch_queue_priority_t priority(队列的优先级 )// unsigned long flags( 此参数暂时无用,用0即可 )// 4.获得全局并发队列dispatch_queue_tqueue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);// 5.全局并发队列的优先级#defineDISPATCH_QUEUE_PRIORITY_HIGH2// 高#defineDISPATCH_QUEUE_PRIORITY_DEFAULT0// 默认(中)#defineDISPATCH_QUEUE_PRIORITY_LOW (-2)// 低#defineDISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN// 后台

二、串行队列(Serial Dispatch Queue)

让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

// 1.使用dispatch_queue_create函数创建串行队列// 创建串行队列(队列类型传递NULL或者DISPATCH_QUEUE_SERIAL)dispatch_queue_tqueue= dispatch_queue_create("queue",NULL);// 2.使用dispatch_get_main_queue()获得主队列dispatch_queue_tqueue= dispatch_get_main_queue();注意:主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行。

三、各种队列的执行效果

特别注意:使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列 (线程卡死)

新手易混淆

有4个术语比较容易混淆:同步、异步、并发、串行

1.同步和异步主要影响:能不能开启新的线程

同步:只是在当前线程中执行任务,不具备开启新线程的能力

异步:可以在新的线程中执行任务,具备开启新线程的能力

2.并发和串行主要影响:任务的执行方式

并发:多个任务并发(同时)执行

串行:一个任务执行完毕后,再执行下一个任务

GCD运用

一、线程间通信

从子线程回到主线程dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{// 执行耗时的异步操作...dispatch_async(dispatch_get_main_queue(), ^{// 回到主线程,执行UI刷新操作});});

二、延时执行

dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(2.0* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

// 2秒后异步执行这里的代码...

});

三、一次性代码

// 使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次staticdispatch_once_tonceToken;dispatch_once(&onceToken, ^{// 只执行1次的代码(这里面默认是线程安全的)});

四、快速迭代

// 使用dispatch_apply函数能进行快速迭代遍历dispatch_apply(10, dispatch_get_global_queue(0,0), ^(size_tindex){// 执行10次代码,index顺序不确定});

五、队列组

// 分别异步执行2个耗时的操作、2个异步操作都执行完毕后,再回到主线程执行操作dispatch_group_tgroup=  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(), ^{// 等前面的异步操作都执行完毕后,回到主线程...});

单例模式

作用:

可以保证在程序运行过程,一个类只有一个实例,而且该实例易于供外界访问。从而方便地控制了实例个数,并节约系统资源

使用场合

在整个应用程序中,共享一份资源(这份资源只需要创建初始化1次)

实现过程:

1.重写实现:

创建一个需要单例模式的文件

// 1.在.m中保留一个全局的static的实例staticid_instance;// 2.重写allocWithZone:方法,在这里创建唯一的实例(注意线程安全)+ (instancetype)allocWithZone:(struct_NSZone*)zone{staticdispatch_once_tonceToken;dispatch_once(&onceToken, ^{        _instance = [superallocWithZone:zone];    });return_instance;}// 3.提供1个类方法让外界访问唯一的实例+ (instancetype)sharedInstance{staticdispatch_once_tonceToken;dispatch_once(&onceToken, ^{        _instance = [[selfalloc] init];    });return_instance;}// 4.实现copyWithZone:方法- (id)copyWithZone:(struct_NSZone*)zone{return_instance;}

2.宏实现:

// .h文件#define SingletonH(name) + (instancetype)shared##name;// .m文件#define SingletonM(name)\static id _instance;\\+ (instancetype)allocWithZone:(struct _NSZone *)zone\{\static dispatch_once_t onceToken;\dispatch_once(&onceToken, ^{\_instance =[super allocWithZone:zone];\});\return _instance;\}\\+ (instancetype)shared##name\{\static dispatch_once_t onceToken;\dispatch_once(&onceToken, ^{\_instance =[[self alloc]init];\});\return _instance;\}\\- (id)copyWithZone:(NSZone *)zone\{\return _instance;\}


NSOperation

NSOperation是苹果封装的一套多线程的东西,不像GCD是纯C语言的,这个是OC的。但相比较之下GCD会更快一些,但本质上NSOPeration是多GDC的封装。

NSOperation相对于GCD:

NSOperation拥有更多的函数可用

NSOperationQueue中,可以建立各个NSOperation之间的依赖关系。

NSOperationQueue支持KVO。可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)

GCD 只支持FIFO 的队列,而NSOperationQueue可以调整队列的执行顺序

NSOperation剖析

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

使用NSOperation子类的方式有3种

NSInvocationOperation

NSBlockOperation

自定义子类继承NSOperation,实现内部相应的方法

NSOperationQueue

1.NSOperation可以调用start方法来执行任务,但默认是同步执行的

2.如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作

// 添加操作到NSOperationQueue中- (void)addOperation:(NSOperation*)op;- (void)addOperationWithBlock:(void(^)(void))block;

NSOperation的使用

配合使用NSOperation和NSOperationQueue就能实现多线程编程

具体步骤:

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

2.将NSOperation对象添加到NSOperationQueue中

3.系统会自动将NSOperationQueue中的NSOperation取出来放到一条新线程中执行

NSInvocationOperation子类

// 1.创建NSInvocationOperation对象- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;// 2.调用start方法开始执行操作- (void)start;// 一旦执行操作,就会调用target的sel方法

注意:

默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作。

只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作

NSBlockOperation子类

// 1.创建NSBlockOperation对象+ (id)blockOperationWithBlock:(void(^)(void))block;// 2.通过addExecutionBlock:方法添加更多的操作- (void)addExecutionBlock:(void(^)(void))block;

注意:

只要NSBlockOperation封装的操作数 > 1,就会异步执行操作

自定义NSOperation

// 1.创建对象继承NSOperation,重写- (void)main;// 在里面实现想执行的任务

注意:

自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)

经常通过

-(BOOL)isCancelled

方法检测操作是否被取消,对取消做出响应。

NSOperation方法

一、最大并发数

可以通过对最大并发数设置,控制程序中线程的数量

// 1.最大并发数的相关方法- (NSInteger)maxConcurrentOperationCount;- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;

二、取消、暂停、恢复

// 1.取消队列的所有操作- (void)cancelAllOperations;// 2.取消单个操作- (void)cancel// 暂停- (void)setSuspended:(BOOL)b;// YES代表暂停队列,NO代表恢复队列// 恢复队列- (BOOL)isSuspended;

三、依赖

NSOperation之间可以设置依赖来保证执行顺序

// 1.比如一定要让操作A执行完后,才能执行操作B,可以这么写[operationB addDependency:operationA];// 操作B依赖于操作A注意:可以在不同queue的NSOperation之间创建依赖关系

四、操作的监听

// 1.可以监听一个操作的执行完毕- (void(^)(void))completionBlock;- (void)setCompletionBlock:(void(^)(void))block;

日记本
Web note ad 1