多线程学习笔记

1 概念

1.1 什么是多线程

1个进行中可以开发多个线程,多个线程可以“同时”执行不同的任务;可以解决程序阻塞的问题;多线程可以提高程序的执行效率

1.2 多线程执行原理

(单核CPU) 同一时间,cpu只能处理1个线程,只有1个线程在执行
多线程同时执行:是CPU快速的在多个线程之间的切换
cpu调度线程的时间足够快,就造成了多线程的“同时”执行
如果线程数非常多,cpu会在n个线程之间切换,消耗大量的cpu资源 (一般开3-6个线程)
每个线程调度的次数会降低,线程的执行效率降低

1.3 多线程优/缺点

优点:

能适当提高程序的执行效率
能适当提高资源的利用率(cpu,内存)
线程上的任务执行完成后,线程会自动销毁

缺点:

开启线程需要占用一定的内存空间(默认情况下,每一个线程都占 512KB)
如果开启大量的线程,会占用大量的内存空间,降低程序的性能
线程越多, cpu 在调用线程上的开销就越大
程序设计更加复杂,比如线程间的通信、多线程的数据共享

1.4 主线程

主线程:一个程序运行后,默认会开启1个线程,称为“主线程” 或 “UI线程”;主线程一般用来 刷新UI界面,处理UI事件(比如: 点击、滚动、拖拽等事件),主要处理与用户的交互。

主线程使用注意:别将耗时的操作放到主线程中;耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种卡的坏体验

1.5 iOS 同步/异步执行

  • 同步执行:比如dispatch_sync,这个函数会把一个 block 加入到指定的队列中,而且会一直等到执行完 block,这个函数才返回。因此在 block 执行完之前,调用 dispatch_sync 方法的线程是阻塞的。
  • 异步执行:比如dispatcy_async,这个函数会把一个 block 加入到指定的队列中,但是和同步执行不同的是,这个函数把 block 加入队列后不等 block 的执行就立刻返回了。

注意:

  • dispatch_async 和 dispatch_sync 作用是将 block 添加进指定的队列中。并根据是否为 sync 决定调用该函数的线程是否需要阻塞。
  • 这里调用该函数的线程并不一定执行 block块,任务的执行者是 GCD 分配给任务所在队列的线程
  • 结论:调用 dispatch_sync 和 dispatch_async 的线程,并不一定是 block 的执行者。

1.6 iOS 串行/并发队列

  • 串行队列:比如 dispatch_get_main_queue,这个队列中所有任务,一定按照FIFO(先进先出)执行。不仅如此,还可以保证在执行某个任务时,在它前面进入队列的所有任务都已经执行完成。对于每一个不同的串行队列,系统会为这个队列建立唯一的线程来执行代码
  • 并行队列:比如 dispatch_get_global_queue,这个队列中的任务是按照FIFO(先进先出)开始执行,注意是开始,但是它们的执行结束时间是不确定的,取决于每个任务的耗时。并发队列中的任务:GCD会动态分配多条线程来执行。具体几条线程取决于当前内存使用状况,线程池中线程数等因素。

1.7 iOS 中多线程的技术方案

image

2 pthread

2.1 pthread 方法

int pthread_create(pthread_t _Nullable * _Nonnull __restrict,const pthread_attr_t * _Nullable __restrict,void * _Nullable (* _Nonnull)(void * _Nullable),void * _Nullable __restrict);

参数:

  1. 指向线程标识符的指针,C 语言中类型的结尾通常 _t/Ref,而且不需要使用 *
  2. 用来设置线程属性
  3. 线程运行函数的起始地址
  4. 运行函数的参数

返回值:

  • 若线程创建成功,则返回0
  • 若线程创建失败,则返回出错编号

2.2 __bridge 桥接

__bridge 桥接 用于告诉编译器如何管理内存

3 NSThread

创建

