计算机原理一_计算机的组成、进程与线程

news2024/11/8 12:09:20

目录儿

  • 一、计算机组成
  • 二、进程与线程
    • 2.1 线程的切换
    • 2.2 CPU的并发控制
      • 2.2.1 关中断
      • 2.2.2 缓存一致性协议
        • 2.2.2.1 缓存Cache
        • 2.2.2.2 缓存行Cache Line
        • 2.2.2.3 缓存一致性
        • 拓展:超线程
      • 2.2.3 内存屏障
        • 2.2.3.1 CPU的乱序执行
        • 拓展1:java 的 this 溢出问题
        • 拓展2:双重检查锁DCL的bug
        • 2.2.3.2 Java 中防止指令重排
          • 禁止编译器乱序
          • 使用内存屏障阻止指令乱序执行
            • JVM中的内存屏障
            • volatile的实现细节
      • 2.2.4 总线锁、缓存锁

一、计算机组成

计算机组成简示:
请添加图片描述

  • CPU主要组成部分:
    • 算术逻辑单元:负责算术和逻辑运算。
    • 程序计数器:保存将要执行的下一条指令的主存地址(属于寄存器的组成部分)
    • 寄存器:至少分为六部分,负责暂存各种数据
      • 指令寄存器(IR)
      • 程序计数器(PC)
      • 地址寄存器(AR)
      • 数据寄存器(DR)
      • 累加寄存器(AC)
      • 程序状态字寄存器(PSW)
  • 总线:连接两个或多个计算机组件
    • 控制总线:管理组件之间的信息流,指示操作是读取还是写入,并确保操作在正确的时间发生。
    • 地址总线:处理器通过地址总线,从内存中读取数据的内存地址。
    • 数据总线:在不同组件之间传输数据。

二、进程与线程

一个程序,读入内存,都是 0 和 1
从内存读入到 cpu 计算这个过程,需要通过总线
指令和数据是无差别存放在存储器中的,怎么知道一段 0 和 1 组成的数据到底是数据还是指令?
总线分三类,从不同总线读取到的数据类型不同,总线是如何解析区分的暂不深究

程序执行过程:
一个程序执行后,首先把可执行文件加载到内存中,找到起始(main方法的)位置,逐步读出指令和数据,进行计算,并写回内存。

什么是进程:
一个程序进入内存,称为进程
同一个进程可以执行多份,比如游戏、微信的多开,每个进程都独立分配内存空间。
单个进程内通常存在需要多个任务通知进行的需要,比如收发数据,usb读取,输入输出设备的数据交互,这个时候就出现了并发的刚性需求
最早提出将一个进程拆分成多个进程的做法,但是存在严重的数据共享窜读的问题
于是线程的概念横空出世,也被称为轻量级进程

什么是线程:
线程没有自己的内存空间,都是用的进程的内存空间
同一个进程的多个线程之间共享内存,但是不共享计算,这也是多线程访问同一个资源产生并发问题的主要原因。

进程是静态的概念:程序进入内存,计算机会给它分配对应资源(主要是内存空间),同时产生一个主线程。
线程是动态的概念:是可执行的计算单元。
一个ALU同一时间只能执行一个线程

2.1 线程的切换

在这里插入图片描述
计算机以时间片为单元轮流执行多个线程,在切换的过程序会把每个线程的执行状态(上下文)保存在该线程所属进程的缓存空间中,以便下次执行该线程时能够读取线程执行到哪里。

问题一:是不是进程的线程的数量越多,执行效率越高?
回答:非也,线程切换是需要时间的,多线程主要目的是为了不浪费计算机的单位时间算力,线程数达到一定程度,切换线程花费的时间比例占比过大,反而影响了计算机的执行效率。

问题二:对于一个程序,设置多少个线程合适(线程池设定多少个核心线程)?
回答:理想情况是根据公式可以算出大概,但实际情况是很难计算线程的等待/计算时间比例,所以要进行压测。
在这里插入图片描述
比如某线程任务在CPU中的执行情况是50%的时间在计算,50%的时间在等待资源,现有一个双核CPU,希望CPU利用率跑到80%左右,则代入公式2×0.8×(1+0.5/0.5)=3.2,则线程应该为3个或者4个为合适。

2.2 CPU的并发控制

CPU的并发控制有四种方式。

2.2.1 关中断

2.2.2 缓存一致性协议

2.2.2.1 缓存Cache

CPU内ALU访问自身寄存器的速度比访问内存的速度快很多很多。为了充分利用CPU的计算能力,在内存和寄存器之间引入了缓存的概念。现在的CPU多采用三级缓存的架构。

