进程-线程 并发-并行 多线程 线程池

1 CPU的核心数和线程数

核心数

CPU的核心数是指物理上,也就是硬件上存在有几个核心。举个例子,双核就是包括2个相对独立的CPU核心单元组,四核就包含4个相对独立的CPU核心单元组,等等,依次类推。

CPU的核心数越高,就能同时处理更多的任务,给人感觉就是处理速度越快。

如下图,我们购买的一块CPU,商家说是6核心,那么就意味着这一块CPU其实就是由6个相对独立的CPU核心单元组构成。

image

线程数

一个核心就是一个物理线程,核心数2就有两个物理线程。

但是,英特尔的超线程技术可以把一个物理线程模拟出两个线程来用,充分发挥CPU性能。此时如果说CPU是4线程,就代表核心数2的两个物理线程可以模拟成四个线程来使用。

一般情况下核心(大脑)数等于线程数,但是,如果CPU搭载了超线程技术,那么就可以一核心(大脑)双线程,即一心(脑)二用,超线程技术一般都是1比2的关系产生线程数,这是由大量实验得到的最佳比例值。

如果我们购买了一颗CPU是4核并搭载了超线程技术,也就是常说的4核8线程CPU,那么就意味着单位时间内这个CPU可以同时给8个线程分配算力,也就是并行运行8个线程。

2 进程和线程

2.1 进程的概念

https://blog.csdn.net/zfz5720/article/details/104698545

https://blog.csdn.net/csdnliuxin123524/article/details/79080731

进程可以分为系统进程和用户进程。凡是用于完成操作系统的各种功能的进程就是系统进程,它们就是处于运行状态下的操作系统本身;所有由用户启动的进程都是用户进程。

一个进程就是一个软件的实例,比如一个QQ就是一个进程,又或者一个浏览器中每一个标签页大概率都是一个进程(少数标签页是由其他标签页通过页面跳转打开的,比如a标签 <a href="" target="_blank">打开tab</> 可以新开一个tab页,这新开的和原先的属于同一进程)

反过来,一个软件可以有多个进程,比如一台电脑上打开多个QQ号。

image

2.2 线程的概念

一个进程可以有一个或者多个线程,这些线程执行不同的任务,以实现进程的需求,这些线程可以并发运行,互相通信以及协作。

线程分为用户级线程和内核级线程,只有内核级线程可以在多核CPU上并行操作,用户级线程只能在同一个核上并发操作。

用户级线程需要排队,好处是程序员可以自定义用户级线程的调度算法,让优先级高的用户级线程先执行、多执行。

image

2.3 两者的比较

进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位。

所谓资源分配的基本单位,就是以进程为单位向CPU申请算力。

所谓任务调度和执行的基本单位,就是进程拿到算力后,意味着CPU有一个核心或者多个核心供给进程,此时进程就可以把自己旗下的线程分配到各个核心上执行任务。

在开销方面:

每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;

线程可以看做轻量级的进程,同一进程内的线程共享进程的地址空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

image

所处环境:

在操作系统中能同时运行多个进程(程序),此时CPU同时处理多个线程,如果CPU核心数≥线程数,那么线程之间就属于并行操作,否则就是并发操作

2.4 从JVM角度理解

对于java程序来说,启动一个main方法就启动了一个jvm虚拟机进程,此时由JVM管理整个软件的线程,如果在java程序中基于new Thread()创建了十个线程,那么这个进程中就有一个主线程(main)和十个子线程。如果再运行一次java程序,那么机器中就有两个JVM进程,分别管理自己的线程。

2.5 一个进程的线程能否在多核CPU的多个核心上运行

2.5.1 文章一

https://www.zhihu.com/question/31683094

题主疑惑

    所谓的“一个进程在多个核上跑”指的是进程在核心1能使用的时长达到上限而被挂起后去核心2上跑,还是令自己的多个线程同时去多个核心上执行。如果是后者怎么保证各个线程的数据一致性

别人的回复

    题主说的这两种情况其实都可以做到。

    ARM的big.LITTLE技术可以实现一个进程被核心1挂起后立马“任务迁移”到核心2跑,但频繁的任务迁移实际上是对性能有害的,一般操作系统不会频繁的迁移任务。

    而Windows和Linux目前都支持一个进程的多个线程在多个核心上跑,这是没有问题的,不同的线程通常有自己独立的线程栈,所以除了访问全局变量外基本上不影响。缓存问题有协议来保证一致性,更复杂的一致性问题可以用锁来保护,当然了,锁会降低效率那是肯定的。

