iOS开发探索多线程GCD队列示例详解

目录
  • 引言
  • 进程与线程
    • 1.进程的定义
    • 2.线程的定义
    • 3、 进程和线程的关系
    • 4、 多线程
    • 5、 时间片
    • 6、 线程池
  • GCD
    • 1、任务
    • 2、队列
    • 3、死锁
  • 总结

引言

在iOS开发过程中,绕不开网络请求、下载图片之类的耗时操作,这些操作放在主线程中处理会造成卡顿现象,所以我们都是放在子线程进行处理,处理完成后再返回到主线程进行展示。

多线程贯穿了我们整个的开发过程,iOS的多线程操作有NSThread、GCD、NSOperation,其中我们最常用的就是GCD。

进程与线程

在了解GCD之前我们先来了解一下进程和线程,及他们的联系与区别

1.进程的定义

  • 进程是指在系统中正在运行的一个应用程序
  • 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内
  • 进程间通信一般使用URL Scheme、UIPasteboard、Keychain、UIActivityViewController等

2.线程的定义

  • 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行
  • 进程想要执行任务,必须得有线程,进程至少要有一条线程
  • 程序启动会默认开启一条线程,这条线程被成为主线程
  • 线程之间的通信一般使用performSelector

3、 进程和线程的关系

  • 1.线程是进程的执行单元,进程的所有任务都在线程中执行
  • 2.线程是 CPU 分配资源和调度的最小单位
  • 3.手机中一个程序对应一个进程,一个进程中可有多个线程,但至少要有一条线程
  • 4.同一个进程内的线程共享进程资源

4、 多线程

同一时间,CPU只能处理1条线程,只有1条线程在执行。多线程并发执行,其实是CPU快速地在多条线程之间调度(切换)。如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象

如果线程非常非常多,CPU会在N多线程之间调度,消耗大量的CPU资源,每条线程被调度执行的频次会降低(线程的执行效率降低)

多线程的优点:

  • 1.能适当提高程序的执行效率;
  • 2.能适当提高资源利用率(CPU、内存利用率) 3.线程上的任务执行完成后,线程会自动销毁

多线程的缺点:

  • 1.开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能,开启线程大概需要90微秒的时间
  • 2.线程越多,每个线程被调度的次数就越低,线程的执行效率就越低,CPU在调度线程上的开销越大
  • 3.程序设计更加复杂,比如线程之间的通信、多线程的数据共享

多线程的生命周期是:新建 - 就绪 - 运行 - 阻塞 - 死亡

  • 新建:实例化线程对象
  • 就绪:向线程对象发送start消息,线程对象被加入可调度线程池等待CPU调度。
  • 运行:CPU 负责调度可调度线程池中线程的执行。线程执行完成之前,状态可能会在就绪和运行之间来回切换。就绪和运行之间的状态变化由CPU负责,程序员不能干预。
  • 阻塞:当满足某个预定条件时,可以使用休眠或锁,阻塞线程执行。sleepForTimeInterval(休眠指定时长),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥锁)。
  • 死亡:正常死亡,线程执行完毕。非正常死亡,当满足某个条件后,在线程内部中止执行/在主线程中止线程对象

5、 时间片

时间片:CPU在多个任务直接进行快速的切换,这个时间间隔就是时间片
设备并发执行的数量是有限的,使用[NSProcessInfo processInfo].activeProcessorCount可以查看当前设备能够支持线程的最大并发数量,比如说最大并发数是8,代表8核cpu,如果同时开启了10个线程,则会有CPU通过时间片轮转的方式让某一个或者某两个线程分别执行一段时间。

6、 线程池

GCD在内部维护了一个线程池,目的是为了复用线程,需要开启线程时,其会先在线程池中查询已开辟的空闲线程缓存,达到节省内存空间和时间的目的。

  • 核心线程是否都在执行任务 - 没有 - 创建新的工作线程去执行
  • 线程池工作队列是否饱和 - 没有 - 将任务存储在工作队列
  • 线程池的线程都处于执行状态 - 没有 - 安排线程去执行
  • 交给饱和策略去处理 GCD的线程池中缓存64条线程,就是同时可以有64条线程正在执行,最大并发执行的线程根据CPU决定。

GCD

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

