当前位置: 首页 > news >正文

线程和进程,以及GCD的简单使用

文章目录

  • 进程(Process)和线程(Thread)
    • 进程和线程的区别
    • 线程的用途
    • 并发和并行
    • 多线程带来的问题
  • Grand Central Dispatch的使用
    • 串行队列
    • 并发队列
    • 创建和管理Dispatch Queues
      • 获取主队列
      • 获取全局队列
      • 自定义队列
      • 向队列中添加任务
      • 队列组
      • 其他方法

进程(Process)和线程(Thread)

现代的操作系统可以同时运行多个程序。因此,我们可以在浏览器(一个程序)阅读这篇文章,同时在音乐播放器(另一个程序)上听音乐

每个程序都被称为正在执行的进程,操作系统拥有协调进程同时运行的能力,利用底层硬件的技巧。最终,我们会感觉程序同时在运行

同时执行多个操作不一定同时运行多个进程,因为每个进程都可以在其内部同时运行多个子任务(sub-task),每个子任务称为线程

我们可以将线程视为进程本身的一部分,每个进程启动时都会触发一个线程,通常称为主线程(main thread、primary thread)。随后,根据程序、开发人员需要启动、终止其他线程

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以把操作系统视为包含多个进程的容器,进程是包含多个线程的容器

进程和线程的区别

操作系统为每个进程分配一块内存。默认情况下,进程间无法共享数据,除非采用更为高级的技巧,即进程间通信

与进程不同,线程之间共享操作系统分配给其父进程的内存块。音频播放引擎可以轻松访问音乐播放器主界面中的数据,反之亦然。因此,线程之间可以很方便的进行通信。线程更为轻量级、占用资源少、创建速度快。因此,线程也被称为轻量级的进程

线程可以很方便的使程序同时执行多项任务。如果没有线程,则程序一次只能执行一项任务,在进程执行完毕后与系统进行同步。这将更加复杂和缓慢

线程的用途

一个进程内使用多个线程可以并发处理任务,提高处理速度

但有以下三点需要考虑:

  1. 不是每一个程序都需要多线程。如果 app 执行顺序任务,或需要等待用户输入,多线程可能没有太大好处。
  2. 不是程序线程越多,其速度就会越快。每个子任务都必须经过仔细考虑和设计,以并行执行。
  3. 并发的多线程并不能保证会并行处理,具体是否会并行需要根据硬件、当前状态来确定。

有一点需要注意,如果设备不支持同时执行多种操作,则操作系统必须伪造多任务并行

并行(parallelism)是任一时间多任务同时运行,并发(concurrency)指多任务同时发生,但未必会被同时执行。

伪造并行是通过上下文切换,制造一种同时运行的错觉

并发和并行

中央处理器负责运行程序。CPU 由多个部分组成,主要部分是所谓的核心(Core),即实际执行计算的地方。任一时间单个 Core 只能执行一项任务

任一时间单个 Core 只能执行一项任务限制了程序的运行。为此,操作系统开发了高级技术,即便是单核的设备,也能提供同时运行多个进程、线程的能力。其中最重要的一项技术是抢占式多任务处理(Preemptive Multitasking),在抢占式环境下,操作系统完全决定进度调度方案,操作系统可以剥夺任务的时间片,提供给其他任务,后续再恢复执行暂停的任务

如果 CPU 仅具有一个内核,操作系统的任务之一就是将单个内核的计算资源分布给多个进程、线程,这些进程、线程将在一个循环中依次执行。这样会产生有多个程序或一个程序多项任务同时运行的假象,但此时并未实现真正的并行

现在,CPU 一般都是多核,每个内核一次可以执行一项操作,也就是 CPU 具有两核及以上才能实现真正的并行

多线程带来的问题

同一进程内的所有线程共享同一块内存,这样同一个进程内的线程可以很方便的交换数据

