链接

从内核的角度来说,它并没有线程这个概念。Linux把所有线程都当做进程来实现。内核并没有准备特别的调度算法或者定义特别的数据结构来表示线程。相反,线程仅仅被视为一个与其他进程共享某些资源的进程。每个线程都拥有唯一属于自己的task_struct,所以在内核中,它看起来就像是一个普通的进程(只是该进程和其他一些进程共享某些资源,如地址空间)。

系统的硬件组成完整冯诺依曼结构模型机.png

冯·诺依曼结构计算机五大部件:控制器、运算器、存储器、输入设备、输出设备。

操作系统

操作系统有2个基本功能:

  1. 对普通程序对硬件的访问进行限制,防止硬件被失控的应用程序滥用
  2. 向应用程序提供简单一致的机制来控制复杂而又大相径庭的低级硬件设备.

操作系统通过几个基本概念( 进程,虚拟存储器,文件)来实现这2个功能:
进程和线程 - 图3
其中,文件是对 I/O 设备的抽象,虚拟存储器是对 主存和磁盘I/O设备的抽象表示,进程是对 处理器、主存 和 I/O设备的抽象表示。

  • 虚拟存储器(虚拟地址空间):虚拟存储器是一个抽象的概念,它为每一个进程提供一个假象,即每个进程都在独占的使用主存(整个内存区域)。

一个虚拟存储器空间包含以下内容:Text Segment、Data Segment、BBS

  • TextSegment 表示程序的代码段
  • DataSegment 表示 已经初始化且初值非0的全局变量和静态局部变量
  • BBS表示未初始化或初始值为0的全局变量和静态局部变量

操作系统负责这些段的加载并分配内存空间,这些段在编译期就分段完成。
对操作系统来说,线程是最小的执行单元,进程是最小的资源管理单元。

进程

进程的概念:为了实现多任务系统,现代操作系统提出了进程的概念,在Linux系统初期,进程作为 CPU 调度的基本单位,后来由于操作系统普遍引入了线程的概念,线程成为了CPU调度的基本单位,而进程只能作为资源拥有的基本单位。
操作系统中,进程提供给应用程序两个重要的抽象:

  1. 它在独立地使用处理器 (通过进程调度,CPU时间片分配)
  2. 它在独立地使用主存(通过进程的虚拟地址空间映射)

Stack(栈):用于存放 局部变量、函数参数、函数返回地址。

Heap(堆):动态分配的内存,由用户自己管理和释放,堆的大小可以在运行时动态地拓展和收缩(C函数调用),
因为空闲地址空间时不连续的,堆在操作系统中是用链表来存储的。

Kernerl Space(内核空间):内核空间 属于操作系统的一部分,常驻内存。操作系统不允许普通的用户应用程序读写这个区域的内容或者直接调用内核空间定义的函数。

进程上下文:一个进程在执行的时候,CPU的所有寄存器中的值、进程的状态以及堆栈上的内容。内核在进行进程的切换时,需要保存当前进程的所有状态,即保存当前进程的上下文,以便再次执行该进程时,能够恢复切换时的状态,继续执行。

进程描述符:有了抽象的概念 ,再来对照着看进程在Linux中的实际表示形式,在Linux中 task_struct描述符结构体表示一个进程的控制块,这个 task_struct结构记录了这个进程所有的context (进程上下文信息)。

  1. struct task_struct{
  2. //列出部分字段
  3. volatitle long state; // 表示进程当前的状态 ,-1表示不可运行,0表示可运行,>0表示停止
  4. void *stack; // 进程内核栈
  5. unsigned int ptrace;
  6. pid_t pid; // 进程号
  7. pid_t tgid; // 进程组号
  8. struct mm_struct *mm,*active_mm // 用户空间,内核空间
  9. struct list_head thread_group; // 该进程的所有线程链表
  10. struct list_head thread_node;
  11. int leader; // 表示进程是否为会话主管
  12. struct thread_struct thread; // 该进程在特定CPU下的状态
  13. // 等等字段:包括一些 表示 使用的文件描述符、该进程被CPU调度的次数、时间、父子、兄弟进程等信息
  14. }

线程