方式1:NSThread *thread = [NSThread alloc] initWithTarget ….. 创建线程
[thread start]; 执行线程
方式2:[NSThread detachNewThreadSelector…….] // 创建并执行
方式3:[ self performSelectorInBackground……] // 创建并执行

线程的状态

image

4 GCD

4.1 Dispatch Queue 执行处理的等待队列

两种 Dispatch Queue:

Serial Dispatch Queue 等待现在执行中的处理
Concurrent Dispatch Queue 不等待现在执行中的处理

4.2 创建 Dispatch Queue

两种方式:

第一种:dispatch_queue_create
第二种:获取系统提供的 Dispatch Queue
dispatch_get_main_queue()
dispatch_get_global_queue(0, 0)

4.3 dispatch_set_target_queue 修改优先级

优先级:
DISPATCH_QUEUE_PRIORITY_HIGH : 高
DISPATCH_QUEUE_PRIORITY_DEFAULT : 默认
DISPATCH_QUEUE_PRIORITY_LOW : 低
DISPATCH_QUEUE_PRIORITY_BACKGROUND :后台

4.4 dispatch_after 指定时间后追加处理到 Dispatch Queue

与 dispatch_time 结合使用

    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
    dispatch_after(time, dispatch_get_main_queue(), ^{
    });
    // 合并为一条语句
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    });

4.5 Dispatch Group 线程组

用于:追加到 Dispatch Queue 中的多个处理全部结束后想执行结束处理

    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ NSLog(@"blk0"); });
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ NSLog(@"blk1"); });
    // 使用 Dispatch Group 可监视这些处理执行的结束,一旦检测到所有处理执行结束,就可将结束的处理追加到 Dispatch Queue中
    // dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"done"); });
    
    // 也可使用
    // dispatch_group_wait :仅等待全部处理执行结束,第二个参数 等待的时间(dispatch_time_t 类型 ; DISPATCH_TIME_FOREVER :永久等待)
    // 返回结果:0:全部处理执行结束。 非0:经过了指定的时间,但属于 Dispatch Group 的某个处理还在执行中
    long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

4.6 dispatch_barrier_async

使用 dispatch_barrier_async 添加的 block 会在之前添加的 block 全部运行结束之后,才在同一个线程顺序执行,从而保证对非线程安全的对象进行正确的操作!

    dispatch_async(queue, blk0_for_reading);
    dispatch_async(queue, blk1_for_reading);
    dispatch_barrier_async(queue, blk_for_writing);
    dispatch_async(queue, blk2_for_reading);
    dispatch_async(queue, blk3_for_reading);

4.7 dispatch_sync 同步

将指定的 Block “同步”追加到指定的 Dispatch Queue中。在追加 Block 结束之前, dispatch_sync 函数会一直等待。

使用场景:

例:执行 Main Dispatch Queue 时,使用另外的线程 Global Dispatch Queue 进行处理,处理结束后立即使用所得到的结果。这种情况就要使用 dispatch_sync函数。

注意事项:

dispatcy_sync 是简易版的 dispatch_group_wait ,dispatcy_sync 容易引起死锁。

死锁代码:

    // 1种:在主线程执行 该句,会造成线程死锁
    // 在主线程 中执行指定的 Block, 并等待其执行结束。而其实 在主线程中下在执行这些源代码,所以无法执行追加到 主线程 的 Block
    dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"Hello"); });
    // 2种:在主线程执行 该句,会造成线程死锁
    dispatch_async(dispatch_get_main_queue(), ^{
        dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"Hello");});
    });
    // 3种:Serial Dispatch Queue 也会引起死锁
    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.serialdispatchQueue", NULL);
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{NSLog(@"Hello"); });
    });
    // 4种:
   // dispatch_barrier_sync 函数的作用是在等待追加的处理全部执行结束后,再追加处理到 Dispatch Queue 中,此外,它还与 dispatch_sync 函数相同,会等待追加处理的t执行结束