多个线程同时从同一内存区域读取数据并不会出现问题,但同时有线程写入、读取就会出现问题。可能出现以下两个问题:

  • 数据争用 Data Race:也称为数据竞争,一个线程修改数据时,另一个线程正在读取数据。如果写入还没有完成,就会读取到修改一半或损坏的数据,在我的天气预报仿写项目中,就因为网络请求单例的数据没有完全写入单例的属性中,导致UI数据出现错误
  • 竞争条件 Race Condition:也称为竞争冒险(Race Hazard)、竞态条件,指一个系统或进程的输出依赖不受控制的事件出现顺序或出现时机

CPU 的内核一次只能执行一个机器指令。因为其不可分割为更小操作,被称为原子的(atomic)

不可分割的特点使原子操作本质上线程安全。当有线程对共享数据执行原子写入时,其他线程无法读取,也就不会读取到损毁的数据;相反,当有线程对共享数据执行读取操作时,其读取到那一刻的值。线程无法插入到执行原子操作的指令中间,因此不会出现数据争用

这也是为什么我们会把线程安全的属性修饰符叫做atomic

Grand Central Dispatch的使用

GCD提供了一个队列,用于管理向其提交的任务,所有的dispatch queue都是先进先出的数据结构,因此队列中任务运行顺序与添加顺序一致,即第一个进入队列的任务,第一个被执行,第二个进入队列的任务第二个被执行,以此类推

所有的队列自身都是线程安全的,所以可以从多个线程中访问它们

GCD的队列分为串行队列和并发队列两种

串行队列

在串行队列中,一次只会执行一个任务,一个任务完成后另一个任务才会开始,两个任务间隔时间无法确定

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

串行队列中任务执行时间由GCD控制,唯一可以保证的是任务执行顺序和添加顺序一致

并发队列

GCD只能保证并发队列中的任务按照添加的顺序开始执行。至于task结束顺序,两个task间时间间隔,任一时间正在运行task数量都无法保证

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上图中的Block 0开始一段时间后Block 1才开始执行,Block 1、Block 2、Block 3开始时间相差很短。虽然Block 3比Block 2开始的晚,但结束的早

GCD决定什么时间开始执行任务。如果两项任务执行时间重合,由GCD决定是否在另一个内核上(如果目前有空闲内核)运行任务,还是执行context switch来执行另一任务

创建和管理Dispatch Queues

在提交任务到dispatch queue之前,必须确定要使用的队列类型以及如何使用。如果有特殊用途,可以自定义配置队列

为了方便使用,GCD默认提供了主队列dispatch_get_main_queue和全局队列dispatch_get_global_queue

获取主队列

主队列是一个全局的串行队列,运行在应用程序的主线程

    // 获取主队列dispatch_queue_t mainQueue = dispatch_get_main_queue();

获取全局队列

全局队列是并发队列。

系统为每个应用程序提供了四个不同优先级的全局队列。因为这些队列是全局的,可以直接使用dispatch_get_global_queue函数请求队列,不需要显式创建

    // 获取优先级为QOS_CLASS_USER_INITIATED的全局队列dispatch_queue_t aQueue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0);

dispatch_get_global_queue函数的第一个参数指定Quality of Service(简称 QoS)

指定优先级的四个参数如下:

QoS Class用途任务所需时间
QOS_CLASS_USER_INTERACTIVE优先级与主线程任务相同,用于处理用户正在等待、需要立即反馈的任务。追求性能和响应速度。接近瞬间完成。
QOS_CLASS_USER_INITIATED用于用户发起的,需要立即获得结果的任务。例如,打开磁盘上的文档,用户点击界面时执行相应操作,即用户交互的下一步需要这一步的执行结果。对响应和性能有较高要求的。几乎瞬间完成,如几秒钟或更少。
QOS_CLASS_UTILITY需要一些时间来完成,不需要立即返回结果。例如,下载或导入数据。一般有提示进度的进度条。追求响应和能源效率的平衡。几秒钟至几分钟。
QOS_CLASS_BACKGROUND在后台运行,不需要用户看到。例如:索引、同步、备份。关注能源效率。几分钟到几个小时。