多核CPU架构:
在这里插入图片描述
L1L2L3为缓存,其中一二级缓存跟核心绑定,三级缓存为多核共享。

2.2.2.2 缓存行Cache Line

缓存行指一次性读取的数据块

处理器从缓存读取数据不是读取单独的一个数据,而是把包括这个数据在内的数据块读过来,这样做的理论基础是程序的局部性原理

程序的局部性原理:

  • 空间局部性:实践证明当处理器访问一个数据的时候,很大几率在短时间内会访问其相邻的数据。
  • 时间局部性:实践证明当处理器执行一条指令的时候,很大几率在短时间内会执行其相邻的指令

如果缓存行大,则命中率高,读取效率低,反之相反
根据工业实践妥协的结果,目前(2021)的计算机多采用64byte(64 * 8 bit)为一行(一个缓存行)。

2.2.2.3 缓存一致性

由于缓存的存在,当多个核心对同一块缓存行进行修改的时候,如何保证每一级缓存中的数据一致?这时候就需要引入缓存一致性这个概念。
在这里插入图片描述
每个处理器厂商都有自己的缓存一致性协议,比如英特尔的MESI,还有其他MOSIMSI等等。
在这里插入图片描述

有些数据很大,超过了64byte(缓存行)的大小无法被缓存或者跨越多个缓存行,这种数据需要使用总线锁来保证数据的一致性。

package 缓存一致性;

import java.util.concurrent.CountDownLatch;

public class CacheTest {

    static class T {
        public long a1, a2, a3, a4, a5, a6, a7;
        public long x;
        public long a8, a9, a10, a11, a12, a13, a14;
    }

    public static long COUNT = 1_000_000_000L; // 1亿
    public static T[] arr = new T[2];

    static {
        // 初始化数组
        arr[0] = new T();
        arr[1] = new T();
    }

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(2);
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                arr[0].x = i;
            }
            countDownLatch.countDown();
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                arr[1].x = i;
            }
            countDownLatch.countDown();
        });

        final long start = System.nanoTime();
        t1.start();
        t2.start();
        countDownLatch.await();
        System.out.println("运行时间(ms) = " + (System.nanoTime() - start) / 1_000_000);
    }
}

拓展:超线程

除此了引入缓存外,还引入了超线程的概念:
在这里插入图片描述
我们说的核就是指ALU,算力核!当一个核配两套寄存器,就成了一核双线程,大大节省了线程切换的时间。

2.2.3 内存屏障

内存屏障分为两级:

  • 编译级别的屏障
  • 指令级别的屏障

在了解内存屏障前先了解CPU的乱序执行。

2.2.3.1 CPU的乱序执行

启动一个程序,进程加载到内存,产生一个主线程,CPU开始执行主线程,处理器处理线程的过程中往往伴随着加载资源(从内存加载数据、等待网络等等)和计算。因为CPU的计算速度是非常快的,可能从内存读取数据的时间周期中就能完成多次运算。因此为了充分利用CPU的算力,CPU会对指令进行优化,比如第一条指令是从内存加载数据,第二条指令是变量自增,在第一条指令内存加载数据还没完成的时候,第二条变量自增指令很可能早已完成,这就形成了后面的指令比前面的指令先完成的乱序执行情况(这是CPU的流水线设计)。

总结:
CPU在等待费时指令执行的时候,优先执行后面的指令。目的就是为了提高执行效率。
单个线程,两条语句,未必是按顺序执行
单线程的重排序,必须保证线程的最终一致性
但在多线程中可能出现不希望看到的乱序情况。

例 证明乱序执行的存在:

import java.util.concurrent.CountDownLatch;

public class Ordering{
    private static int x = 0, y = 0;
    private static int a = 0, b = 0;

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < Long.MAX_VALUE; i++) { // 超大次数循环
            // 重置数据
            x = 0;
            y = 0;
            a = 0;
            b = 0;

            CountDownLatch latch = new CountDownLatch(2);
            // 线程一
            Thread t1 = new Thread(() -> {
                a = 1;
                x = b;
                latch.countDown();
            });
            // 线程二
            Thread t2 = new Thread(() -> {
                b = 1;
                y = a;
                latch.countDown();
            });
            t1.start();
            t2.start();
            latch.await();
            if (x == 0 && y == 0) {
                System.err.println("第" + i + "次循环出现(" + x + "," + y + ");");
                break;
            }
        }
    }
}

执行结果:
在这里插入图片描述