4.8 dispatch_apple

dispatch_apple 函数是 dispatch_sync 函数和 Dispatch Group 的关联 API。 该函数按指定的次数将指定的 Block 追加到指定的 Dispatch Queue 中,并等待全部处理执行结束。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply([array count], queue, ^(size_t index) {
        NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
    });
    // 最后输出的 done
    NSLog(@"done");

4.9 dispatch_suspend / dispatch_resume

dispatch_suspend 函数 挂起指定的 Dispatch Queue
dispatch_resume 函数 恢复指定的 Dispatch Queue

4.10 Dispatch Semaphore

4.11 dispatch_once

保证在应用程序执行中只执行一次指定处理。一般用于 生成单例模式对象时使用

4.12 Dispatch I/O

5 NSOperation

5.1 NSOperation 简介

  1. 作用:配合使用 NSOperation 和 NSOperationQueue 能实现多线程编辑
  2. 具体步骤:
    (1)先将需要执行的操作封装到一个 NSOperation 对象中
    (2)然后将 NSOperation 对象添加到 NSOperationQueue 中
    (3)系统会自动将 NSOperationQueue 中的 NSOperation 取出来
    (4)将取出的 NSOperation 封装的操作放到一条新线程中执行
  3. NSOperation 的子类
  • NSInvocationOption
  • NSBlockOperation
  • 自定义子类继承 NSOperation 实现内部相应的方法
  1. NSOperationQueue
    NSOperationQueue的作⽤:NSOperation可以调⽤start⽅法来执⾏任务,但默认是同步执行的
    如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作
    添加操作到NSOperationQueue中,自动执行操作,自动开启线程

5.2 NSInvocationOperation

    // NSOperation 中 : 操作 -> 异步执行的任务;  队列 -> 全局队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"queue"];
    //将操作添加到队列,会"异步"执行 selector 方法
    // [queue addOperation:op];

    // start方法 会在当前线程执行 @selector 方法
    [op start];

5.3 NSBlockOperation

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 第 1 种
    NSBlockOperation *blockOp = [NSBlockOperation blockOperationWithBlock:^{
         NSLog(@"thread = %@", [NSThread currentThread]);
    }];
    [queue addOperation:blockOp];
    // 第 2 种 直接添加block
    [queue addOperationWithBlock:^{
        NSLog(@"thread = %@", [NSThread currentThread]);
    }];
// addExecutionBlock
    //NSBlockOperation创建时block中的任务是在主线程执行,而运用addExecutionBlock加入的任务是在子线程执行的
    NSBlockOperation *blockOp = [NSBlockOperation blockOperationWithBlock:^{
         NSLog(@"thread = %@", [NSThread currentThread]);
    }];
    [blockOp addExecutionBlock:^{
        NSLog(@"1 execution thread = %@", [NSThread currentThread]);
    }];
    [blockOp addExecutionBlock:^{
        NSLog(@"2 execution thread = %@", [NSThread currentThread]);
    }];
    [blockOp start];

线程间通讯

    NSOperationQueue *q = [[NSOperationQueue alloc] init];
    [q addOperationWithBlock:^{
        NSLog(@"耗时操作 %@", [NSThread currentThread]);
        // 主线程更新 UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"更新 UI %@", [NSThread currentThread]);
        }];
    }];

5.4 最大并发操作数

并发数:同时执行的任务数
最大并发数:同一时间最多只能执行的任务的个数
注意:如果没有设置最大并发数,那么并发数的个数是由系统内存和CPU决定。最大并发数一般以 2~3为宜。

// 设置最大并发数
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//    queue.maxConcurrentOperationCount = 2;
    [queue setMaxConcurrentOperationCount:2];

5.5 暂停 & 继续

  • 队列挂起,当前“没有完成的操作”,是包含在队列的操作数中的
  • 队列挂起,不会影响已经执行操作的执行状态
  • 队列一旦被挂起,再添加的操作不会被调度
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 暂停和恢复 YES:暂停队列 NO:恢复队列
    queue.suspended = !queue.suspended;
    // 当前状态
    BOOL b = queue.isSuspended;

