IOS知识总结——多线程

OC中的多线程

OC中多线程根据封装程度可以分为三个层次:NSThread、GCD和NSOperation,另外由于OC兼容C语言,因此仍然可以使用C语言的POSIX接口来实现多线程,只需引入相应的头文件:#include<pthread.h>。

NSThread

NSThread是封装程度最小最轻量级的,使用更灵活,但要手动管理线程的生命周期、线程同步和线程枷锁等,开销较大;
NSThread的基本使用比较简单,可以动态创建初始化NSThread对象,对其进行设置然后启动,也可以通过NSThread的静态方法快速创建并启动新线程;此外NSObject基类对象还提供了隐式快速创建NSThread线程的performSelector系列类别扩转工具方法;NSThread还提供了一些静态工具接口来控制当前线程以及获取当前线程的一些信息。

/*   1、是否开启了多线程   */
BOOL isMultiThreaded = [NSThread isMultiThreaded];
/*   2、获取当前线程   */
NSThread *currentThread = [NSThread currentThread];
/*   3、获取主线程   */
NSThread *mainThread = [NSThread mainThread];
/*   4、睡眠当前线程   */
[NSThread sleepForTimeInterval:5];
/*   5、退出当前线程吗,注意不要在主线程调用,防止主线程被kill掉   */
//   [NSThread exit];

/**  NSThread 线程对象基本创建,target为入口函数所在的对象,selector为线程入口函数    **/
/*   1 线程实例对象创建与设置   */
NSThread *newThread = [NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
/*   设置线程优先级threadPriority(0~1.0),即将被抛弃,将使用qualityOfService代替   */
newThread.threadPriority = 1.0;
newThread.qualityOfService = NSQualityOfServiceUserInteractive;
/*  开启线程  */
[newThread start];
/*   2 静态方法快速创建并开启新线程   */
[NSThread detachNewThreadSelector(run) toTarget:self withObject:nil];

/**  NSObject基类隐式创建线程的一些静态工具方法  **/
/*   1 在当前线程上执行方法,延迟2s  */
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
/* 2 在指定线程上执行方法,不等待当前线程 */
[self performSelector:@selector(run) onThread:newThread withObject:nil waitUntilDone:NO];
/* 3 后台异步执行函数 */
[self performSelectorInBackground:@selector(run) withObject:nil];
/* 4 在主线程上执行函数 */
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];

GCD

GCD对线程操作进行了封装,加入了很多新的特性,内部进行了效率优化,提供了简洁的C语言接口,使用更加简单高效,也是苹果推荐的方式。
对于GCD多线程编程的理解需要结合实例和实践去体会、总结。网上有一篇GCD的详细介绍,地址为:https://github.com/nixzhu/dev-blog/blob/master/2014-04-19-grand-central-dispatch-in-depth-part-1.md,教程分为两部分,此处为第一部分地址。这里进行总结,整理出如下内容:

同步dispatch_sync与异步dispatch_async任务派发
串行队列与并行队列diapatch_queue_t
dispatch_once_t只执行一次
diapatch_after延后执行

两个关键概念

串行与并行(Serial和Concurrent):
这个概念在创建操作队列的时候有宏定义参数,用来指定创建的是串行队列还是并行队列。

串行指的是队列内任务一个接一个的执行,任务之间要依次等待不可重合,且添加的任务按照先进先出FIFO的顺序执行,但并不是指这就是单线程,只是同一个串行队列内的任务需要依次等待排队执行,避免出现竞态条件,但仍然可以创建多个串行队列并行的执行任务,也就是说,串行队列内饰串行的,串行队列之间仍然是可以并行的,同一个串行队列内的任务的执行顺序是确定的(FIFO),且可以创建多个串行队列;

并行指的是同一个队列先后添加的多个任务可以同时并列执行,任务之间不会相互等待,且这些任务的执行顺序执行过程不可预测。

