【C++八股-第16期】进程与线程 ②

❤ 10朵花 秒更下一期

alt

提纲:

👉 八股:

  1. 介绍一下管道的概念,及其实现原理

  2. Linux中一个线程占用多少内存?

  3. 32位系统访问4GB以上内存存在什么限制?

  4. 概述进程、线程、协程概念及区别?

  5. TLB是什么东西

  6. cache line是什么

  7. 互斥量能不能在进程中使用?

1. 介绍一下管道的概念,及其实现原理

管道(Pipe) 是 UNIX 和 Linux 系统中常用的进程间通信(IPC)机制之一,它允许一个进程将数据发送到另一个进程,即在同一时刻 两个进程之间只能进行单项通信,因此管道也被称为 半双工管道。

半双工:

  • 指同一时刻数据只能由一个进程流向另一个进程

管道的本质是一种特殊的文件

  • 匿名管道(Unnamed Pipe)只能用于有亲缘关系的进程间通信,例如父子进程。

  • 命名管道(Named Pipe 或 FIFO)可以在无亲缘关系的进程之间进行通信,通过路径名来标识。

管道的基本实现原理:

管道的实现原理基于内核缓冲区文件描述符

  • 内核缓冲区:

    • 当管道创建时,内核会分配一个缓冲区,用于在进程之间传递数据。
    • 这个缓冲区是一个先进先出(FIFO)的队列,确保数据按照写入的顺序被读取。
  • 文件描述符:

    • pipe系统调用创建一对文件描述符,一个用于读(read end)fd[0],一个用于写(write end)fd[1]fd[1]的输出是fd[0]的输入,管道是建立在内核之中的
    • 这两个文件描述符指向内核缓冲区的不同端,写描述符用于写入数据到缓冲区,读描述符用于从缓冲区读取数据。
  • 数据传输:

    • 写进程通过写描述符将数据写入内核缓冲区。
    • 读进程通过读描述符从内核缓冲区读取数据。
    • 当缓冲区满时,写操作将阻塞,直到有空间可写;当缓冲区为空时,读操作将阻塞,直到有数据可读。

alt

管道的创建和使用:

#include <unistd.h>
#include <iostream>
#include <cstring>

int main() {
    int fd[2];
    if (pipe(fd) == -1) {
        std::cerr << "Pipe failed" << std::endl;
        return 1;
    }

    pid_t pid = fork();
    if (pid == -1) {
        std::cerr << "Fork failed" << std::endl;
        return 1;
    }

    if (pid == 0) { // 子进程
        close(fd[1]); // 关闭写端
        char buffer[100];
        read(fd[0], buffer, sizeof(buffer)); // 从管道读取数据
        std::cout << "Child received: " << buffer << std::endl;
        close(fd[0]); // 关闭读端
    } else { // 父进程
        close(fd[0]); // 关闭读端
        const char *msg = "Hello from parent";
        write(fd[1], msg, strlen(msg) + 1); // 向管道写入数据
        close(fd[1]); // 关闭写端
    }

    return 0;
}

   

   

2. Linux中一个线程占用多少内存?

在Linux系统中,每个线程的默认栈大小通常为8MB

这意味着每个线程在创建时会预留最多8MB的虚拟内存空间用于其栈。

但是,这并不意味着每个线程一开始就实际使用了8MB的物理内存。

内存分配机制

Linux通过一种称为“按需分配”(lazy allocation)或“缺页分配”(demand paging)的机制来管理内存。以下是这种机制的工作原理:

  • 当创建一个新线程时,操作系统会为其栈预留一块虚拟内存区域。这块区域的默认大小通常为8MB。

  • 实际上,这块8MB的虚拟内存区域并不会立即全部映射到物理内存。当线程第一次访问栈中的某个地址时,才会触发缺页异常,操作系统会分配相应的物理内存页(通常为4KB)。

  • 因此,线程实际使用的物理内存仅略大于其实际需要的内存量。这种分配方式可以有效节约物理内存。

   

   

3. 32位系统访问4GB以上内存存在什么限制?

在不用任何额外技术的,正常情况下无法访问

在32位系统中,内存地址空间的限制源于地址总线的宽度。

32位系统的地址总线长度为32位,这意味着可以表示的内存地址范围是2^32,即4GB。具体来说:

  • 32位地址总线可以表示的最大内存空间是4GB(2^32字节)。

  • 操作系统和硬件通常需要保留一部分地址空间用于设备和系统用途,因此实际可用的物理内存通常略少于4GB。

PAE(Physical Address Extension)技术

使用 PAE技术 可以实现32位系统访问 4GB以上内存

PAE技术通过扩展内存地址总线来实现这一点:

  • PAE将内存地址从32位扩展到36位。
  • 36位地址总线可以表示的最大内存空间是64GB(2^36字节)。

   

   

4. 概述进程、线程、协程概念及区别?

  • 进程: 内核调度

    • 进程是操作系统中运行的程序的实例(操作系统会创建一个进程来运行你的程序代码)。
    • 进程包含了程序代码、数据、和所有必要的资源,如文件句柄、内存地址空间、执行上下文等。
    • 操作系统通过进程调度来管理CPU时间分配给各个进程。
  • 线程: 内核调度

    • 线程是进程中的一个执行单元,一个进程可以包含多个线程。
    • 它们共享进程的资源(如内存、文件句柄等)
    • 但有独立的执行上下文(如程序计数器、寄存器等)。
  • 协程: 程序自身调度

    • 协程是比线程更轻量化的用户态上下文切换机制。
    • 协程由程序自己调度管理,因为在用户态,所以切换速度比线程、进程快得多。
    • 协程只能在同一个线程内运行,不是并行执行。
    • 协程通过yieldresume等机制显式地让出和恢复控制权。

