内功心法-多线程的基本使用

一 ) 为什么使用多线程?

每个iOS应用程序都有个专门用来更新显示UI界面、处理用户的触摸事件的主线程,因此不能将其他太耗时的操作放在主线程中执行,不然会造成主线程堵塞(出现卡机现象),带来极坏的用户体验。一般的解决方案就是将那些耗时的操作放到另外一个线程中去执行,多线程编程是防止主线程堵塞,增加运行效率的最佳方法。

这里有两个概念是进程和线程。 进程就是负责程序运行的内存分配,而线程就是进程中得一个独立的执行那个路径。它是程序的执行路径,负责程序中代码的实际运行。这好比进程就是火车,线程就是火车的车厢,没有火车,车厢也跑不起来当然一个火车也不能只有一个车厢。

二)  ios 中常用的三种多线程是什么?

1  Thread: 这是相对轻量级别的,抽象级别相对来说比较低,但是需要管理线程的生命周期,同步以及加锁,这会导致一定的性能开销

2 Operations:  这个是基于OC实现的,以面向对象的方式封装了需要执行的操作,我们可以不必关心线程的管理和同步的问题。它可以开始,取消线程执行。他有两个默认的实现方法:NSInvocationOperation和NSBlockOperation。当然我们也可以自定NSOperations,只有实现里面的main方法即可

3  GCD:也是重点讲的一个,它是ios4才开始使用的,当然现在我们ios9都出来了,GCD的成熟度足可以让我们放心的使用,它是基于C实现的,性能上要相对来说好一些,而且可以用最简单的代码去实现复杂的线程问题。

三) NSThread的基本使用方法

1.动态初始化

NSThread threadOne = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];

//其中run是一个方法,我们通过方法来执行多线程中得处理

[_threadOne setName:@"one"]; //给线程初始化一个别名

[_threadOne start];//开始执行

2 静态初始化

[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; //通过这一句代码我们就可以创建出另一个线程出来并去执行

3 创建隐式线程:

[self performSelectorInBackground:@selector(run) withObject:nil];

注:这是NSObject的一个方法,他可以让一个耗时间的处理放入后台去执行,但是在swift中抛弃了,原因是苹果觉得这个方法是线程不安全的

4 获取当前的线程

NSThread *current = [NSThread currentThread];//返回的是目前的线程

5 返回主线程(刷新UI控件必须在主线程中执行)

[self performSelectorOnMainThread:@selector(main:) withObject:nil waitUntilDone:YES];

6 等待(暂停)当前的线程

[NSThread sleepForTimeInterval:3.0f] ;//3秒之后执行

或者

NSDate *date = [NSDate dateWithTimeInterval:2 sinceDate:[NSDate date]];

[NSThread sleepUntilDate:date]; 等待date完成后再去执行

注:前者是等待时间的完成,后者是等待数据的完成

四)  NSOperation使用方法:

NSOperation 实例封装了需要执行的操作和执行操作所需的数据,并且能够以并发或非并发的方式执行这个操作。NSOperation在ios4后也基于GCD实现,但是相对于GCD来说可控性更强,并且可以加入操作依赖。NSOperation提供了ready cancelled executing finished这几个状态变化,我们的开发也是必须处理自己关心的其中的状态。这些状态都是基于keypath的KVO通知决定,所以在你手动改变自己关心的状态时,请别忘了手动发送通知。这里面每个属性都是相互独立的,同时只可能有一个状态是YES。finished这个状态在操作完成后请及时设置为YES,因为NSOperationQueue所管理的队列中,只有isFinished为YES时才将其移除队列,这点在内存管理和避免死锁很关键。

1.NSInvocationOperation:

NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download:) object:nil];//红色多线程中处理的方法

[op start]; //开始

[op cancel];//取消

2  NSBlockOperation

NSBlockOperation *block = [NSBlockOperation    blockOperationWithBlock:^{

NSLog(@"%@",[NSThread currentThread]); //处理线程的block方法

}];

3 NSOperationQueue

一个NSOperation对象可以通过调用start方法来执行任务,默认是同步执行的。也可以将NSOperation添加到一个NSOperationQueue(操作队列)中去执行,而且是异步执行的。一旦NSoperation添加到NSoperationQueue中,用户就无权对NSoperation管理,都有NSoperationQueue来执行。