解释:
因为时间片的存在,处理器轮流执行线程任务,那么可能出现8种情况
① 执行 线程一 → 线程二:结果为 x = 0,y = 1;
在这里插入图片描述
① 执行 线程二 → 线程一:结果为 x = 1,y = 0;
在这里插入图片描述
③ 执行 线程一 → 线程二 → 线程一 → 线程二:结果为 x = 1,y = 1;
在这里插入图片描述
④ 执行 线程二 → 线程一 → 线程二→ 线程一:结果为 x = 1,y = 1;
在这里插入图片描述
⑤ 执行 线程一 → 线程二→ 线程一:结果为 x = 1,y = 1;
在这里插入图片描述
⑥ 执行 线程二 → 线程一→ 线程二:结果为 x = 1,y = 1;
在这里插入图片描述

⑦⑧
虽然在程序中
线程一中指令顺序为 a=1; x=b;
线程一中指令顺序为 b=1; y=a;
但是对于单线程来说,以线程一为例,无论是先执行 a = 1 ,还是先执行 x = b ,最终放入内存的结果都是一样的,因为 x = b 这个 b 对于线程一(x)来说它只是一个地址(引用),地址的实际值并不在线程一赋值,所以先执行 x = b 也是完全没问题的,它能够保证本线程的最终一致性。但是在多线程中,若碰巧出现线程一和线程二同时发生指令乱序执行,导致出现 x=0,y=0,虽然理论上指令的执行没问题,但是产生了我们不希望看到的结果:
在这里插入图片描述

拓展1:java 的 this 溢出问题

例:

import java.io.IOException;

public class ThisEscape {
    private int num = 8;

    public ThisEscape() {
        new Thread(()->{System.out.println(this.num);});
    }

    public static void main(String[] args) throws IOException {
        ThisEscape thisEscape = new ThisEscape();
    }
}

先看对象的创建过程:
在这里插入图片描述
C++中,申请完空间后变量的值是一个遗留值(上一个程序运行后留下来的值),而Java的做法是在申请空间后,会给变量赋初始值(从汇编码可以看出),目的是为了保证安全。
问题就在这里,上面例子在构造器中就创建线程通过this引用调用num变量

new Thread(()->{System.out.println(this.num);});

虽然在汇编码中this的引用建立是在构造方法的结尾才执行,但是因为编译器和处理器会对指令重排序,很有可能先建立了this引用再进行变量的初始化,在对象初始化不完全的时候就通过this建立引用关系去调用对象的变量,会出现对象不稳定的情况,这就是著名的 this溢出问题。就像 你女朋朋穿衣服穿到一半你把她拉出去见人,她是上半身走光还是下半身走光都有可能

因此,有一个原则:不要在构造方法中启动线程或者注册监听器。

拓展2:双重检查锁DCL的bug

在单例模式的懒汉式中,面对多线程问题,通常用synchronized关键字上锁,其中最优解为双重检查锁Double Check Lock,能保证单例安全。

private static Test INSTANCE;

public static Test getInstance(){
	if(Test.INSTANCE == null){
		synchronized(Test.class){
			if(Test.INSTANCE == null){
				Test.INSTANCE = new Test();
			}
		}
	}
	return Test.INSTANCE;
}

但是在这个DCL解决方案中也存在bug。
在这里插入图片描述
同样是创建对象的过程指令重排序的问题,如果线程1在创建对象的过程中发生了指令重排,先建立的引用关系,此时线程2进入判断就会发现实例不为null,那它就会 直接拿到实例然后调用,这时它拿到的是初始化不完全的不稳定对象。

解决办法:防止指令重排
在JAVA中可以通过volatile关键字(修饰变量)防止指令重排

private static volatile Test INSTANCE;

public static Test getInstance(){
	if(Test.INSTANCE == null){
		synchronized(Test.class){
			if(Test.INSTANCE == null){
				Test.INSTANCE = new Test();
			}
		}
	}
	return Test.INSTANCE;
}

synchronized可以保证原子性、可见性但是不能保证有序性。

2.2.3.2 Java 中防止指令重排

禁止编译器乱序

编译器对代码进行优化的时候可能会对两个没有依赖关系的指令重排序。

使用内存屏障阻止指令乱序执行

内存屏障是特殊指令:看到这种指令,前面的指令必须执行完,后面的才能执行。

对于不同的CPU,有不同的屏障指令。如Intel的屏障指令是lfencesfencemfence

JVM中的内存屏障

所有实现JVM规范的虚拟机,必须实现四个屏障:

  • LoadLoad屏障:
    对于这样的语句Load1;LoadLoad;Load2;
    在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
  • StoreStore屏障:
    对于这样的语句Store1;StoreStore;Store2;
    在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
  • LoadStore屏障:
    对于这样的语句Load1;LoadStore;Store2;
    在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
  • StroeLoad屏障:
    对于这样的语句Store1;StoreLoad;Load2;,
    在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。