GCD的优势:

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

1、任务

就是执行操作的意思,也就是在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行任务有两种方式:同步执行(sync)和异步执行(async)

  • 同步(Sync) :同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行,即会阻塞线程。只能在当前线程中执行任务(是当前线程,不一定是主线程),不具备开启新线程的能力。
  • 异步(Async) :线程会立即返回,无需等待就会继续执行下面的任务,不阻塞当前线程。可以在新的线程中执行任务,具备开启新线程的能力(并不一定开启新线程)。如果不是添加到主队列上,异步会在子线程中执行任务

2、队列

队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。队列的作用就是存储任务,和线程没有任何关系。每读取一个任务,则从队列中释放一个任务
在 GCD 中有两种队列:串行队列和并发队列。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。

  • 串行队列(Serial Dispatch Queue) :
    同一时间内,队列中只能执行一个任务,只有当前的任务执行完成之后,才能执行下一个任务。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)。主队列是主线程上的一个串行队列,是系统自动为我们创建的
  • 并发队列(Concurrent Dispatch Queue) :
    同时允许多个任务并发执行。(可以开启多个线程,并且同时执行任务)。并发队列的并发功能只有在异步(dispatch_async)函数下才有效

3、死锁

在串行队列中添加任务的block中含有另一个向同一队列添加的同步任务就会发生死锁,因为同步任务立即执行,队列需要遵守FIFO(先进先出)的原则,串行队列需要等待前一个任务执行结束才会执行下一个任务,导致互相等待造成死锁。

接下来我们从源码中了解一下GCD的串行队列和并发队列都做了什么,有什么区别。

dispatch_queue_t main = dispatch_get_main_queue(); //主队列
dispatch_queue_t global = dispatch_get_global_queue(0, 0); //全局队列
dispatch_queue_t serial = dispatch_queue_create("WT", DISPATCH_QUEUE_SERIAL); // 串行队列
dispatch_queue_t concurrent = dispatch_queue_create("WT", DISPATCH_QUEUE_CONCURRENT); //并发队列
// 主队列源码
dispatch_get_main_queue(void) {
    return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}
