iOS多线程总结

demo:MultiThreadingPractice

进程:系统正在运行的一个应用程序,没打开一个app系统就开启一条进程,每个进程是相互独立的,运行在专用且受保护的内存空间;

线程:进程需要开启线程处理任务,一条进程至少有一个线程且可以开启多条线程;

线程的串行:每条线程同一时间只能执行一条任务,线程按照顺序一个个执行任务;

多线程:进程开启多条线程,并行执行不同任务;提高工作效率。

多线程原理:同一时间CPU只能处理一条线程,当有多条线程执行任务时,CUP会在各个线程之间快速切换,处理线程任务;所以当线程非常多时,会大量消耗CPU资源,且每条线程执行的频次很低。

多线程优点:能适当提高效率,适当提高资源利用率;
多线程缺点:创建线程会消耗内存资源,创建一条线程大约需要90毫秒;创建线程太多会降低程序性能,大量消耗CPU;

主线程:每条进程会默认开启一条线程显示和刷新UI、处理UI事件,默认开启的这条线程称为主线程;耗时操作一般不放在主线程中,防止UI出现卡顿;

iOS多线程实现方案:

thread.png

NSThread

一个NSThread对象就代表一条线程;

  • 创建、启动线程:
NSThread *thread = [[NSThread alloc]initWithTarget:self  selector:@selector(sel)  object:nil];
[thread start];
  • 主线程相关方法:
+(NSThread*)mainThread;// 获得主线程
-(BOOL)isMainThread;// 是否为主线程
+(BOOL)isMainThread;// 是否为主线程
  • 获取当前线程:
NSThread*current = [NSThread currentThread];
  • 线程的名字:
-(void)setName:(NSString*)n;
-(NSString*)name;

其他创建线程的方式:

  • 创建线程后自动启动线程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
  • 隐式创建并启动线程
[self performSelectorInBackground:@selector(run) withObject:nil];

控制线程状态:

  • 启动线程
-(void)start;

进入就绪状态->运行状态。当线程任务执行完毕,自动进入死亡状态

  • 阻塞(暂停)线程
+(void)sleepUntilDate:(NSDate*)date;
+(void)sleepForTimeInterval:(NSTimeInterval)time;
  • 强制停止线程
+(void)exit;//进入死亡状态

注意:一旦线程停止(死亡)了,就不能再次开启任务

多线程的安全隐患

当多个线程访问同一块资源时,很可能会发生数据错乱和数据安全问题;

安全隐患解决方案

一般使用线程同步技术解决多个线程访问同一资源的问题,可查看线程同步方案了解具体实现。

线程间的通信

线程间通信的体现:

  • 1个线程传递数据给另1个线程
  • 在1个线程中执行完特定任务后,转到另1个线程继续执行任务

线程间通信常用方法

-(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
-(void)performSelector:(SEL)aSelector onThread:(NSThread*)the withObject:(id)arg waitUntilDone:(BOOL)wait;

GCD

GCD:全称是Grand Central Dispatch,纯C语言,提供了非常多强大的函数。

GCD的优势

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

GCD中有2个核心概念

  • 任务:执行什么操作
  • 队列:用来存放任务

GCD的使用的2个步骤

1、定制任务,确定想做的事情
2、将任务添加到队列中

GCD会自动将队列中的任务取出,放到对应的线程中执行
任务的取出遵循队列的FIFO原则:先进先出,后进后出

执行任务

GCD中有2个用来执行任务的常用函数

  • 同步的方式执行任务
/*
queue:队列
block:任务
*/
dispatch_sync(dispatch_queue_t queue,dispatch_block_t block);
  • 用异步的方式执行任务
dispatch_async(dispatch_queue_t queue,dispatch_block_t block);

同步和异步的区别

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

GCD的队列可以分为2大类型

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

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

同步、异步、并发、串行的区别:

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

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

  • 并发:允许多个任务并发(同时)执行
  • 串行:一个任务执行完毕后,再执行下一个任务

使用dispatch_queue_create函数创建队列

dispatch queue的名称推荐使用应用程序ID逆序全域名Queue的名称。该名称会出现在程序崩溃时生成的crashLog中方便调试,也更简单易懂。

/*
label: 队列名称
attr: 队列的类型
*/
dispatch_queue_create(constchar *label, dispatch_queue_attr_t attr);

创建并发队列

dispatch_queue_t queue = dispatch_queue_create("com.GCDPractice.concurrentQueue",DISPATCH_QUEUE_CONCURRENT);

GCD默认已经提供了全局的并发队列,供整个应用使用,可以无需手动创建
使用dispatch_get_global_queue函数获得全局的并发队列

/*
priority:  队列的优先级
flags:  此参数暂时无用,用0即可
*/
dispatch_queue_t dispatch_get_global_queue(dispatch_queue_priority_t priority, 
unsignedlong flags); 

获得全局并发队列

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

全局并发队列的优先级

DISPATCH_QUEUE_PRIORITY_HIGH 2// 高
DISPATCH_QUEUE_PRIORITY_DEFAULT 0// 默认(中)
DISPATCH_QUEUE_PRIORITY_LOW (-2)// 低
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN// 后台

GCD中获得串行有2种途径

  • 使用dispatch_queue_create函数创建串行队列,队列类型传递NULL或者DISPATCH_QUEUE_SERIAL
dispatch_queue_t queue = dispatch_queue_create("com.GCDPractice.serialQueue",NULL);
  • 使用主队列(跟主线程相关联的队列)
    主队列是GCD自带的一种特殊的串行队列
    放在主队列中的任务,都会放到主线程中执行
    使用dispatch_get_main_queue()获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();

同步异步和队列的配合使用效果如下图

GCD.png

注意:
使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列,造成死锁

线程间通信

从子线程回到主线程

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

1、调用NSObject的方法

[self performSelector:@selector(run) withObject:nil afterDelay:2.0];//2秒后再调用self的run方法

2、使用GCD函数

dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(2.0* NSEC_PER_SEC)),dispatch_get_main_queue(),^{
      //2秒后执行这里的代码...
});