2.5.2 文章二

https://www.zhihu.com/question/28254974

这篇文章介绍到题主自己运行一个软件,出现了1核有难,3核围观的局面

这说明一个进程的线程能否在多个核心上跑,得看这个进程对应的软件,在编写时是否支持多线程并行处理,比如题主介绍的情况,一个软件在执行计算任务,由于这个软件在设计时就不支持多线程,这就导致运行这个软件时,一个2核4线程的CPU,只有一个核在运算,其他核都处于闲置状态。

2.5.3 文章三

https://zhuanlan.zhihu.com/p/87272557

https://bbs.csdn.net/topics/360159066

知乎的文章说明了线程分为用户级线程和内核级线程,只有内核级线程可以在多核CPU上并行操作,用户级线程只能在同一个核上并发操作,要想创建内核级线程,就得调用操作系统给的内核级线程创建API。

CSDN的讨论说明了,对于java程序来说,在windows上,jvm调用_beginthreadex创建线程,因此创建的是内核级线程,所以java程序的线程在windows系统上可以被分配到多个核心并行运行。

这也说明了上面的文章中提到的“得看软件是否子好吃多线程并行处理”,其实就是看软件是否创建了多线程,以及创建的线程是否属于内核级线程。

2.5.3 总结

一个进程的线程,理论上可以被放置在多核CPU的多个核心上并行处理,但是实际上运行软件时到底会不会出现一核有难八核围观的局面,得看软件在编写时有没有使用多线程技术——创建多条内核级线程。

2.6 多核CPU 可以并行多个进程吗

2.6.1 文章

https://www.zhihu.com/question/271821176

回答者介绍了多核CPU和多CPU,这是两个概念,如图,多CPU则每一个CPU都有自己的MMU,而多核CPU则共享一个MMU。

image

多核心的架构,由于共用一套MMU和cache,所以地址空间是一个,同一时刻只能运行一个进程,此时进程不能并行只能并发。

多cpu架构则可以支持进程并行。

从回答者所给的图中可以看出,多个进程在多核CPU上,是并发执行而非并行。

image

2.6.2 延申 关于同一进程下的多个线程

回答者说同一个进程下的多线程可以并行执行,因为多线程共享同一套进程空间资源。

然后贴出的图片可以看到,如果是内核级线程,可以在多个核心上实现并行,但是用户级线程只能并发。

2.6.3 总结

上面的回答是从MMU不能多个进程共享而给出的答案,但是有人提到现代CPU已经实现了每个核都有MMU。

那么我在此大胆反推:

    - 实现了每个核心单独拥有MMU的多核CPU就像多CPU那样,可以实现进程之间的并行。

    - 但如果是共享MMU的多核CPU,其实还是一个CPU,同一时刻只能运行一个进程。

image

2.7 CPU核心数与多线程的关系

https://blog.csdn.net/qq_33530388/article/details/62448212?spm=1001.2101.3001.6650.7&utm_medium=distribute.pc_relevant.none-task-blog-2defaultBlogCommendFromBaidudefault-7.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2defaultBlogCommendFromBaidudefault-7.no_search_link

文章中的内容

    1. 文章介绍了单核CPU同一时刻只能处理一个线程,但是基于线程调度算法,可以并发执行多个线程,实现多任务操作。这一刻还在执行进程A的线程1,下一刻可能执行进行A的线程2,也可能执行进程B的线程1。

    2. 另外介绍了java程序中,由JVM虚拟机负责线程调度,也就是一个java程序要执行的任务,会放在一条或几条线程上,再由JVM向操作系统申请使用CPU,申请到后,JVM就把手中的线程放到CPU上执行操作。

    3. JVM的线程调度模式为抢占式,也就是我们可以人为对JVM上的各个线程任务定义优先级,当JVM拿到CPU分配的算力后,会先给优先级高的线程分配算力。

    4. 另外文章提出了一个假设——单核上,假如有2个进程,各自有5个线程,那么时间分配上两个进程是1:1分配,如果一个进程有15个线程,另一个有5个,那么时间分配是3:1。

    5. 文章也提到了——假如是双核CPU,现在有2个进程,那么并不会出现一个进程占据一个CPU,然后所有线程都在这个核心上运算,那样如果一个进程的任务很简单,岂不是要出现1核有难,多核围观的局面了吗?所以CPU会根据调度算法,把进程的线程分配到其他核上执行。