struct dispatch_queue_static_s _dispatch_main_q = {
    DISPATCH_GLOBAL_OBJECT_HEADER(queue_main),
    #if !DISPATCH_USE_RESOLVERS
    .do_targetq = _dispatch_get_default_queue(true),
    #endif
    .dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |
    DISPATCH_QUEUE_ROLE_BASE_ANON,
    .dq_label = "com.apple.main-thread",
    .dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),
    .dq_serialnum = 1,
};
// 全局队列源码
dispatch_get_global_queue(intptr_t priority, uintptr_t flags) {
    dispatch_qos_t qos = _dispatch_qos_from_queue_priority(priority);
    ……
    return _dispatch_get_root_queue(qos, flags & DISPATCH_QUEUE_OVERCOMMIT);
}
#define DISPATCH_QUEUE_WIDTH_FULL 0x1000ull
#define DISPATCH_QUEUE_WIDTH_POOL (DISPATCH_QUEUE_WIDTH_FULL - 1)
struct dispatch_queue_global_s _dispatch_root_queues[] = {
    #define _DISPATCH_ROOT_QUEUE_IDX(n, flags) \
    ((flags & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) ? \
    DISPATCH_ROOT_QUEUE_IDX_##n##_QOS_OVERCOMMIT : \
    DISPATCH_ROOT_QUEUE_IDX_##n##_QOS)
    #define _DISPATCH_ROOT_QUEUE_ENTRY(n, flags, ...) \
    [_DISPATCH_ROOT_QUEUE_IDX(n, flags)] = { \
    DISPATCH_GLOBAL_OBJECT_HEADER(queue_global), \
    .dq_state = DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE, \
    .do_ctxt = _dispatch_root_queue_ctxt(_DISPATCH_ROOT_QUEUE_IDX(n, flags)), \
    .dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL), \
    .dq_priority = flags | ((flags & DISPATCH_PRIORITY_FLAG_FALLBACK) ? \
    _dispatch_priority_make_fallback(DISPATCH_QOS_##n) : \
    _dispatch_priority_make(DISPATCH_QOS_##n, 0)), \
    __VA_ARGS__ \
    }
    _DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, 0,
        .dq_label = "com.apple.root.maintenance-qos",
        .dq_serialnum = 4,
    ),
    ...省略部分...
    _DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
        .dq_label = "com.apple.root.user-interactive-qos.overcommit",
        .dq_serialnum = 15,
    ),
};
// 串行队列源码
#define DISPATCH_QUEUE_SERIAL NULL
// 并发队列源码
#define DISPATCH_QUEUE_CONCURRENT DISPATCH_GLOBAL_OBJECT(dispatch_queue_attr_t,  _dispatch_queue_attr_concurrent)
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr) {
    return _dispatch_lane_create_with_target(label, attr, DISPATCH_TARGET_QUEUE_DEFAULT, true);
}
#if OS_OBJECT_USE_OBJC
#define DISPATCH_GLOBAL_OBJECT(type, object) ((OS_OBJECT_BRIDGE type)&(object))
#define DISPATCH_QUEUE_WIDTH_MAX  (DISPATCH_QUEUE_WIDTH_FULL - 2)
static dispatch_queue_t _dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa, dispatch_queue_t tq, bool legacy) {
    // 串行队列dqai为{}
    dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
    ...省略部分 - 根据dqai规范化参数...
    dispatch_lane_t dq = _dispatch_object_alloc(vtable, sizeof(struct dispatch_lane_s));
    // 初始化队列 并发队列 DISPATCH_QUEUE_WIDTH_MAX  串行队列 1
    _dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ? DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER | (dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));
    ...省略部分...
    return _dispatch_trace_queue_create(dq)._dq;
}
_dispatch_queue_attr_to_info(dispatch_queue_attr_t dqa) {
    dispatch_queue_attr_info_t dqai = { };
    // 串行队列直接返回 {}
    if (!dqa) return dqai; //
    #if DISPATCH_VARIANT_STATIC
        if (dqa == &_dispatch_queue_attr_concurrent) { // 并发队列
            dqai.dqai_concurrent = true;
            return dqai;
        }
    #endif
    ...一系列操作...
    return dqai;
}
static inline dispatch_queue_class_t _dispatch_queue_init(dispatch_queue_class_t dqu, dispatch_queue_flags_t dqf, uint16_t width, uint64_t initial_state_bits) {
    uint64_t dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(width);
    dispatch_queue_t dq = dqu._dq;
    dispatch_assert((initial_state_bits & ~(DISPATCH_QUEUE_ROLE_MASK |
    DISPATCH_QUEUE_INACTIVE)) == 0);
    if (initial_state_bits & DISPATCH_QUEUE_INACTIVE) {
        dq->do_ref_cnt += 2; // rdar://8181908 see _dispatch_lane_resume
        if (dx_metatype(dq) == _DISPATCH_SOURCE_TYPE) {
            dq->do_ref_cnt++; // released when DSF_DELETED is set
        }
    }
    dq_state |= initial_state_bits;
    dq->do_next = DISPATCH_OBJECT_LISTLESS;
    dqf |= DQF_WIDTH(width); // 串行队列DQF_WIDTH(1) -- 主队列  -- 串行队列
    os_atomic_store2o(dq, dq_atomic_flags, dqf, relaxed);
    dq->dq_state = dq_state;
    dq->dq_serialnum =
    os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed);
    return dqu;
}

我们可以看到初始化的时候,主队列及串行队列初始化队列都是DQF_WIDTH(1),全局并发队列DQF_WIDTH(0x1000ull - 1 = 15),并发队列DQF_WIDTH(0x1000ull - 2 = 14);

主队列的number = 1,全局队列number = 4-15,其他number也可以在Dispatch Source/init.c文件里查找到。

总结

串行队列就类似单行道,并发队列相当于多车道,虽然都是FIFO的数据结构,但是串行队列只能往一个队列中添加任务,一定会按照放入队列的顺序进行顺序执行;

并发队列可以往多个队列中添加任务,等待线程执行队列中的任务,线程的调度队列的情况和任务的复杂度决定了任务的执行顺序。

以上就是iOS开发探索多线程GCD队列示例详解的详细内容,更多关于iOS开发多线程GCD队列的资料请关注我们其它相关文章!

(0)