3、使用NSTimer

 [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:NO];
  • 使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{
   //只执行1次的代码(这里面默认是线程安全的)
});
  • 使用dispatch_apply函数能进行快速迭代遍历
dispatch_apply(10,dispatch_get_global_queue(0,0),^(size_t index){
     //执行10次代码,index顺序不确定
});
队列组

有这么1种需求
首先:分别异步执行2个耗时的操作
其次:等2个异步操作都执行完毕后,再回到主线程执行操作
如果想要快速高效地实现上述需求,可以考虑用队列组

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(),^{
     //等前面的异步操作都执行完毕后,回到主线程...
});
GCD中用来实现同步的函数
  • dispatch_barrier_async
dispatch_barrier_async(dispatch_queue_t queue,dispatch_block_t block);

在dispatch_barrier_async前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行。
注意:这个queue不能是全局的并发队列

  • dispatch_Set_Target_Queue
dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t _Nullable queue);

这个函数可以设置queue的执行优先级也可以设置queue的执行层级。对于不可并行执行且追加到多个Serial Dispatch Queue中的任务,如果使用dispatch_set_target_queue函数将这多个Serial Dispatch Queue的优先级设置跟某一个Serial Dispatch Queue一样,即可防止并行处理。具体代码参见demo。

  • dispatch_semaphore_t
dispatch_semaphore_create(long value);

dispatch_semaphore_t通过计数控制任务的执行,当计数为0时等待,计数为1或大于1时,执行任务。
当计数值大于或等于1时,对计数进行减一并从dispatch_semaphore_wait函数返回。当dispatch_semaphore_wait返回0时,可安全的执行排他控制的处理。该处理结束时可通过dispatch_semaphore_signal函数将dispatch_semaphore_t的计数值加一;

  • dispatch_suspend、dispatch_resume

当追加大量处理到dispatch queue时,在处理过程中,希望不执行已经追加的处理,可以挂起dispatch queue,可以执行时再恢复。

dispatch_suspend(dispatch_object_t object);
dispatch_resume(dispatch_object_t object);

NSOperation

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

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

  • 先将需要执行的操作封装到一个NSOperation对象中
  • 然后将NSOperation对象添加到NSOperationQueue中
  • 系统会自动将NSOperationQueue中的NSOperation取出来
  • 将取出的NSOperation封装的操作放到一条新线程中执行

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

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

NSInvocationOperation

  • 创建NSInvocationOperation对象
-(id)initWithTarget:(id)targetselector:(SEL)selobject:(id)arg;
//调用start方法开始执行操作
-(void)start;

一旦执行操作,就会调用target的sel方法

注意:
默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作
只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作

NSBlockOperation

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

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

最大并发数
maxConcurrentOperationCount:同时执行的任务数
比如,同时开3个线程执行3个任务,并发数就是3

  • 队列设置最大并发数的相关方法
-(NSInteger)maxConcurrentOperationCount;
-(void)setMaxConcurrentOperationCount:(NSInteger)cnt;
  • 取消队列的所有操作
-(void)cancelAllOperations;

提示:
也可以调用NSOperation的-(void)cancel方法取消单个操作

暂停和恢复队列

-(void)setSuspended:(BOOL)b;// YES代表暂停队列,NO代表恢复队列
-(BOOL)isSuspended;
  • NSOperation之间可以设置依赖来保证执行顺序
    比如一定要让操作A执行完后,才能执行操作B,可以这么写
[operationB addDependency:operationA];// 操作B依赖于操作A

可以在不同queue的NSOperation之间创建依赖关系,但不能相互依赖;

  • 可以监听一个操作的执行完毕
-(void(^)(void))completionBlock;
-(void)setCompletionBlock:(void(^)(void))block;
  • 自定义NSOperation
    自定义NSOperation的步骤很简单
    重写-(void)main方法,在里面实现想执行的任务

  • 重写-(void)main方法的注意点
    自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)
    经常通过-(BOOL)isCancelled方法检测操作是否被取消,对取消做出响应

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,423评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,147评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,019评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,443评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,535评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,798评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,941评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,704评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,152评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,494评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,629评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,295评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,901评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,978评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,333评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,499评论 2 348

推荐阅读更多精彩内容