线程概念:线程在Linux中有时被称为轻量级进程(LightweightProcess,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源(共享整个虚拟地址空间)。
线程的实现:在Linux中,实际上线程和进程使用的是同一个结构体 task_struct。在Linux眼里,不会区分进程和线程,内核在进程任务调度时,仅仅是根据调度算法选择一个 task_struct,至于这个task_struct,到底是进程、还是线程 内核并不关心。属于同一个进程有一个线程组的概念,当创建一个线程时,创建一个 task_struct结构体,但是这个 task_struct 共享进程的 虚拟内存 (也就是 task_struct 结构中的 mm_sruct *mm字段)。

上下文切换

当CPU 执行从一个线程(进程)到另一个线程(进程)时,需要先保存当前工作 线程的上下文信息,以便下次回来时重新运行。多线程同多进程一样,存在一个上下文切换的消耗,但是线程的上下文切换要比进程小的多。

多线程 vs 多进程

  • 多线程之间堆内存共享,而进程相互独立,线程间通信可以直接基于共享内存来实现,比进程的常用的那些多进程通信方式更轻量。
  • 在上下文切换来说,不管是多线程还是都进程都涉及到寄存器、栈的保存,但是线程不需要切换 页面映射(虚拟内存空间)、文件描述符等,所以线程的上下文切换也比多进程轻量
  • 多进程比多线程更安全,一个进程基本上不会影响另外一个进程。在实际的开发中,一般不同任务间(可以把一个线程、进程叫做一个任务)需要通信,使用多线程的场景比多进程多。但是多进程有更高的容错性,一个进程的crash不会导致整个系统的崩溃,在任务安全性较高的情况下,采用多进程。

    协程

    什么是协程:英文Coroutines,是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。协程就像非常轻量级的线程。线程是由系统调度的,线程切换或线程阻塞的开销都比较大。而协程依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的,协程是由开发者控制的。所以协程也像用户态的线程,非常轻量级,一个线程中可以创建任意个协程。

    1. fun main() {
    2. // 方法一,使用 runBlocking 顶层函数
    3. runBlocking {
    4. }
    5. // 方法二,使用 GlobalScope 单例对象
    6. // 👇 可以直接调用 launch 开启协程
    7. GlobalScope.launch {
    8. val image = withContext(Dispatchers.IO) { // 👈 切换到 IO 线程,并在执行完成后切回 UI 线程
    9. // getImage(imageId) // 👈 将会运行在 IO 线程
    10. }
    11. }
    12. // 创建一个Job,并用这个Job来管理CoroutineScope的所有子协程
    13. val job = Job()
    14. val coroutineContext: CoroutineContext = Dispatchers.Main + job
    15. // 方法三,自行通过 CoroutineContext 创建一个 CoroutineScope 对象
    16. // 👇 需要一个类型为 CoroutineContext 的参数
    17. val coroutineScope = CoroutineScope(coroutineContext)
    18. coroutineScope.launch {
    19. suspendCoroutine {
    20. }
    21. }
    22. // 结束所有子协程
    23. job.cancel()
    24. }
  • Kotlin 的协程用力瞥一眼

  • Kotlin 协程的挂起好神奇好难懂?今天我把它的皮给扒了
  • 到底什么是「非阻塞式」挂起?协程真的更轻量级吗?

    Android中的进程和线程

    进程

    当一个程序第一次启动的时候,Android 会启动一个 Linux 进程和一个主线程。默认情况下,同一应用的所有组件均在相同的进程中运行,且大多数应用都不会改变这一点。如果我们发现需要控制某个组件所属的进程,则可在清单文件中执行此操作。

组件运行在哪个进程中,是在 AndroidManifest 文件中进行设置的,、、 和 均支持 android:process 属性,此属性可以指定该组件应在哪个进程运行。我们可以设置此属性,使每个组件均在各自的进程中运行,或者使一些组件共享一个进程,而其他组件则不共享。此外, 元素还支持 android:process 属性,用来设置适用于所有组件的默认值。

线程

除了 Thread 本身以外,在 Android 中可以扮演线程角色的还有很多,比如 AsyncTask 和 IntentService,同时 HandlerThread 也是一种特殊的线程。尽管 AsyncTask、IntentService 以及 HandlerThread 的「表现形式」都有别于传统的线程,但是它们的本质仍然是传统的线程。对于 AsyncTask 来说,它的底层用到了线程池,对于 IntentService 和 HandlerThread 来说,他们的底层则直接使用了线程。

从 Android 3.0 开始,系统要求网络访问必须在子线程中进行,否则网络访问将会失败并抛出 NetworkOnMainThreadException 这个异常,这样做是为了避免主线程由于被耗时操作阻塞从而出现 ANR 现象。
而 Android 规定访问 UI 只能在主线程中进行,如果在子线程中访问 UI,那么程序就回抛出异常。ViewRootImpl 对 UI 操作做了验证,这个验证工作是由 ViewRootImpl 的 checkThread() 方法来完成的。

  1. void checkThread(){
  2. if(mThread != Thread.currentThread()){
  3. throw new CalledFromWrongThreadException(
  4. "Only the original thread that created a view hierarch can
  5. ouch its views.");
  6. }
  7. }

Android 系统为什么不允许在子线程中访问 UI 呢?这是因为 Android 的 UI 控件不是线程安全的,如果在多线程中并发访问可能会导致 UI 控件处于不可预期的状态,那为什么系统不对 UI 控件的访问加上锁机制呢?
缺点有两个:

  • 加上锁机制会让 UI 访问的逻辑变得复杂
  • 锁机制会降低 UI 访问的效率

鉴于这两个缺点,最简单且高效的方法就是采用单线程模型来处理 UI 操作,对于开发者来说也不是很麻烦,只是需要通过 Handler 切换一下 UI 的访问执行线程即可。

Linux多线程设计

linux多线程设计是指基于Linux操作系统下的多线程设计,包括多任务程序的设计,并发程序设计,网络程序设计,数据共享等。Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。
线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者。传统的Unix也支持线程的概念,但是在一个进程(process)中只允许有一个线程,这样多线程就意味着多进程。现在,多线程技术已经被许多操作系统所支持,包括Windows/NT,当然,也包括Linux。
为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题。
使用多线程的理由之一是和进程相比,它是一种非常”节俭”的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段堆栈段数据段,这是一种”昂贵”的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

时间片

时间片(timeslice)又称为“量子(quantum)”或“处理器片(processor slice)”是分时操作系统分配给每个正在运行的进程微观上的一段CPU时间(在抢占内核中是:从进程开始运行直到被抢占的时间)。现代操作系统(如:WindowsLinuxMac OS X等)允许同时运行多个进程 —— 例如,你可以在打开音乐播放器听音乐的同时用浏览器浏览网页并下载文件。事实上,虽然一台计算机通常可能有多个CPU,但是同一个CPU永远不可能真正地同时运行多个任务。在只考虑一个CPU的情况下,这些进程“看起来像”同时运行的,实则是轮番穿插地运行,由于时间片通常很短(在Linux上为5ms-800ms),用户不会感觉到。
时间片由操作系统内核的调度程序分配给每个进程。首先,内核会给每个进程分配相等的初始时间片,然后每个进程轮番地执行相应的时间,当所有进程都处于时间片耗尽的状态时,内核会重新为每个进程计算并分配时间片,如此往复。
时间片的分配:通常状况下,一个系统中所有的进程被分配到的时间片长短并不是相等的,尽管初始时间片基本相等(在Linux系统中,初始时间片也不相等,而是各自父进程的一半),系统通过测量进程处于“睡眠”和“正在运行”状态的时间长短来计算每个进程的交互性,交互性和每个进程预设的静态优先级(Nice值)的叠加即是动态优先级,动态优先级按比例缩放就是要分配给那个进程时间片的长短。一般地,为了获得较快的响应速度,交互性强的进程(即趋向于IO消耗型)被分配到的时间片要长于交互性弱的(趋向于处理器消耗型)进程。

双核四线程和四核四线程

  • 双核四线程其实是虚拟出两个核心,在原有核心的基础上开超线程,可以让1个核心同时处理两个不一样的任务,由于一个核心同时做两件事,就像人类一样左右手做不同的事情,效率会打折,相比单核单线,单核双线也只能提升30%的性能。
  • 四核四线程就是真正可以同时处理4个任务而效率不打折的了。相比双核双线程有1倍的性能提升。比双核四线程也有60%的提升。所以这也是为何Intel i3 四线程和i5 四线程价格差了400的原因。
  • 在讲双核和四核有什么区别之前,先给大家讲一下CPU 从单核向双核过渡的由来。从双核技术本身来看,到底什么是双内核?
    • 毫无疑问双内核应该具备两个物理上的运算内核。其实最早的真正意义的双核是AMD 公司的产品。据现有的资料显示,AMD Opteron 处理器从一开始设计时就考虑到了添加第二个内核,两个CPU 内核使用相同的系统请求接口SRI 、HyperTransport 技术和内存控制器,兼容90 纳米单内核处理器所使用的940引脚接口。而英特尔的双核心却仅仅是使用两个完整的CPU 封装在一起,连接到同一个前端总线上。可以说,AMD 的解决方案是真正的“双核”,而英特尔的解决方案则是“双芯”。
    • 不过随着技术的发展,现在市面上的处理器,不管是amd 还是intel 的都已经是真正意义上的多核了。在比较双 核和四核处理器时,我们不能只看它的核心数量。因为四核的处理器性能不一定都比双核的好,比如amd 的x4 640和intel 的i3 3220相比。肯定是i3 3220的总体性能比x4 640要好,因为i3 3220有3M 的三级缓存,而x4 640却没有三级缓存,而且制作工艺方面也比x4 640先进不少。
    • 看CPU 的性能不能简单的从核心数量上去评价,而要综合其它重要参数,如:CPU 主频、总线速度、制作工艺,一级、二级、三级缓存。笔者以前说过一句话,四条腿的动物不一定比两条腿的动物跑的快,反过来也成立。
    • 双核和四核CPU 的区别你可以简单的这样理解:玩游戏时组队打boss ,双核就代表是两个人组队打boss ,四核就代表4个人组队打boss 。一般情况下4个人要比2个人打boss 的效率高。但是如果这4个人都是等级很低的新手,而那2个人却是等级很高的老手的话,也许那两个人能更快的打完boss 。
  • 双核心四线程:指处理器中有两个核心, 但是利用了超线程技术,一个核心就有2个线程,所以两个核心就有4个线程。一般来说,两个核心就只有2线程。 补充:
    1. 什么是超线程技术? 超线程技术就是利用特殊的硬件指令,把两个逻辑内核模拟成两个物理芯片,让单个处理器都能使用线程级并行计算,进而兼容多线程操作系统和软件,减少了CPU 的闲置时间,提高的CPU 的运行效率。因此支持Intel 超线程技术的cpu ,打开超线程设置,允许超线程运行后,在操作系统中看到的cpu 数量是实际物理cpu 数量的两倍,就是1个cpu 可以看到两个,两个可以看到四个。有超线程技术的CPU 需要芯片组、软件支持,才能比较理想的发挥该项技术的优势。 操作系统如:Microsoft Windows XP、Microsoft Windows 2003,Linux kernel 编辑本段详细介绍
    2. 什么是双核处理器 简而言之,双核处理器即是基于单个半导体的一个处理器上拥有两个一样功能的处理器核心。换句话说,将两个物理处理器核心整合入一个核中。企业IT 管理者们也一直坚持寻求增进性能而不用提高实际硬件覆盖区的方法。多核处理器解决方案针对这些需求,提供更强的性能而不需要增大能量或实际空间。
    3. 双核心处理器技术的引入是提高处理器性能的有效方法。因为处理器实际性能是处理器在每个时钟周期内所能处理器指令数的总量,因此增加一个内核,处理器每个时钟周期内可执行的单元数将增加一倍。在这里我们必须强调一点的是,如果你想让系统达到最大性能,你必须充分利用两个内核中的所有可执行单元:即让所有执行单元都有活可干! 为什么IBM 、HP 等厂商的双核产品无法实现普及呢,因为它们相当昂贵的,从来没得到广泛应用。
  • 超线程—帮你理解双核四线程
    • CPU 生产商为了提高CPU 的性能,通常做法是提高CPU 的时钟频率和增加缓存容量。不过目前CPU 的频率越来越快,如果再通过提升CPU 频率和增加缓存的方法来提高性能,往往会受到制造工艺上的限制以及成本过高的制约。尽管提高CPU 的时钟频率和增加缓存容量后的确可以改善性能,但这样的CPU 性能提高在技术上存在较大的难度。实际上在应用中基于很多原因,CPU 的执行单元都没有被充分使用。如果CPU 不能正常读取数据(总线/内存的瓶颈),其执行单元利用率会明显下降。
    • 另外就是目前大多数执行线程缺乏ILP (Instruction-Level Parallelism,多种指令同时执行)支持。这些都造成了目前CPU 的性能没有得到全部的发挥。因此,Intel 则采用另一个思路去提高CPU 的性能,让CPU 可以同时执行多重线程,就能够让CPU 发挥更大效率,即所谓“超线程(Hyper-Threading ,简称“HT”)”技术。
    • 超线程技术就是利用特殊的硬件指令,把两个逻辑内核模拟成两个物理芯片,让单个处理器都能使用线程级并行计算,进而兼容多线程操作系统和软件,减少了CPU 的闲置时间,提高的CPU 的运行效率。采用超线程及时可在同一时间里,应用程序可以使用芯片的不同部分。虽然单线程芯片每秒钟能够处理成千上万条指令,但是在任一时刻只能够对一条指令进行操作。而超线程技术可以使芯片同时进行多线程处理,使芯片性能得到提升。超线程技术是在一颗CPU 同时执行多个程序而共同分享一颗CPU 内的资源,理论上要像两颗CPU 一样在同一时间执行两个线程,P4处理器需要多加入一个Logical CPU Pointer(逻辑处理单元)。因此新一代的P4 HT的die 的面积比以往的P4增大了5%。而其余部分如ALU (整数运算单元)、FPU (浮点运算单元)、L2 Cache(二级缓存)则保持不变,这些部分是被分享的。虽然采用超线程技术能同时执行两个线程,但它并不象两个真正的CPU 那样,每个CPU 都具有独立的资源。当两个线程都同时需要某一个资源时,其中一个要暂时停止,并让出资源,直到这些资源闲置后才能继续。因此超线程的性能并不等于两颗CPU 的性能。英特尔P4 超线程有两个运行模式,Single Task Mode(单任务模式)及Multi Task Mode(多任务模式),当程序不支持Multi-Processing (多处理器作业)时,系统会停止其中一个逻辑CPU 的运行,把资源集中于单个逻辑CPU 中,让单线程程序不会因其中一个逻辑CPU 闲置而减低性能,但由于被停止运行的逻辑CPU 还是会等待工作,占用一定的资源,因此Hyper-Threading CPU运行Single Task Mode程序模式时,有可能达不到不带超线程功能的CPU 性能,但性能差距不会太大。也就是说,当运行单线程运用软件时,超线程技术甚至会降低系统性能,尤其在多线程操作系统运行单线程软件时容易出现此问题。 需要注意操作系统如:Microsoft Windows XP、Microsoft Windows 2003,Linux kernel 2.4.x以后的版本也支持超线程技术。

      多线程,到底该设置多少个线程?

      线程执行

      线程的执行,是由CPU进行调度的,一个CPU在同一时刻只会执行一个线程,我们看上去的线程A 和 线程B并发执行。
      为了让用户感觉这些任务正在同时进行,操作系统利用了时间片轮转的方式,CPU给每个任务都服务一定的时间,然后把当前任务的状态保存下来,在加载下一任务的状态后,继续服务下一任务。任务的状态保存及再加载,这段过程就叫做上下文切换。

      为什么要多线程

      小伙伴想想在我们真实业务中,我们是什么流程?
      进程和线程 - 图4
      上图的流程:

      1、先发起网络请求 2、Web服务器解析请求 3、请求后端的数据库获取数据 4、获取数据后,进行处理 5、把处理结果放回给用户

这个是我们处理业务的时候,常规的请求流程;我们看一下整个过程涉及到什么计算机处理。

1、网络请求——->网络IO 2、解析请求——->CPU 3、请求数据库——->网络IO 4、MySQL查询数据——->磁盘IO 5、MySQL返回数据——->网络IO 6、数据处理——->CPU 7、返回数据给用户——->网络IO

讲到这里,小伙伴们是不是感觉又不乱了,在真实业务中我们不单单会涉及CPU计算,还有网络IO和磁盘IO处理,这些处理是非常耗时的。如果一个线程整个流程是上图的流程,真正涉及到CPU的只有2个节点,其他的节点都是IO处理,那么线程在做IO处理的时候,CPU就空闲出来了,CPU的利用率就不高。多线程的用处,就是为了提升CPU利用率。

提升QPS/TPS

衡量系统性能如何,主要指标系统的(QPS/TPS)

QPS/TPS:每秒能够处理请求/事务的数量 并发数:系统同时处理的请求/事务的数量 响应时间:就是平均处理一个请求/事务需要时长

QPS/TPS = 并发数/响应时间
上面公式代表并发数越大,QPS就越大;所以很多人就会以为调大线程池,并发数就会大,也会提升QPS,所以才会出现一开始前言所说的,大多数人的误区。
其实QPS还跟响应时间成反比,响应时间越大,QPS就会越小。
虽然并发数调大了,就会提升QPS,但线程数也会影响响应时间,因为上面我们也提到了上下文切换的问题,那怎么设置线程数的呢?

如何设置线程数

那我们如何分配线程?我们提供一个公式:
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目

备注这个公式也是前辈们分享的,当然之前看了淘宝前台系统优化实践的文章,和上面的公式很类似,不过在CPU数目那边,他们更细化了,上面的公式只是参考。不过不管什么公式,最终还是在生产环境中运行后,再优化调整。

我们继续上面的任务,我们的服务器CPU核数为4核,一个任务线程cpu耗时为20ms,线程等待(网络IO、磁盘IO)耗时80ms,那最佳线程数目:( 80 + 20 )/20 * 4 = 20。也就是设置20个线程数最佳。
从这个公式上面我们就得出,线程的等待时间越大,线程数就要设置越大,这个正好符合我们上面的分析,可提升CPU利用率。那从另一个角度上面说,线程数设置多大,是根据我们自身的业务的,需要自己去压力测试,设置一个合理的数值。

基础常规标准

那我们小伙伴们会问,因为很多业务集中到一个线程池中,不像上面的案例比较简单,事实上业务太多,怎么设置呢?这个就是要去压力测试去调整。不过我们的前辈已经帮我们总结了一个基础的值(最终还是要看运行情况自行调整)

  1. 1CPU密集型:操作内存处理的业务,一般线程数设置为:CPU核数 + 1 或者 CPU核数*2。核数为4的话,一般设置 5 8
  2. 2IO密集型:文件操作,网络操作,数据库操作,一般线程设置为:cpu核数 / (1-0.9),核数为4的话,一般设置 40

CPU密集型 vs IO密集型

CPU密集型(CPU-bound)

  • CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 100%,CPU要读/写I/O(硬盘/内存),I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading很高。
  • 在多重程序系统中,大部份时间用来做计算、逻辑判断等CPU动作的程序称之CPU bound。例如一个计算圆周率至小数点一千位以下的程序,在执行的过程当中绝大部份时间用在三角函数和开根号的计算,便是属于CPU bound的程序。
  • CPU bound的程序一般而言CPU占用率相当高。这可能是因为任务本身不太需要访问I/O设备,也可能是因为程序是多线程实现因此屏蔽掉了等待I/O的时间。

    IO密集型(I/O bound)

  • IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。

  • I/O bound的程序一般在达到性能极限时,CPU占用率仍然较低。这可能是因为任务本身需要大量I/O操作,而pipeline做得不是很好,没有充分利用处理器能力。

    CPU密集型 vs IO密集型

    我们可以把任务分为计算密集型和IO密集型。

计算密集型任务(每个任务就是一个线程)的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。

第二种任务的类型是IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。

CPU密集型:线程池线程数量,尽量等于核心数或者核心数+1,保证一个核心一线程,减少CPU切换线程时的开销
IO密集型:线程池线程数量,cpu核数 / (1-0.9),核数为4的话,一般设置 40,尽量提高cpu的利用率。

多进程、多线程、多核CPU