相关推荐

  • iOS开发探索多线程GCD常用函数

    目录 正文 单例 栅栏函数 调度组 dispatch_group_t 信号量 dispatch_semaphore_t dispatch_source 总结 正文 前篇文章我们了解了GCD的任务的原理,接下来我们在探索一下GCD中我们开发常用的函数 单例 下面我们从源码中看一下我们创建单例的时候使用的dispatch_once,都做了什么,是通过什么操作保证全局唯一的 void dispatch_once(dispatch_once_t *val, dispatch_block_t block)

  • IOS开发之多线程NSThiread GCD NSOperation Runloop

    IOS中的进程和线程 通长来说一个app就是一个进程 ios开发中较少的运用进程间的通信(XPC),绝大多数使用线程. 在ios开发中,为了保证流畅性以及线程安全,所有与UI相关的操作都应该放在主线程,所以有时候主线程也叫UI线程. 影响UI体验,耗时时间较长的操作,尽量放到非主线程中.比如网络请求以及和本地的IO操作. 在IOS开发中有关于多线程的知识点主要包括:NSThread.GCD.NSOperation和Runloop NSThread NSthread就是一个线程,它的底层是对pth

  • iOS开发探索多线程GCD任务示例详解

    目录 引言 同步任务 死锁 异步任务 总结 引言 在上一篇文章中,我们探寻了队列是怎么创建的,串行队列和并发队列之间的区别,接下来我们在探寻一下GCD的另一个核心 - 任务 同步任务 void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block); 我们先通过lldb查看其堆栈信息,分别查看其正常运行和死锁状态的信息 我们再通过源码查询其实现 #define _dispatch_Block_

  • iOS 多线程总结之GCD的使用详解

    进程与线程 进程就是一个应用程序在处理机上的一次执行过程,它是一个动态的概念,而线程是进程中的一部分,进程包含多个线程在运行. 线程是指进程内的一个执行单元,也是进程内的可调度实体. 与进程的区别: (1)地址空间:线程是进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间; (2)资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源 (3)线程是处理器调度的基本单位,但进程不是. (4)二者均可并发执行. GCD 1.什么是GCD? 全

  • ios开发中的容错处理示例详解

    前言 后台服务器返回给客户端的值有时会是null,有时会是"<null>",直接赋值并进行后续操作有时会导致崩溃. 之前的处理方式都是尽量让后台服务器返回数据时不返回null或者是"<null>",但是他们还是时不时返回这些数据,所以app时不时就会出现闪退现象.一出现这种问题,调试后发现是他们返回null或者是"null"的数据类型,因为是线上的问题,所以让他们直接在后台将出现问题的字段进行处理就好了.久而久之,发现这种

  • IOS开发基础之二维数组详解

    IOS开发基础之二维数组详解 首先我们知道OC中是没有二维数组的,二维数组是通过一位数组的嵌套实现的,但是别忘了我们有字面量,实际上可以和C/C++类似的简洁地创建和使用二维数组.这里总结了创建二维数组的两种方法以及数组的访问方式. 通过字面量创建和使用二维数组(推荐) // 1.字面量创建二维数组并访问(推荐) NSArray *array2d = @[ @[@11,@12,@13], @[@21,@22,@23], @[@31,@32,@33] ]; // 字面量访问方式(推荐) NSLog

  • IOS 开发中画扇形图实例详解

    IOS 开发中画扇形图实例详解 昨天在做项目中,遇到一个需要显示扇形图的功能,网上搜了一下,发现code4app里面也没有找到我想要的那种类似的效果,没办法了,只能自己学习一下如何画了. 首先我们需要了解一个uiview的方法 -(void)drawRect:(CGRect)rect 我们知道了这个方法,就可以在自定义UIView的子类的- (void)drawRect:(CGRect)rect里面绘图了,关于drawrect的调用周期,网上也是一找一大堆,等下我会整理一下,转载一篇供你们参考.

  • iOS开发系列--通知与消息机制详解

    概述 在多数移动应用中任何时候都只能有一个应用程序处于活跃状态,如果其他应用此刻发生了一些用户感兴趣的那么通过通知机制就可以告诉用户此时发生的事情.iOS中通知机制又叫消息机制,其包括两类:一类是本地通知:另一类是推送通知,也叫远程通知.两种通知在iOS中的表现一致,可以通过横幅或者弹出提醒两种形式告诉用户,并且点击通知可以会打开应用程序,但是实现原理却完全不同.今天就和大家一块去看一下如何在iOS中实现这两种机制,并且在文章后面会补充通知中心的内容避免初学者对两种概念的混淆. 本地通知 本地通

  • iOS开发系列--地图与定位源代码详解

    概览 现在很多社交.电商.团购应用都引入了地图和定位功能,似乎地图功能不再是地图应用和导航应用所特有的.的确,有了地图和定位功能确实让我们的生活更加丰富多彩,极大的改变了我们的生活方式.例如你到了一个陌生的地方想要查找附近的酒店.超市等就可以打开软件搜索周边;类似的,还有很多团购软件可以根据你所在的位置自动为你推荐某些商品.总之,目前地图和定位功能已经大量引入到应用开发中.今天就和大家一起看一下iOS如何进行地图和定位开发. 定位 地图 定位 要实现地图.导航功能,往往需要先熟悉定位功能,在iO

  • Android多线程断点续传下载示例详解

    一.概述 在上一篇博文<Android多线程下载示例>中,我们讲解了如何实现Android的多线程下载功能,通过将整个文件分成多个数据块,开启多个线程,让每个线程分别下载一个相应的数据块来实现多线程下载的功能.多线程下载中,可以将下载这个耗时的操作放在子线程中执行,即不阻塞主线程,又符合Android开发的设计规范. 但是当下载的过程当中突然出现手机卡死,或者网络中断,手机电量不足关机的现象,这时,当手机可以正常使用后,如果重新下载文件,似乎不太符合大多数用户的心理期望,那如何实现当手机可以正

  • iOS中捕获日志与异常示例详解

    前言 在平时自己调试的时候,可以直接连接电脑,直接在窗口中查看结果.但是在测试人员测试,或者灰度测试的时候,怎么才能拿到日志呢?最先想到的肯定是输出到本地文件,然后在需要的时候进行上传. 分享一段之前找到的方法,下面的代码提供了两个主要功能: – 把日志输出到文件中 – 捕捉异常信息 [解析都写在注释中了] 示例代码 - (void)redirectNSLogToDocumentFolder { //如果已经连接Xcode调试则不输出到文件 //该函数用于检测输出 (STDOUT_FILENO)

  • IOS 开发之应用唤起实现原理详解

    一.什么是iOS应用唤起 IOS中的应用唤起用来实现以下功能:在浏览器中可以通过某些方式打开IOS手机本地的app,如果该app没有安装可以跳转到该应用对应的App Store的下载页. 二.App store下载页连接 App store中某个应用的下载页连接形如:https://itunes.apple.com/us/app/id399608199.在PC端浏览器打开该连接会跳转到应用详情页的PC端界面.在Safari中打开该连接,浏览器会询问是否在App Store中打开该连接,选择打开即

  • iOS富文本的使用方法示例详解

    前言 常常会有一段文字显示不同的颜色和字体,或者给某几个文字加删除线或下划线的需求. 使用富文本NSMuttableAttstring(带属性的字符串),上面的一些需求都可以很简便的实现. 最近想实现一个功能,如图: 每月价格 最初实现的时候想到了用两个Label,来实现,第一个显示¥4000,设置一个字体,第二个显示/月,设置另一个字体.这样就能实现这个效果了,但是最后想一想还是用富文本比较好,顺便可以学习一下. 今天我们先实现这个简单的效果. 先创建一个Label: -(UILabel *)

  • Servlet开发JavaWeb工程示例详解

    一.什么是Servlet? Servlet是在服务器上运行的小程序,也就是一个Java类,但比较特殊,不需要new,自动就可以运行.也有创建.垃圾回收和销毁过程.Servlet是JavaWeb的三大组件之一(Servlet.Filter.Listener),它属于动态资源.Servlet的作用是处理请求,服务器会把接收到的请求交给Servlet来处理,在Servlet中通常需要: 接收请求数据: 处理请求: 完成响应. 例如客户端发出登录请求,或者输出注册请求,这些请求都应该由Servlet来完

随机推荐

其他