5.6 取消全部操作

  • 取消队列中所有的操作
  • 不会取消正在执行中的操作
  • 不会影响队列的挂起状态
    // 1. 取消所有操作
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 取消对列中的所有操作,同样不会影响到正在执行中的操作!
    [queue cancelAllOperations];
    
    // 2. 取消单个操作
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"queue"];
    [queue addOperation:op];
    [op cancel];

5.6 操作优先级

优先级的取值: 优先级高的任务,调用的几率会更大
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8

    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"queue"];
    // 设置操作的优先级
    [op setQueuePriority:NSOperationQueuePriorityLow];

5.7 依赖关系 addDependency

   NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
       NSLog(@"登录 %@", [NSThread currentThread]);
   }];
   NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
       NSLog(@"付费 %@", [NSThread currentThread]);
   }];
   NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
       NSLog(@"下载 %@", [NSThread currentThread]);
   }];
   NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
       NSLog(@"通知用户 %@", [NSThread currentThread]);
   }];
   // 通过设置依赖来保证执行顺序
   [op2 addDependency:op1];
   [op3 addDependency:op2];
   [op4 addDependency:op3];
   // 注意不要循环依赖
   //    [op1 addDependency:op4];
   
   NSOperationQueue *queue = [[NSOperationQueue alloc] init];
   // queue 队列是否阻塞当前线程
   [queue addOperations:@[op1, op2, op3] waitUntilFinished:NO];
   [[NSOperationQueue mainQueue] addOperation:op4];
   
   NSLog(@"come here");

6 NSOperation 与 GCD 的对比

  • GCD
  1. 将 block 添加到队列(串行/并发/主队列),并且指定任务执行的函数(同步/异步)
  2. GCD 是底层的 C 语言构成的 API
  3. 在队列中执行的是由 block 构成的任务,这是一个轻量级的数据结构
  4. 要停止已经加入 queue 的 block 需要写复杂的代码
  5. 需要通过 Barrier 或者同步任务设置任务之间的依赖关系
  6. 只能设置队列的优先级
  7. 高级功能:
    一次性 once
    延迟操作 after
    调度组
  • NSOperation
  1. 核心概念:把 操作(异步) 添加到 队列(全局的并发队列)
  2. OC 框架,更加面向对象,是对 GCD 的封装
  3. Operation 作为一个对象,为我们提供了更多的选择
  4. 可以随时取消已经设定要准备执行的任务,已经执行的除外
  5. 可以跨队列设置操作的依赖关系
  6. 可以设置队列中每一个操作的优先级
  7. 高级功能:
    最大操作并发数(GCD不好做)
    继续/暂停/全部取消
    跨队列设置操作的依赖关系
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容

  • 多线程的基本知识 先补一发基础知识 什么是线程 线程,有时被称为轻量级进程(Lightweight Process...
    箪食豆羹阅读 549评论 3 8
  • 多线程种类 说句无关紧要的话,终于会用简书的样式引用了~~~本文借鉴大神的讲解,链接在此:http://www.j...
    Apple技术产品粉阅读 124评论 0 0
  • iOS多线程编程 基本知识 1. 进程(process) 进程是指在系统中正在运行的一个应用程序,就是一段程序的执...
    陵无山阅读 6,043评论 1 14
  • 很久前的总结,今天贴出来。适合看了就用,很少讲解,纯粹用法。 目录 Dispatch Queue dispatch...
    和女神经常玩阅读 646评论 0 3
  • 我没有漂亮女生飘逸柔顺的长发,没有漂亮女生的柳叶弯眉,没有漂亮女生樱桃似的小嘴,也没有漂亮女生细柳般的身材。总...
    蔡雯静阅读 437评论 1 4