iOS知识梳理 - 多线程(1)杂谈

进程和线程

首先,一般所说的进程和线程都是系统内核层面的概念。html

进程(Process),直观来说就是运行中的程序,它是系统进行资源分配的最小单位。即,系统给每一个进程分配虚拟地址空间等资源,进程间默认是不共享内存等资源的。python

线程(thread)自己理解起来其实更简单,它是个依附于进程的更细粒度的调用单位,它有本身的栈等数据,但一般内存是共享的(在一个进程的多个线程间)。然而实际讨论起来会更复杂一些。git

线程分为内核级线程用户级线程两种,分类的标准主要是线程的调度者在核内仍是核外。说白了,内核级线程就是系统提供的线程。而用户级线程就是程序自身实现的线程。理论上他们的本质区别只是这些,但实际上确是天差地别。github

线程的调度策略大致上分两种:抢占式调度和协同式调度。抢占式指线程的切换时机由系统控制,每一个线程都会得到或多或少的CPU时间片,单个线程卡死并不会卡住其它线程;协同式调度是指某一线程执行完或执行到必定阶段后,主动让出CPU,切换到另外一个线程执行,一个线程卡死了,别的线程也就得不到CPU资源,整个进程就全完了。golang

咱们通常讨论的线程,都默认了调度方式为抢占式调度。而讨论协同式调度的线程时,一般使用一个更高大上一点的词:协程web

从另外一个维度来看一下,咱们使用的线程API,和内核级线程的对应关系,一般分为一对1、多对一和多对多。一对一时其实就是内核级线程;多对一时是用户级线程,实际实现时都是协同式调度的,即协程。而多对多时,即多个上层线程会被分派到多个内核级线程执行,这种被成为混合型线程。编程

咱们通常讨论的线程都是这种一对一模型下的线程,也是目前最普遍的线程实现。不管是Android仍是iOS,以及Java服务端,默认地,都是这种。咱们后面的讨论也主要在这种模型下展开。swift

多对一模型的用户级线程已经几乎看不到了,这里再也不讨论。数组

多对多模型仍是很多的,这两年很火的golang就是用的多对多模型,另外有一些jvm也有实现但默认选项还是一对一。以golang的goroutine为例,实际实现能够理解为线程池+协程的结合。goroutine其实比较接近线程池中的task概念,排队进入线程池执行。而它优于task的地方在于,goroutine是带上下文的,即执行到一半时,能够停下来带着上下文从新排队,排到它时继续执行,这又是协程的特性了。这种底层设计结合优秀的上层语法,获得的golang是个很是有吸引力的语言。网络

参考:

线程和进程的区别是什么?

内核态是指一个特殊的进程,仍是指进程的一种特殊状态?

用户级线程和内核级线程的区别

Linux下调用pthread库建立的线程是属于用户级线程仍是内核级线程

线程究竟是什么?

发散地探讨了一些,下面仍是重点讨论内核级线程。

线程池

多线程编程给咱们带来了诸多便利,但仍有一些不方便的地方。好比出于性能考虑,线程数量不能太多。

并行是指物理层面上的同时处理,即CPU多个核心各自处理不一样的事情。这很nice。

并发是指逻辑上的同时处理多个事情,实际上CPU多是在同一个核上分了时间片处理的,也就是前面说的抢占式调度。线程数量越多,线程间切换的开销也就越大。这就致使咱们不能随意建立线程。

另外,若是有比较多的建立、销毁线程操做,开销也是比较大的。

为了解决这一问题,最多见的方案是线程池模式。一开始就设定好池子里线程的数量,把要执行的任务往池子里丢,有线程空闲就处理,没有就排队。

GCD就是iOS平台下基于线程池的方案,它暴露Queue、Task这样的接口,一般只会使用CPU核心数那么多的线程数量(参考Number of threads created by GCD?),保证了性能。相比直接调用线程接口,下降了心智负担,而且有效地提升了多线程程序的下限。

线程冲突

线程冲突是多线程编程中最大的问题,容易遇到,又比较难搞。好比两个线程都想要操做一个数组,一个想往里塞数据,一个想要删数据,就很容易冲突。没作异常捕获的话程序就直接挂了。

常规方案就是加锁。可是加锁是个,咋说呢,颇有学问的事情。加得很差会特别影响性能(→_→ 参考python的GIL)。一些复杂的业务场景下,锁的问题可能会很是很是很是很是复杂。

这个问题的本质在于,线程间是经过共享内存来通讯的,同时读写同一块内存时必然遇到冲突问题。因而,经过事件驱动/消息机制进行线程间通讯的方式逐渐受到人们关注。直白点来讲,你须要这块数据的时候我复制一份发给你,你就从消息通道读数据,不要跟我共享别的变量之类的了。

通常场景下,这种方式带来的内存和CPU开销并不会太多,逻辑上却比锁要简单太多了,所以这些年受到了普遍的关注。

如下几个都是这种思想的实现:

有的语言(go)是把这做为可选方案,而有的语言(dart)直接把这做为惟一方案。当消息机制成为线程间通讯的惟一方案时,线程已经再也不是线程了(共享内存算是线程的比较核心的特征了),所以dart自称单线程语言,其isolate是区别于多线程的一种并发编程机制。

用通讯代替共享内存是个大的思路,实际上衍生出了多重并发编程模型如Actor、CSP等。这里就再也不更细地分析了,能够参考erlang/go的实现。

参考:

并发编程模型:事件驱动 vs 线程

如何理解 Golang 中“不要经过共享内存来通讯,而应该经过通讯来共享内存”?

多线程和异步

比较早的时候,是不多用异步调用的。要发网络请求的时候,就开个子线程让子线程进行同步调用,阻塞等待调用结果。

这种方式称为同步,期间这个线程是阻塞的。

显然,这是对线程资源极大的浪费。所以如今这样作的少了,一般发出网络请求后当前线程会继续往下执行,请求回包后会经过某种事件处理机制触发回调函数进行处理。

这种方式称为异步,期间这个线程是非阻塞的。

除了网络请求,IO读写等场景也是相似的。这些不占用CPU的场景应当优先使用异步的手段,而不是开子线程处理。(iOS平台下好像基本上没有这样作的,之前写Java的时候见到的比较多)

iOS下,一般咱们用block或delegate进行异步编程,写起来会比同步代码麻烦一些。协程的一大好处就是可以以相似同步代码的方式写异步代码。对应的async/await是swift最受人期待的新特性,以前一度传言swift5会上,惋惜并无...

相关文章
相关标签/搜索