同步和异步任务派发(Synchronous和Asynchornous):GCD多线程编程时经常会使用dispatch_async和dispatch_sync函数往指定队列中添加任务块,区别就是同步和异步。同步指的是阻塞当前线程,要等添加的耗时任务块block完成后,函数才能返回,后面的代码才可以继续执行。如果在主线程上,则会发生阻塞,用户会感觉应用不响应,这是要避免的。而有时需要使用同步任务的原因是想保证先后添加的任务要按照编写的逻辑顺序依次执行;异步指的是将任务添加到队列后函数立刻返回,后面的代码不用等待添加的任务完成即可继续执行。

dispatch_sync与dispatch_async
通过下面的代码比较异步和同步任务的区别:

/* 1. 提交异步任务 */
NSLog(@"开始提交异步任务:");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    /* 耗时任务... */
    [NSThread sleepForTimeInterval:10];
});
NSLog(@"异步任务提交成功!");
    
/* 2. 提交同步任务 */
NSLog(@"开始提交同步任务:");
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    /* 耗时任务... */
    [NSThread sleepForTimeInterval:10];
});
NSLog(@"同步任务提交成功!");

打印结果:

2017-02-28 16:01:44.643 SingleView[19100:708069] 开始提交异步任务:
2017-02-28 16:01:44.643 SingleView[19100:708069] 异步任务提交成功!
2017-02-28 16:01:44.644 SingleView[19100:708069] 开始提交同步任务:
2017-02-28 16:01:54.715 SingleView[19100:708069] 同步任务提交成功!

通过打印结果的时间可以看出,异步任务提交后立即就执行下一步打印提交成功了,不会阻碍当前线程,提交的任务会在后台去执行;而提交同步任务要等到提交的后台任务结束后才可以继续执行当前线程的下一步。此处在主线上添加的同步任务就会阻塞主线程,导致后面界面的显示要延迟,影响用户体验。

dispatch_queue_t 队列的创建方法很简单,需要提供两个参数,一个是标记自定义队列的唯一标识,另一个是指定串行队列还是并行队列的宏参数:

/* 创建一个并发队列 */
dispatch_queue_t concurrent_queue = dispatch_queue_create("com.yiger.gcd.concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
/* 创建一个串行队列 */
dispatch_queue_t serial_queue = dispatch_queue_create("com.yiger.gcd.serial_queue", DISPATCH_QUEUE_SERIAL);

另外GCD还提供了几个常用的全局队列一级主队列,获取方法如下:

// 获取主队列(在主线程上执行)
dispatch_queue_t main_queue = dispatch_get_main_queue();
// 获取不同优先级的全局队列(优先级从高到低)
dispatch_queue_t queue_high = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_queue_t queue_default = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t queue_low = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t queue_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

dispatch_once_t这个函数控制指定代码只会被执行一次,常用来实现单例模式,这里以单例模式实现的代码为例展示用法,其中的是立法语句只会被执行一次:

+ (instancetype)shareInstance {
    static LocalNotificationManager *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[LocalNotificationManager alloc] init];
    });
    return instance;
}

dispatch_after_t 通过该函数可以让要提交的任务在指定时间后执行:

    // 定义延迟时间:3s
    dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
    dispatch_after(delay, dispatch_get_main_queue(), ^{
        // 要执行的任务...
    });

dispatch_group_t 组调度可以实现等待一组操作都完成后执行后续操作,典型的例子是大图片的下载,例如可以将大图片分成几块同时下载,等各部分都下载完后再后续将图片拼接起来,提高下载的效率。
使用方法如下:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /*操作1 */ });
dispatch_group_async(group, queue, ^{ /*操作2 */ });
dispatch_group_async(group, queue, ^{ /*操作3 */ }); 
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 任务全部完成后续操作... });

主线程
对于UI的更新,必须要在主线程上执行才会及时生效,当前代码不在主线程时,需要将UI更新的代码单独同步到主线程,同步的方法有三种,可以使用NSThread类的performSelectorOnMainThread方法或者NSOperationQueue类的mainQueue主队列来进行同步,推荐使用GCD方法:

dispatch_async(dispatch_get_main_queue(), ^{
        // UI更新代码...
    });

NSOpertation

NSOperation是基于GCD的一个抽象基类,将线程封装成要执行的操作,不需要管理线程的生命周期和同步,但比GCD可控性更强,例如可以加入操作依赖(addDependency)、设置操作队列最大可并发执行的操作个数(setMaxConcurrentOperationCount)、取消操作(cancel)等。NSOperation作为抽象基类不具备封装我们的操作的功能,需要使用两个它的实体子类:NSBlockOperation和NSInvocationOperation,或者继承NSOperation自定义子类。

NSBlockOperation和NSInvocationOperarion用法的主要区别是:前者执行代码块,后者执行指定的方法,相对来说前者更加灵活易用。NSOperation操作配置完成后便可调用start函数在当前线程执行,如果要异步执行避免阻塞当前线程则可以加入NSOpearionQueue中异步执行。简单用法如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    /* NSInvocationOperation初始化 */
    NSInvocationOperation *invoOpertion = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
    [invoOpertion start];
    
    /* NSBlockOperation初始化 */
    NSBlockOperation *blkOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperation");
    }];
    [blkOperation start];
}

- (void)run {
    NSLog(@"NSInvocationOperation");
}

另外NSBlockOperation可以后续继续添加block执行块,操作启动后会在不同线程并发的执行这些执行块:

/* NSBlockOperation初始化 */
NSBlockOperation *blkOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"NSBlockOperationA");
}];
[blkOperation addExecutionBlock:^{
    NSLog(@"NSBlockOperationB");
}];
[blkOperation addExecutionBlock:^{
    NSLog(@"NSBlockOperationC");
}];
[blkOperation start];
2017-04-04 11:27:02.805 SingleView[12008:3666657] NSBlockOperationB
2017-04-04 11:27:02.805 SingleView[12008:3666742] NSBlockOperationC
2017-04-04 11:27:02.805 SingleView[12008:3666745] NSBlockOperationA

另外说了NSOperation的可控性比GCD要强,其中一个非常重要的特性是可以设置各操作之间的依赖,即强行规定操作A要在操作B完成之后才能开始执行,成为操作A依赖于操作B:

/* 获取主队列(主线程) */
NSOperationQueue *queue = [NSOperationQueue mainQueue];
/* 创建a、b、c操作 */
NSOperation *c = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"OperationC");
}];
NSOperation *a = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"OperationA");
}];
NSOperation *b = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"OperationB");
}];
/* 添加操作依赖,c依赖于a和b,这样c一定会在a和b完成后才执行,即顺序为:A、B、C */
[c addDependency:a];
[c addDependency:b];
/* 添加操作a、b、c到操作队列queue(特意将c在a和b之前添加) */
[queue addOperation:c];
[queue addOperation:a];
[queue addOperation:b];
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,245评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,749评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,960评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,575评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,668评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,670评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,664评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,422评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,864评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,178评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,340评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,015评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,646评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,265评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,494评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,261评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,206评论 2 352

推荐阅读更多精彩内容

  • OC中的多线程 OC中多线程根据封装程度可以分为三个层次:NSThread、GCD和NSOperation,另外由...
    那月无痕阅读 158评论 0 0
  • OC中的多线程 OC中多线程根据封装程度可以分为三个层次:NSThread、GCD和NSOperation,另外由...
    爱笑的猫mi阅读 15,480评论 2 34
  • OC中的多线程 OC中多线程根据封装程度可以分为三个层次:NSThread、GCD和NSOperation,另外由...
    旅行者_sz阅读 276评论 0 1
  • 多年来,计算机的最大性能主要受限于它的中心微处理器的速度。然而由于个别处理器已经开始达到它的瓶颈限制,芯片制造商开...
    Zhen斌iOS阅读 628评论 0 0
  • iOS多线程编程 基本知识 1. 进程(process) 进程是指在系统中正在运行的一个应用程序,就是一段程序的执...
    陵无山阅读 6,039评论 1 14