我的收获

    1. 文章提到的JVM负责调度java程序的进程是一个新知识点,这样就能理解java程序中创建线程池的作用了,就是在JVM上准备好线程,就像准备好“汽车”那样,当java程序的任务需要放在线程上时——多线程操作,那么就不再需要现场创建线程,而是直接使用JVM上创建好的线程,然后就等CPU分配算力然后执行就行

    2. 文章中提到的单核CPU对进程的时间分配只是个猜想,要想知道这个假设对不对,就得去研究CPU的线程调度算法。

    3. 文章提到CPU会根据调度算法,把进程的线程分配到空闲的核上执行,但是根据其他文章和现实生活看到的情况,这只是说明CPU支持这种操作,但是具体能否实现还得看被这样处理的软件在编写的时候,有没有支持这种多线程,如果一个软件的计算任务就放在一条线程,那么CPU即便能分配空闲核心去计算,这个进程也没办法完成。

    4. 如果进程支持多线程操作,那么CPU确实会分配空闲核心给进程跨核心执行线程,而线程跨核心下的数据交互就靠MESI协议实现

3 并行和并发

https://exp.newsmth.net/topic/article/8c78c826f63589a7f6f49abb1245e1e6

3.1 并行

3.1.1 概念

并行的意思就是任务A和任务B同时执行。

3.1.2 进程角度

从进程的角度上看,对于多核但MMU唯一的CPU而言,无法做到进程并行,但是可以做到进程的内核级线程并行;对于多核且MMU也有多个的CPU,或者干脆就是多CPU而言,可以做到进程并行。

3.1.3 线程角度

从线程的角度上看,当进程拿到多核CPU算力后,内核级线程就可以同时在多个CPU核心上并行执行。

3.2 并发

3.2.1 概念

并发的意思就是任务A和任务B轮流执行一段时间,交替执行。

3.2.2 进程角度

从进程的角度上看,对于多核但MMU唯一的CPU而言,进程无法做到并行,此时进程之间互相等待,这就是并发。

3.2.3 进程的并发调度算法

实现并发技术相当复杂,最容易理解的是“时间片轮转进程调度算法”,它的思想简单介绍如下:在操作系统的管理下,所有正在运行的进程轮流使用CPU核心,每个进程允许占用CPU核心的时间非常短(比如10毫秒),这样用户根本感觉不出来 CPU是在轮流为多个进程服务,就好像所有的进程都在不间断地运行一样。但实际上在任何一个时间内有且仅有一个进程占有一个CPU核心。

3.2.4 线程角度

进程拿到CPU算力后,要调度线程执行任务。从用户级线程的角度上看,线程之间无法像内核级线程那样并行,只能挤在一个CPU核心上互相排队等待执行,这也是并发。

3.2.5 线程的并发调度算法

用户级线程的缺点是无法像内核级线程那样并行,优点是程序员可以自定义调度算法,让程序员自定义的优先级高的用户级线程先执行、多执行。

3.3 两者的区别

并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。所以说,并行是并发的子集。

4 多任务操作系统

在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态,这便是多任务。

现代的操作系统几乎都是多任务操作系统,能够同时管理多个进程的运行。多任务带来的好处是明显的,比如你可以边听网易云音乐,一边上网,与此同时甚至可以将下载的文档打印出来,而这些任务之间丝毫不会相互干扰。

现代电脑基本都是多核心CPU,根据CPU核心数是否大于等于进程数,由操作系统决定是并行实现多任务还是并发实现多任务。

5 多线程

https://blog.csdn.net/ybhuangfugui/article/details/109192596

5.1 多线程的概念

当一个进程占用一个CPU核心时,它要实现多种任务,比如word又要读取用户打字的信息又要显示到屏幕上,此时该进程就会创建多个线程分别执行不同的任务,这就是多线程。

又或者一个进程虽然只做视频渲染这种纯“计算”任务,但是为了加快渲染速度,那么就会创建多个内核级线程,以便于在得到CPU算力后,让线程占据多个CPU核心,并行操作,这种创建多个线程的操作,也是多线程。

与多线程相对的是单线程,顾名思义即是只有一条线程在执行任务,日常工作和学习上基本碰不上。

5.2 多线程的作用

多线程最直观的作用就是加速某个任务的完成。

假如我们写了一个软件,任务是令100不断自减直到为0,这个数字每自减1次耗时1秒,其中调用CPU运算用了0.5秒,调用磁盘读写IO用了0.5秒。