简单地说就是任一屏障前后的操作不能换顺序。

JVM的内存屏障最终还是要映射到CPU提供的内存屏障上。

最流行的JAM内存屏障实现:hotspot
hotspot是用C++写的,因为Java是解释性语言,是解释运行的,当hotspot中的二进制码解释器bytecodeinterpretor.cpp遇到volatile关键字时,它通过汇编指令lockvolatile映射成CPU级别的屏障:
在这里插入图片描述

汇编指令LOCK用于在多处理器中执行指令时对共享内容的独占使用;
它的作用是能够将当前处理器对应缓存的内容刷新到内存,并使其他处理器对应的缓存失效,另外还提供了使后面有序的指令无法越过这个内存屏障的作用。

volatile的实现细节

volatile有两大作用:

  • 保证可见性:一颗核心改了另外的核心立刻可见,一个线程改了另外的线程立刻可见
  • 禁止指令重排

JVM会对volatile修饰的那块内存的读写操作指令前后添加内存屏障:
在这里插入图片描述

2.2.4 总线锁、缓存锁

上面内存屏障中提到的汇编指令lock就是其中之一,至于什么时候锁总线什么时候锁内存赞不深究。

资料来源:哔哩哔哩 马士兵-小森 清华大牛耗时1000分钟把计算机底层知识 | 计算机组成原理 | 操作系统
https://www.bilibili.com/video/BV1qe4y1T7t3?p=5&spm_id_from=pageDriver&vd_source=c2e346bb74807b3fde142d31e57292ce

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/142004.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux(一):Linux基本结构

一、Linux系统划分 linux系统分为用户区、内核区 1.1分区目标 保护数据和硬件安全&#xff0c;对系统进行分区也就是进程分区&#xff0c;当处于用户态&#xff0c;只能访问用户区&#xff0c;用户无法修改内核&#xff0c;保证硬件安全&#xff0c;操作系统不易损坏&#…

【Qt】QMainWindow应用程序窗口类简单介绍

QMainWindow介绍 QMainWindow是一个为用户提供主窗口程序的类&#xff0c;是许多应用程序的基础&#xff0c;包含的组件有&#xff1a; 菜单栏QMenuBar&#xff0c;一个主窗口最多只能有一个菜单栏&#xff1b;包含一个下拉菜单项的列表&#xff0c;这些菜单项由QAction动作类…

【git版本控制】| git版本控制操作命令(全)

文章目录一、简介二、工作模式1 集中式&#xff08;CVS、SVN&#xff09;2 分布式Git三、Git1 工作模式2 git工作流程3 工作区和版本库4 注意事项5 基本操作5.1 创建本地版本库5.2 初始化本地版本库5.3 .git目录的作用5.4 创建用户5.5 其他操作6 git分支7 常见警告8 免密登录9 …

interface接口--GO面向对象编程思想

一、interface接口 interface 是GO语言的基础特性之一。可以理解为一种类型的规范或者约定。它跟java&#xff0c;C# 不太一样&#xff0c;不需要显示说明实现了某个接口&#xff0c;它没有继承或子类或“implements”关键字&#xff0c;只是通过约定的形式&#xff0c;隐式的…

【C语言进阶】自定义类型:结构体,枚举,联合体

目录 1、结构体的声明 1.1 结构体基础知识 1.2 结构体的声明 1.3 特殊的声明 1.4 结构体的自引用 1.5 结构体变量的定义和初始化 1.6 结构体内存对齐 ​编辑1.7 修改默认对齐数 1.8 结构体传参 2. 位段 2.1 什么是位段 2.2 位段的内存分配 2.3 位段的跨平台问…

【owt-server】代码结构及新增一个agent

owt server 官方 5.0 仓库:代码结构 manage console manage api portal sip portal 与agent 并列 agent又有很多种类。 启动脚本 启动一个新的agent 比如streaming-agent streaming-agent )cd ${OWT_HOME}/s

分布式id

分布式id一 什么是分布式系统唯一ID二 分布式系统唯一ID的特点三 分布式系统唯一ID的实现方案3.1 基于UUID3.2 基于数据库自增id3.3 基于数据库集群模式3.4 基于Redis模式3.5 基于雪花算法&#xff08;Snowflake&#xff09;模式3.6 百度&#xff08;uid-generator&#xff09;…

Python爬虫数据到sqlite实例

参考链接:https://blog.csdn.net/qq_45775027/article/details/115319253最近需要使用到爬虫数据库&#xff0c;原文中作者有些没补齐&#xff0c;略作修改之后跑通了。主要修改&#xff1a;1.调整了数据获取的正则表达式&#xff1b;2. 改了一下数据库的table名和定义名字&…