QOS_CLASS_USER_INTERACTIVE的优先级与主线程相同,但QOS_CLASS_USER_INTERACTIVE仍然是在全局队列,更新UI只能在主线程中

自定义队列

除了使用系统提供的队列,还可以手动创建队列。

    // 创建串行队列dispatch_queue_t serialQueue = dispatch_queue_create("com.GCD.serialQueue", DISPATCH_QUEUE_SERIAL);// 创建并发队列dispatch_queue_t conQueue = dispatch_queue_create("com.GCD.conQueue", DISPATCH_QUEUE_CONCURRENT);

dispatch_queue_create函数有两个参数,第一个参数指定队列名称,debugger和性能工具会显示此处队列名称以帮助跟踪任务执行情况。第二个参数指定是串行还是并发队列

我们可以创建任意数量串行队列,但这些串行队列之间是并发关系。例如,创建了四个串行队列,每个串行队列执行一个任务,系统可能同时执行这四个任务

向队列中添加任务

  1. dispatch_async(queue, block)

    异步 提交一个任务到指定的队列,并且立即返回,通常用于将耗时操作(如网络请求、文件读写、大数据处理)放在后台执行,以避免阻塞主线程

  2. dispatch_sync(queue, block)

    同步 提交一个任务到指定的队列,函数会 等待任务执行完毕 后才返回,在当前线程需要依赖另一个队列的任务执行结果时使用。但要特别注意,不要在主线程同步提交任务到主队列,会造成死锁

队列组

当一个队列中所有的任务全部执行完毕后,才会执行通知中的操作

  • dispatch_group_create():创建一个新的队列组
  • dispatch_group_async(group, queue, block):向队列组中添加任务
  • dispatch_group_notify(group, queue, block):在队列组中的所有任务都完成后,执行一个通知任务
  • dispatch_group_wait(group, timeout):同步等待队列组中的所有任务完成

在我的天气预报项目中有使用队列组,所以我们就用这个来当例子。我们需要等待所有的城市数据全部请求到之后,再去更新UI界面