如果我们编写代码时只创建了一个用户级线程去执行这个任务,那么就要耗费100秒,很慢,但是我们可以在编写代码时创建多个用户级线程都来执行这个任务,假如创建了2个用户级线程,那么就只需要50秒就能完成整个任务,因为其中一个用户级线程在读写IO时,就可以让另一个用户级线程使用CPU进行计算,将CPU充分利用起来。

如果我们创建多个内核级线程,并且程序也能占据到多核CPU的多个核心,让这些内核级线程并行执行,整体任务将会更快。

5.3 多线程一定比单线程快吗

https://zhidao.baidu.com/question/1899763123370157940.html

https://blog.csdn.net/Z875983491/article/details/103349644?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2defaultCTRLISTdefault-1.no_search_link&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2defaultCTRLISTdefault-1.no_search_link

https://blog.csdn.net/qq_38006520/article/details/83349149?spm=1001.2101.3001.6650.8&utm_medium=distribute.pc_relevant.none-task-blog-2defaultBlogCommendFromBaidudefault-8.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2defaultBlogCommendFromBaidudefault-8.no_search_link

这得分类讨论,但是答案肯定是不一定。

对于多个用户级线程:

    如果CPU没有被充分利用起来,用户级线程的操作中大部分时间中CPU都处于闲置状态,那么多条用户级线程就可以提升CPU的使用率,令程序执行效率提高。

    如果CPU在单条用户级线程中已经将近全负荷运行了,读写IO基本不花时间,那么多条用户级线程由于还得考虑线程创建和上下文切换的时间开销,最终耗费的时间反倒变多了,程序执行效率反倒降低了。

对于多个内核级线程:

    如果CPU只分配了一个CPU核心给进程,那么内核级线程就等同于用户级线程。

    如果CPU分配了多个CPU核心给进程,那么内核级线程就能发挥自己的并行处理能力,多线程自然比单线程快。

5.4 多线程是并行还是并发

用户级线程

    总是并发的。

内核级线程

    由于可能被分配到一个CPU核心上执行,也可能被分配到不同CPU核心上执行,分配过程是操作系统所为,不可人为控制,所有都有可能。

总结

多线程只是更加充分的利用cpu资源做更多的事,但并不保证会并行执行。一方面要看这个线程是否是内核级线程,另一方面还要看cpu核心有没有空余资源分给它。有的话就可以并行(也就是同时)执行任务。没有cpu核心资源的话,就是并发执行(线程切换执行)的

6 多线程下的线程安全问题

如果多个线程同时执行一个任务,比如上面的扣减数字,这意味着他们共享同一种资源(保存数字的int变量)。

当多线程处理同一个任务时,假如A线程先抢占到CPU资源,他刚刚进行了第一次操作(只是计算完毕,int变量从1减少到0,还没有进行读写IO操作),而此时B线程抢占到了CPU的资源,由于共享资源还来不及发生变化(int变量还是1,满足int变量大于0的条件,B线程继续调用CPU执行计算),就会导致最终int变量从1减少为0的操作被执行了两次。

反应到现实生活中的场景就是“超卖”。

这个问题主要的矛盾在于,CPU的使用权抢占和资源的共享发生了冲突,这就是线程安全问题。

要解决线程安全问题,我们只需要让一条线程占据CPU的资源时,阻止第二条线程同时抢占CPU的执行权,在代码中,我们只需要在方法中使用同步代码块(也就是常说的锁)。

7 java线程池

https://blog.csdn.net/m0_37947204/article/details/80658424?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2defaultCTRLISTdefault-1.no_search_link&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2defaultCTRLISTdefault-1.no_search_link

7.1 线程的销毁方式

当进程得到CPU时,创建多个线程然后并发运行这些线程完成任务,当遇到以下几种状况时,线程会被销毁。

    1. 自然死亡。从线程主函数返回,线程正常退出
    

    2. 非正常死亡。从线程主函数抛出异常或线程触发segfault信号等非法操作
    

    3. 自杀。在线程中调用pthread_exit()来立刻退出线程
    

    4. 他杀。其他线程调用pthread_cancel()来终止某个线程

7.2 线程池的概念

创建和销毁线程的过程势必会消耗内存,而在Java中,内存资源是极其宝贵的,所以,基于池化思想,我们就提出了线程池的概念。

线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。

7.3 线程池的创建方式

线程池的创建方式

7.4 java创建的线程是用户级还是内核级

https://bbs.csdn.net/topics/360159066

从别人的讨论可以得知,是内核级,所以可以很好的利用CPU资源。

posted @ 2021-11-16 17:24  夏·舍  阅读(282)  评论(0编辑  收藏  举报