基于Java+SpringBoot+vue+element实现前后端分离牙科诊所管理系统详细设计

基于JavaSpringBootvueelement实现前后端分离牙科诊所管理系统详细设计 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式 文章目…

【Linux】虚拟地址空间 --- 虚拟地址、空间布局、内存描述符、写时拷贝、页表…

该吃吃&#xff0c;该喝喝&#xff0c;遇事儿别往心上隔&#x1f60e; 文章目录一、虚拟地址空间1.虚拟地址的引出&#xff08;看不到物理地址&#xff0c;只能看看虚拟地址喽&#xff09;2.虚拟地址空间布局&#xff08;五个段&#xff09;3.感性理解一下虚拟地址空间&#xf…

【C++修炼之路】C++入门(上)

&#x1f451;作者主页&#xff1a;进击的安度因 &#x1f3e0;学习社区&#xff1a;进击的安度因&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C修炼之路 文章目录一、前言二、第一个 C 程序三、C 关键字(C98)四、命名空间1、命名空间的定义2、命名空间…

C++ Prime课后习题第一章编程

编程一个C程序&#xff0c;它显示您的姓名和地址。#include <iostream>int stonetolb(int); int main() {using namespace std;cout << "zzz ";cout << "闵行"<<endl;return 0; }编写一个程序&#xff0c;要求用户输入一个以long…

3台机器配置hadoop集群_Hadoop+Hbase 分布式集群架构

安装搭建Hadoop1、 配置说明本次集群搭建共三台机器&#xff0c;具体说明下&#xff1a;主机名IP说明nn01192.168.1.51DataNode、NodeManager、ResourceManager、NameNodedn01192.168.1.52DataNode、NodeManager、SecondaryNameNodedn02192.168.1.53DataNode、NodeManager2 、安…

基于浏览器的 PDF 编辑器:RAD PDF for ASP.NET

版本 3.34 改进的 PDF 收藏/投资组合支持和服务器 API 改进 Ω578867473功能更新 为更基本的 PDF 文件损坏/语法错误添加了更正添加了 PdfButtonField.NamedAction 属性添加了 PdfButtonField.IsNamedAction 属性添加 PdfButtonField() 构造函数 - PdfButtonFields 可以由服…

unity-常用组件实操案例

文章目录transform摄像机cameraskybox相机权重&#xff08;depth&#xff09;Audio sourcevideo playertransform 不但控制着组件的旋转、位置、缩放并且还控制着组件间的父子关系 using System; using System.Collections; using System.Collections.Generic; using UnityEn…

不锈钢企业如何利用APS排程软件提升管理效益?

保温杯一般是由陶瓷或不锈钢加上真空层做成的盛水容器&#xff0c;顶部有盖&#xff0c;密封严实&#xff0c;真空绝热层能使装在内部的水等液体延缓散热&#xff0c;以达到保温的目的。保温杯从保温瓶发展而来的&#xff0c;保温原理与保温瓶一样&#xff0c;只是人们为了方便…

Collection

面向对象语言对事物的体现都是以对象的形式&#xff0c;所以为了方便对多个对象的操作&#xff0c;就对对象进行存储&#xff0c;集合就是存储对象最常用的一种方式 数组和集合的不同&#xff1a; 数组长度是固定的&#xff1b;集合长度是可变的。 数组中可以存储基本数据类…

C#在控制台中打印进度条【同步和异步】

使用控制台打印进度条的简单方法。 有现成的IProgress接口进行操作&#xff1a; 实例&#xff1a; var prog new Progress<double>((theV > {Console.WriteLine($"Now the Progress&#xff1a;" COUNT / 10.0 * 100 "%" new string(#, COUN…

社科院与杜兰大学金融管理硕士---授人以鱼不如授人以渔,培养全新金融人才

古人云&#xff1a;“授人以鱼&#xff0c;三餐之需&#xff1b;授人以渔&#xff0c;终身之用”。都说职场入战场&#xff0c;一入职场就如履薄冰。走的每一步可能都影响着自己的职业生涯。在职场无烟的战争中&#xff0c;会慢慢发现差距一点点的被拉开了。金融是现代经济的血…

开发工具(二)基于Source Insight与Samba共享在windows中开发Linux工程

layout: post title: 开发工具&#xff08;二&#xff09;基于Source Insight与Samba共享在windows中开发Linux工程 description: 开发工具&#xff08;二&#xff09;基于Source Insight与Samba共享在windows中开发Linux工程 tag: 开发工具 文章目录资源共享流程说明Source In…