进程、线程、协程对比

特性 进程(Process) 线程(Thread) 协程(Coroutine)
从属关系 独立的运行实例 进程中的执行单元 线程中的执行单元
调度单位 系统资源调度的最小单位 CPU调度的最小单位 程序控制的最小单位
系统开销 系统开销大,切换时需要刷新TLB,获取新的地址空间 系统开销较小,只需切换硬件上下文和内核栈 系统开销更小,切换只涉及用户空间,无需内核干预
内存单元 独立的内存空间,包括代码段、数据段和堆 共享进程的内存空间,每个线程有自己的栈和寄存器组 共享线程的栈,无需多线程的锁机制
通信方式 消息队列、信号量、共享内存等 volatilewait()notify() 直接调用函数,无需多线程的锁机制
执行效率 相对较低,切换开销大 相对较高,切换开销小于进程 极高,切换开销极小
适用场景 多核、多机分布式应用 多核并行应用 高并发场景,单线程并发控制
其他特点 一个进程挂掉不会影响其他进程 一个线程挂掉会导致整个进程挂掉 不依赖内核,切换速度快,操作灵活

   

   

5. TLB是什么东西

TLB(Translation Lookaside Buffer,翻译后备缓冲)是现代计算机处理器中的一种高速缓存,用于加速虚拟地址到物理地址的转换过程。

作用

  • 提高地址转换效率: TLB存储最近使用的虚拟地址到物理地址的映射,从而避免每次访问内存都需要通过页表进行地址转换。大大减少虚拟地址到物理地址转换所需时间。

  • 减少了内存访问的延迟: TLB命中时,可以直接获取物理地址,避免多次访问内存进行页表查找。

工作原理示例

假设CPU要访问一个虚拟地址0xABCDEF

  • TLB查找: TLB命中

    • 先在TLB中查找虚拟地址0xABCDEF对应的页号。
    • 如果命中,则直接得到物理帧号,组合形成物理地址访问内存。
  • 页表查找: TLB未命中

    • 如果未命中,则需要访问页表获取物理帧号,最终使用得到的物理地址访问内存。
    • 通过页表查找到对应的物理帧号后,将这个映射关系加载到TLB中,以备下次快速查找。
  • TLB刷新:

    • 当发生上下文切换(即进程切换)时,TLB中的条目可能需要刷新,因为新的进程可能使用不同的虚拟地址空间。
    • 通常通过硬件机制或操作系统指令刷新TLB,确保新进程的地址转换正确。
CPU
 |  虚拟地址(0xABCDEF)
 v
+------------------+
|      TLB查找     |
+------------------+
 |             |
 |  命中		 | 未命中
 |             | 
 v             v
+-------+ 	 +------------------+
|物理地址| 	 |      页表查找     |
+-------+  	 +------------------+
		            |      |
		            | 得到物理地址
		            v
		       +----------+
		       | 物理地址 |
		       +----------+

   

   

6. cache line是什么

**缓存行(Cache Line)**是缓存(Cache)中的最小存储单元。

!大小: 缓存行的大小通常是固定的,常见的大小有32字节、64字节等。 !存储单位: 缓存中的数据以缓存行为单位存储和管理,缓存行包含若干个连续的字节。 !对齐: 缓存行通常是对齐的,即它的起始地址是缓存行大小的整数倍。

工作原理

  • 加载数据:

    • 当CPU访问某个内存地址时,如果该地址的数据不在缓存中(缓存未命中),缓存会加载包含该地址的整个缓存行到缓存中。
    • 例如,如果缓存行大小为64字节,当CPU访问地址0x1000,缓存会将0x1000到0x103F范围内的数据加载到一个缓存行中。
  • 数据访问:

    • 如果CPU再次访问0x1000到0x103F范围内的任一地址,缓存可以直接提供数据(缓存命中),无需访问主存。
    • 这样大大提高了访问速度,因为访问缓存比访问主存快得多。

缓存行的重要性

  • 局部性原理:

    • 时间局部性: 最近被访问的数据很可能再次被访问。
    • 空间局部性: 与某个地址相邻的地址很可能在不久后被访问。
    • 缓存行利用空间局部性,将连续的数据一次性加载到缓存中,提高了访问效率。
  • 伪共享(False Sharing):

    • 当多个处理器的线程在同一个缓存行的不同部分上操作时,会导致缓存一致性协议频繁更新缓存行,降低性能。
    • 通过适当的内存对齐和避免共享缓存行的数据,可以减少伪共享的影响。

   

   

7. 互斥量能不能在进程中使用?

**互斥量(Mutex)**可以在进程中使用,用于解决不同进程或同一进程内多个线程之间的资源竞争问题。

  • 进程间的互斥:

    • 不同的进程之间可能会竞争共享资源,例如文件、共享内存等。在这种情况下,需要互斥量来同步访问,防止数据竞争和数据不一致问题。
    • 进程间的互斥量通常使用系统提供的IPC(进程间通信)机制,如System V信号量或POSIX信号量。
  • 进程内的互斥:

  • 一个进程中可以包含多个线程,这些线程之间也可能会竞争共享资源,如全局变量、堆数据等。为了防止多个线程同时修改共享数据,导致数据不一致,需要使用互斥量进行同步。

  • 进程内的互斥量可以使用标准的互斥锁,如std::mutex(C++11)或pthread_mutex_t(POSIX线程库)。

   

   

全部评论
必须花花
点赞 回复 分享
发布于 07-18 18:30 河南

相关推荐

投递蔚来等公司10个岗位 蔚来求职进展汇总
点赞 评论 收藏
分享
19 8 评论
分享
牛客网
牛客企业服务