3.1 添加一个队列

NSoperationQueue *myQueue = [[NSOperationQueue alloc]init];

NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download:) object:nil];

[myQueue addOperation:op];

3.2 主队列(任何刷新UI的方法都必须在主队列中执行)

[[NSOperationQueue mainQueue]addOperationWithBlock:^{

//可以执行刷新UI控件的方法

}];

3.3 队列直接可以设置依赖,比如队列1需要队列2执行完毕后才能执行(addDependency)

NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{

NSLog(@"%@",[NSThread currentThread]);

}];

NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{

NSLog(@"%@",[NSThread currentThread]);

}];

[block1 addDependency:block2];

[myQueue addOperation:block1];

[myQueue addOperation:block2];

3.4可以设置最大并发的操作数量

[myQueue setMaxConcurrentOperationCount:2];

3.5 一旦添加到队列,队列就拥有了这个Operation对象并且不能被  删除,唯一能做的事情是取消。

[myQueue cancelAllOperations];

3.6获取NSOperation

myQueue.operations 这是一个数组,里面存放这添加进入这个队列的所有任务

3.7 如果你想临时暂停Operations的执行,可以使用queue的setSuspended:方法暂停queue。

[myQueue setSuspended:YES];

四)GCD

1 GCD是Grand Central Dispatch的简称,它是基于C语言的。如果使用GCD,完全由系统管理线程,我们不需要编写线程代码。只需定义想要执行的任务,然后添加到适当的调度队列(dispatch queue)。GCD会负责创建线程和调度你的任务,系统直接提供线程管理。

2 GCD的操作思想是讲操作放在队列中去执行

1  操作是用block来实现的

2 队列是先进先出的,它是负责调度任务执行所在的线程

3 GCD分为串行和并行,有自定义,主队列和全局队列三种。一个同步函数只在完成了它预定的任务后才返回。一个异步函数,刚好相反,会立即返回,预定的任务会完成但不会等它完成。因此,一个异步函数不会阻塞当前线程去执行下一个函数。如果在主队列中执行同步的话,会造成死锁的发生

3 串行和并行

串行:一次只能执行一个任务, 当前任务完成才开始出列并启动下一个任务

并行:则尽可能多地启动任务并发执行

4GCD基本使用方法

1 自定义队列

dispatch_queue_t q = dispatch_queue_create("gcdDemo1", DISPATCH_QUEUE_SERIAL); DISPATCH_QUEUE_SERIAL:串行,可以传入nil 默认是串行,"gcdDemo1”是这个线程的别名,可以传nil

dispatch_queue_t q = dispatch_queue_create("gcdDemo2", DISPATCH_QUEUE_CONCURRENT);  DISPATCH_QUEUE_CONCURRENT:并行

dispatch_async(q, ^{    注:异步执行,可以开辟多个线程去执行,无需等待

NSLog(@"%@",[NSThread currentThread]);

});

dispatch_sync(q, ^{  注:同步执行,只开辟一个线程,需要等待上一个任务的完成才能执行

NSLog(@"%@",[NSThread currentThread]);

});

2 全局队列

dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

DISPATCH_QUEUE_PRIORITY_DEFAULT:默认的,全局队列是有优先级的

DISPATCH_QUEUE_PRIORITY_HIGH      最高(优先执行)

DISPATCH_QUEUE_PRIORITY_DEFAULT    默认

DISPATCH_QUEUE_PRIORITY_LOW        最低

DISPATCH_QUEUE_PRIORITY_BACKGROUND 后台

3 主队列

dispatch_queue_t q = dispatch_get_main_queue();

每一个应用程序都有一个主线程  在ios中所有的ui刷新都再主线程中执行!这是因为苹果为了提高性能,大部分库都是线程不安全的,如果在子线程刷新控件会造成一些问题,所有所有的UI控件的刷新都由主线程上刷新

4 延迟执行

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

dispatch_after(time, dispatch_get_main_queue(), ^{

NSLog(@"我是3秒才执行的!");

});

5 dispatchGroup组队列