// 根据自身的城市名称,请求所有城市的网络数据
- (void) requestAllCityWeatherData {dispatch_group_t group = dispatch_group_create();for (NSString* cityName in self.cityNameArray) {dispatch_group_enter(group);NSLog(@"新的循环,城市名为%@",cityName);__weak typeof(self) weakSelf = self;// 请求网络数据// 回调操作    dispatch_group_leave(group);}dispatch_group_notify(group, dispatch_get_main_queue(), ^{for (int i = 0; i < self.allWeatherArray.count; i++) {DetailPageVC* detailVC = [[DetailPageVC alloc] init];detailVC.model.Info = self.allWeatherArray[i];detailVC.model.InfoDic = self.allWeatherDic[self.cityNameArray[i]];NSLog(@"%@",self.allWeatherDic[self.cityNameArray[i]][@"simple"]);[self.VCArray addObject:detailVC];}//        NSLog(@"发布通知");[[NSNotificationCenter defaultCenter] postNotificationName:@"updateMasterPage" object:nil];// 保存数据
//        [[NSUserDefaults standardUserDefaults] setObject:self.cityNameArray forKey:@"cityNameArray"];
//        [[NSUserDefaults standardUserDefaults] setObject:self.allWeatherArray forKey:@"allWeatherArray"];
//        [[NSUserDefaults standardUserDefaults] synchronize];});
//    [[NSNotificationCenter defaultCenter] postNotificationName:@"updateMasterPage" object:nil];}

其他方法

dispatch_once(predicate, block)

确保以下的代码只会执行一次,也是这是实现单例模式最简单、最安全的方式

+ (instancetype)sharedInstance {static Singleton *instance = nil;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{instance = [[super allocWithZone:NULL] init];});return instance;
}

dispatch_barrier_async(queue, block)

并发队列 提交一个“栅栏”任务。这个任务必须是队列中唯一正在执行的任务,它会等待前面提交的任务都执行完毕,然后自己开始执行,并且在它执行完成前,后面提交的任务都不能开始

通常用于读写数据库或文件。你可以在多个线程中并发地“读”,但当需要“写”时,你需要一个栅栏来确保此时没有其他线程在读写,保证数据安全


文章转载自:

http://qhSi3tRm.htsrm.cn
http://s5XGbIri.htsrm.cn
http://mrUD1EY6.htsrm.cn
http://CdCusM0h.htsrm.cn
http://K5Iqythn.htsrm.cn
http://px2mnlYZ.htsrm.cn
http://3zcUBixS.htsrm.cn
http://dVUhe3ci.htsrm.cn
http://zyIXn6fG.htsrm.cn
http://cco0DEd9.htsrm.cn
http://7Fbmo6Gg.htsrm.cn
http://uujDvlEv.htsrm.cn
http://xKRTpWYf.htsrm.cn
http://tYh9MsuV.htsrm.cn
http://0SGC50tY.htsrm.cn
http://XzAtpXCE.htsrm.cn
http://tV9PdtO2.htsrm.cn
http://diHGWo8d.htsrm.cn
http://er4PP8Qo.htsrm.cn
http://LhffwcmE.htsrm.cn
http://xLoTMefe.htsrm.cn
http://7TyAPzqB.htsrm.cn
http://wLshPtGr.htsrm.cn
http://vpfxaUo8.htsrm.cn
http://5JdYsMFO.htsrm.cn
http://T59TSt5b.htsrm.cn
http://ftWx3HOi.htsrm.cn
http://hfDqvMLM.htsrm.cn
http://Aqt2ARzM.htsrm.cn
http://X3EAoZgS.htsrm.cn
http://www.dtcms.com/a/383916.html

相关文章:

  • C++_STL和数据结构《1》_STL、STL的迭代器、c++中的模版、STL的容器、列表初始化、三个算法、链表
  • 学习日报|线程池专题学习总结
  • kubectl 报错 couldn‘t get current server API group list:
  • 求最小公倍数(GCD)和最大公约数(LCM)——原理和代码
  • 单调栈数据结构
  • OceanBase V4.3.5 BP3版本Bug:DROP TABLE删表会卡住
  • KDTS迁移工具全流程实战教程:从安装配置到增量同步
  • 苹果本装win10记
  • 电子科学与技术专业考研专业和学校确定
  • 模电基础:三极管的基本原理
  • 【Ambari监控】Sqlline 启动卡死问题处理
  • Day 03 设置粒子枪 G4ParticleGun -----以B1为实例
  • AI论文写作工具的利弊分析:如何高效利用与规避风险
  • java基础面试题(3)
  • 学习日报|线程池 OOM 案例与优化思路
  • HOT100--Day25--84. 柱状图中最大的矩形,215. 数组中的第K个最大元素,347. 前 K 个高频元素
  • Linux网络:socket编程UDP
  • GeoHash分级索引技术
  • RISC与CISC:ARM指令集解析
  • 第十二篇:Qcom Camx打印实时帧率 FPS
  • 【开题答辩全过程】以 “候鸟式养老机构”管理系统的设计与实践为例,包含答辩的问题和答案
  • 造车阶段解读
  • 技术论文分析分析论文《计算机病毒判定专家系统原理与设计》思考其在游戏中的应用
  • Elasticsearch面试精讲 Day 18:内存管理与JVM调优
  • Android开发-文本输入
  • C++启航:从0到1,解锁面向对象编程的第一把密钥
  • 基于Dash和Plotly的交互式人体肌肉评分可视化系统[附源码】
  • Linux 开发工具(2)
  • Java进阶教程,全面剖析Java多线程编程,什么是多线程,笔记01
  • 论文参考文献交叉引用+中括号变成上标+自动生成目录方法