dispatchGroup作用:当 dispatch_group_async函数将多个任务关联到一个Dispatch Group和相应的queue中,group会并发地同时执行这些任务。而且Dispatch Group可以用来阻塞一个线程, 直到group关联的所有的任务完成执行。有时候你必须等待任务完成的结果,然后才能继续后面的处理。

dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, q, ^{

NSLog(@"1 == %@",[NSThread currentThread]);

});

dispatch_group_async(group, q, ^{

NSLog(@"2 == %@",[NSThread currentThread]);

});

dispatch_group_async(group, q, ^{

NSLog(@"3 == %@",[NSThread currentThread]);

});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{//不管前面的线程谁先执行,最后都会执行notify方法。

NSLog(@"4 == %@",[NSThread currentThread]);

});

6 dispathc_apply

dispathc_apply是dispatch_sync 和dispatch_group的关联API.它以指定的次数将指定的Block加入到指定的队列中。并等待队列中操作全部完成.

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_apply(10, globalQueue, ^(size_t index) {

NSLog(@"%zu",index);

dispatch_source_merge_data(socurce, 1);

});

7 dispatch_source_t 信号源

dispatch source是一个监视某些类型事件的对象。当这些事件发生时,它自动将一个block放入一个dispatch queue的执行例程中。书中定义。我的理解就是多线程中得KVO,它检测用户事件,它是由dispatch_source_merge_data函数来向自己发送信号,然后通过dispatch_source_set_event_handler这个函数去执行。

这是我写了一个进度条的例子

执行部分:  __weak __typeof(self)weakSelf = self;

dispatch_source_t  socurce = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());

dispatch_source_set_event_handler(socurce, ^{

[weakSelf.progressIndicator setProgress:dispatch_source_get_data(socurce)  animated:YES];

});

dispatch_resume(socurce);

监听部分:    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_apply(10, globalQueue, ^(size_t index) {

NSLog(@"%zu",index);

dispatch_source_merge_data(socurce, 1);

});

/*

dispatch_source_create(dispatch_source_type_t type,

uintptr_t handle,

unsigned long mask,

dispatch_queue_t queue);

第1个参数:要监听的事件类型

第2个参数:可以理解为句柄、索引或id,假如要监听进程,需要传入进程的ID

第3个参数:根据参数2,可以理解为描述,提供更详细的描述,让它知道具体要监听什么

第4个参数:当事件发生时,将block添加至哪个队列来执行

**/

8 dispatch_semaphore信号量

当我们在处理一系列线程的时候,当数量达到一定量,在以前我们可能会选择使用NSOperationQueue来处理并发控制,在GCD中我们需要通过dispatch_semaphore来控制它的并发数量。

dispatch_group_t group = dispatch_group_create();

dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

for (int i = 0; i < 10; i++)

{

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//如果技术器的数值大于等于1的时候进行-1操作

dispatch_group_async(group, queue, ^{

NSLog(@"----%d",i);

sleep(2);

dispatch_semaphore_signal(semaphore);//计数器+1

});

}

简单的介绍一下这一段代码,创建了一个初使值为5的semaphore,每一次for循环都会创建一个新的线程,线程结束的时候会发送一个信号,线程创建之前会信号等待,所以当同时创建了5个线程之后,for循环就会阻塞,等待有线程结束之后会增加一个信号才继续执行,如此就形成了对并发的控制,如上就是一个并发数为5的一个线程队列。

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

推荐阅读更多精彩内容

  • 一、前言 上一篇文章iOS多线程浅汇-原理篇中整理了一些有关多线程的基本概念。本篇博文介绍的是iOS中常用的几个多...
    nuclear阅读 2,043评论 6 18
  • NSThread 第一种:通过NSThread的对象方法 NSThread *thread = [[NSThrea...
    攻城狮GG阅读 784评论 0 3
  • 最近大家都在学习跟项目有关的知识,有跟项目源码学习有关的学习卡,也有跟项目有关的技术学习卡,比如:Jenkins,...
    Yvette14阅读 564评论 3 1
  • 不知怎么,这学期就是对很多事情都不感兴趣,对篮球不感兴趣,对学习也没兴趣,对什么事都觉得没有意思。 状态有些不...
    叁TROIS阅读 191评论 0 1