文章

操作系统高频总结一

进程fork后不同进程会共享哪些资源

在 UNIX 和 Linux 系统中,当使用 fork() 创建一个子进程时,子进程会复制父进程的大部分资源,但在某些情况下,父子进程之间会共享特定资源。

以下是 fork() 后父子进程之间共享和独立的资源分类:


1. 子进程与父进程之间的资源分配

1.1 独立的资源

  • 进程 ID (PID):
    • 子进程有自己独立的进程 ID。
  • 父子关系:
    • 子进程的父进程 ID (PPID) 设置为调用 fork() 的父进程的进程 ID。
  • 用户态栈和堆:
    • 子进程会复制父进程的用户态栈和堆,但两者是独立的,修改一方不会影响另一方。
  • 文件描述符的偏移:
    • 文件描述符表是共享的(见下),但文件偏移量是独立的。
  • 信号处理:
    • 子进程继承父进程的信号处理配置,但两者是独立的。
  • 内存:
    • 子进程会复制父进程的内存空间(通过写时复制实现)。父子进程对各自的内存修改不会互相影响,除非显式使用共享内存。

1.2 共享的资源

  • 文件描述符:
    • 父子进程共享文件描述符表。文件描述符的打开状态(例如文件位置、访问模式等)在父子进程之间是共享的。
    • 如果一个进程关闭文件描述符,另一个进程尝试访问该文件描述符会失败。
  • 打开的文件:
    • 打开的文件由内核对象表示,文件描述符引用这些对象,因此父子进程共享同一个打开文件的内核对象。
    • 例如,父子进程可以同时访问同一个文件,且共享文件偏移量(除非显式地设置独立的偏移量)。
  • 信号量:
    • POSIX 信号量或系统 V 信号量是共享的。
  • 共享内存:
    • 如果父进程使用 shmget(System V)或 mmap(POSIX)创建了共享内存区域,子进程可以继承并与父进程共享这些区域。
  • 文件锁:
    • 父子进程共享文件锁。
  • 文件系统信息:
    • 例如工作目录和 umask 值。
  • 消息队列:
    • 系统 V 消息队列或 POSIX 消息队列在父子进程之间是共享的。

2. 写时复制 (Copy-On-Write, COW)

  • 在现代操作系统中,fork() 并不会真正复制父进程的整个内存空间,而是通过写时复制机制共享物理内存页。
  • 只有当父进程或子进程尝试修改共享内存页时,内核才会为该进程分配独立的内存页。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
    int var = 42; // 父子进程共享,但写时复制
    pid_t pid = fork();

    if (pid == 0) {
        // 子进程
        printf("Child: var = %d\n", var);
        var = 100;
        printf("Child (after modification): var = %d\n", var);
    } else {
        // 父进程
        printf("Parent: var = %d\n", var);
        sleep(1); // 确保子进程先运行
        printf("Parent (after child modification): var = %d\n", var);
    }

    return 0;
}

输出示例:

1
2
3
4
Parent: var = 42
Child: var = 42
Child (after modification): var = 100
Parent (after child modification): var = 42

子进程对 var 的修改不会影响父进程,表明内存是独立的。


3. 父子进程间的独立性和共享总结表

资源类型独立/共享说明
PID独立子进程有独立的进程 ID。
文件描述符表共享文件描述符共享,但文件偏移量可以独立。
内存空间独立(COW)写时复制后独立,只有共享内存区域是共享的。
信号处理设置独立父子进程各自管理自己的信号处理。
信号量/消息队列共享系统 V 和 POSIX 信号量、消息队列是共享的。
打开文件状态共享打开的文件及其锁定状态是共享的。
当前工作目录和 umask共享当前工作目录和文件创建掩码由父子进程共享。
环境变量独立子进程会复制父进程的环境变量,但修改不影响父进程。
文件锁共享文件锁是共享的,适用于系统 V 和 POSIX 文件锁机制。

4. 如何避免资源共享带来的问题

  • 显式关闭文件描述符:父子进程如果不需要共享某些文件描述符,可以在其中一方显式关闭。
  • 使用 O_CLOEXEC:创建文件描述符时,可以设置 O_CLOEXEC 标志,防止文件描述符在 exec 系列函数调用后继承到子进程。
  • 同步访问:对于共享资源(如共享内存或文件锁),需要使用适当的同步机制(如信号量或互斥锁)。
  • 避免无意义的 fork():如果共享资源可能引发竞争问题,应考虑线程模型或其他进程间通信方式(如管道、消息队列等)。

总之,理解 fork() 之后的资源共享和独立特性,对于编写可靠的多进程程序至关重要。

死锁的概念,进程调度算法怎么解决死锁

死锁的概念

死锁(Deadlock) 是指两个或多个进程在执行过程中因竞争资源而互相等待,从而导致无法继续执行的状态。死锁通常发生在以下四个必要条件同时成立时:

  1. 互斥条件(Mutual Exclusion):某些资源只能被一个进程独占。
  2. 持有并等待(Hold and Wait):进程已经持有了至少一个资源,同时又在等待额外的资源,而这些资源被其他进程占有。
  3. 不可抢占(No Preemption):资源不能被强制剥夺,只能由持有它的进程释放。
  4. 循环等待(Circular Wait):存在一个进程集合,其中每个进程都在等待另一个进程占用的资源,形成一个环。

当这四个条件同时满足时,就可能发生死锁。


进程调度算法如何解决死锁

进程调度算法可以通过预防避免检测与恢复这三种方法来解决或缓解死锁问题。


1. 死锁预防

通过破坏死锁的必要条件,防止死锁的发生:

  • 破坏互斥条件:尽可能让资源支持共享访问,例如将某些资源虚拟化。
  • 破坏持有并等待条件:要求进程在进入临界区之前一次性申请所有需要的资源,或者在申请资源时不允许持有其他资源。
  • 破坏不可抢占条件:允许调度器强制剥夺资源,并分配给其他需要的进程。
  • 破坏循环等待条件:对所有资源进行统一编号,并要求进程按照编号的顺序请求资源。

2. 死锁避免

利用算法动态分配资源,在分配前检测是否会导致死锁:

  • 银行家算法(Banker’s Algorithm):
    • 检查资源分配是否处于安全状态。
    • 安全状态:系统能够按照某种顺序完成所有进程的资源请求,不会发生死锁。
    • 如果分配资源后系统进入不安全状态,则拒绝分配。

3. 死锁检测与恢复

如果无法预防或避免死锁,可以定期检查系统是否发生了死锁,并采取措施恢复:

  • 死锁检测
    • 使用资源分配图(Resource Allocation Graph, RAG)或等待图(Wait-For Graph, WFG)分析是否存在循环依赖。
    • 检测算法需要分析当前资源分配和进程状态,识别死锁。
  • 死锁恢复
    • 资源剥夺:强制回收某些进程持有的资源,并重新分配给其他进程。
    • 终止进程:终止部分或全部死锁进程,释放资源。
    • 回滚进程:将死锁进程回滚到某个安全状态,以解除死锁。

示例:结合调度算法解决死锁

好的!我们来详细解释一下银行家算法的示例,并逐步分析整个过程,让你更清楚地理解如何通过该算法避免死锁。


示例问题描述

假设有以下资源和进程的分配情况:

  1. 系统有 3 类资源 $A, B, C$,总资源数量分别为 $A: 6, B: 3, C: 4$。
  2. 系统中有 3 个进程 $P_1, P_2, P_3$,每个进程对资源的最大需求、当前分配的资源情况如下表:
初始状态表
进程已分配(Allocation)最大需求(Max)剩余需求(Need = Max - Allocation)
$P_1$$A:1, B:0, C:1$$A:2, B:1, C:2$$A:1, B:1, C:1$
$P_2$$A:1, B:1, C:0$$A:3, B:2, C:1$$A:2, B:1, C:1$
$P_3$$A:1, B:1, C:1$$A:2, B:2, C:2$$A:1, B:1, C:1$

系统当前的可用资源(Available)为:

$Available = { A: 3, B: 1, C: 2 }$


问题:如果 $P_2$ 请求资源 $A:1, B:0, C:1$,是否会导致死锁?

我们需要通过银行家算法来判断系统在满足 $P_2$ 的请求后,是否还处于安全状态


银行家算法步骤

1. 检查请求是否满足

检查 $P_2$ 的请求 $Request = { A:1, B:0, C:1 }$ 是否小于等于以下两个条件:

  • Need:$P_2$ 的剩余需求
  • Available:系统当前可用资源

$Request \leq Need \quad \text{且} \quad Request \leq Available$

  • $Need = { A:2, B:1, C:1 }$,显然 $Request \leq Need$。
  • $Available = { A:3, B:1, C:2 }$,显然 $Request \leq Available$。

因此,可以满足 $P_2$ 的请求,进入下一步。


2. 假设分配资源,更新资源状态

假设系统分配资源 $A:1, B:0, C:1$ 给 $P_2$,更新状态如下:

  • 已分配(Allocation):增加 $P_2$ 的已分配资源:

    $P_2.\text{Allocation} = { A:1+1, B:1+0, C:0+1 } = { A:2, B:1, C:1 }$

  • 剩余需求(Need):减少 $P_2$ 的需求:

    $P_2.\text{Need} = { A:2-1, B:1-0, C:1-1 } = { A:1, B:1, C:0 }$

  • 可用资源(Available):减少系统的可用资源:

    $Available = { A:3-1, B:1-0, C:2-1 } = { A:2, B:1, C:1 }$

更新后的状态如下表:

进程已分配(Allocation)最大需求(Max)剩余需求(Need = Max - Allocation)
$P_1$$A:1, B:0, C:1$$A:2, B:1, C:2$$A:1, B:1, C:1$
$P_2$$A:2, B:1, C:1$$A:3, B:2, C:1$$A:1, B:1, C:0$
$P_3$$A:1, B:1, C:1$$A:2, B:2, C:2$$A:1, B:1, C:1$

系统当前的可用资源为:

$Available = { A:2, B:1, C:1 }$


3. 检查是否处于安全状态

尝试找到一个安全序列,即系统能够依次满足所有进程的资源需求。

  • Step 1:检查 $P_1$:

    $P_1.\text{Need} = { A:1, B:1, C:1 } \leq Available = { A:2, B:1, C:1 }$

    满足需求,假设 $P_1$ 完成后,释放资源:

    $Available = Available + P_1.\text{Allocation} = { A:2+1, B:1+0, C:1+1 } = { A:3, B:1, C:2 }$

  • Step 2:检查 $P_3$:

    $P_3.\text{Need} = { A:1, B:1, C:1 } \leq Available = { A:3, B:1, C:2 }$

    满足需求,假设 $P_3$ 完成后,释放资源:

    $Available = Available + P_3.\text{Allocation} = { A:3+1, B:1+1, C:2+1 } = { A:4, B:2, C:3 }$

  • Step 3:检查 $P_2$:

    $P_2.\text{Need} = { A:1, B:1, C:0 } \leq Available = { A:4, B:2, C:3 }$

    满足需求,假设 $P_2$ 完成后,释放资源:

    $Available = Available + P_2.\text{Allocation} = { A:4+2, B:2+1, C:3+1 } = { A:6, B:3, C:4 }$

所有进程均能完成,系统处于安全状态


结论

可以安全分配 $A:1, B:0, C:1$ 给 $P_2$,不会导致死锁。


总结

通过正确选择策略(预防、避免或检测与恢复),进程调度算法可以有效解决或缓解死锁问题,具体选择取决于系统需求和实现复杂性。

线程和进程的区别、应用场景

线程和进程是计算机程序的两个执行单位,了解它们的区别和应用场景对高效编程和系统设计非常重要。


线程和进程的区别

1. 定义

  • 进程(Process)
    • 操作系统中程序执行的基本单位。
    • 每个进程拥有独立的内存空间,包括代码段、数据段和堆栈段。
    • 一个进程可以包含多个线程。
  • 线程(Thread)
    • 进程中的一个执行单元,是程序执行的最小单位。
    • 线程共享进程的资源(如内存、文件描述符),但有独立的执行栈和寄存器。

2. 资源占用

  • 进程:
    • 每个进程都有独立的资源,切换进程时需要保存和恢复上下文(上下文切换开销较大)。
    • 消耗更多的系统资源。
  • 线程:
    • 线程之间共享进程的资源,切换时开销较小。
    • 更轻量级,创建和销毁线程的成本低于进程。

3. 通信方式

  • 进程通信:
    • 需要通过操作系统提供的机制(如管道、消息队列、共享内存、信号等)进行通信。
    • 进程间通信(IPC)复杂且性能较低。
  • 线程通信:
    • 线程共享内存,通信更简单(如使用全局变量)。
    • 需要同步机制(如互斥锁、信号量)防止资源竞争。

4. 独立性

  • 进程:
    • 进程之间相互独立,一个进程的崩溃不会影响其他进程。
  • 线程:
    • 同一进程内的线程相互依赖,一个线程的崩溃可能导致整个进程终止。

应用场景

进程的应用场景

  1. 高隔离要求
    • 进程之间的内存空间独立,适用于需要高隔离的场景,如浏览器的多标签页(每个标签页一个独立进程)。
  2. 分布式系统
    • 在分布式系统中,各服务通常运行在独立的进程中,易于扩展和维护。
  3. 系统稳定性要求高
    • 一个模块的崩溃不应影响其他模块,比如数据库管理系统的独立守护进程。
  4. 跨语言或不同技术栈集成
    • 比如用不同语言开发的微服务,运行在各自的进程中。

线程的应用场景

  1. 并发任务
    • 同一任务中多个部分需要并发执行,如 Web 服务器处理多个客户端请求。
  2. 轻量级任务
    • 线程的创建和销毁开销低,适合处理轻量任务,如多线程下载。
  3. 需要共享数据
    • 线程间共享内存,适用于需要高效共享数据的场景,如多线程排序、数据处理等。
  4. 实时响应
    • 在需要快速响应的应用中,比如 GUI 应用中主线程负责用户界面,后台线程负责处理耗时操作。

总结

特性进程线程
定义独立的程序执行单元进程内的执行单元
内存空间独立分配共享进程内存
开销大,创建和切换较慢小,创建和切换较快
通信复杂度高(需使用 IPC)低(共享内存即可)
稳定性一个进程崩溃不影响其他进程一个线程崩溃影响整个进程
适用场景高隔离要求、高稳定性任务并发、轻量任务

在选择进程或线程时,需要根据实际需求权衡性能、隔离性和复杂性。

多线程中各种锁,读写锁,互斥锁

在多线程编程中,锁是用来管理线程对共享资源的访问的关键机制。根据场景和需求,常用的锁包括互斥锁、读写锁等。以下是对各种锁的详细介绍。


1. 互斥锁(Mutex, Mutual Exclusion Lock)

特点

  • 用于保护临界区,确保同一时刻只有一个线程能够访问共享资源。
  • 线程需要获取锁后才能进入临界区,离开时需释放锁。

实现方式

  • 在C++中,可以使用 std::mutexstd::unique_lock

适用场景

  • 当只有一个线程可以对共享资源进行读写时。
  • 简单的线程同步场景。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;

void print_thread(int id) {
    mtx.lock();
    std::cout << "Thread " << id << " is working" << std::endl;
    mtx.unlock();
}

int main() {
    std::thread t1(print_thread, 1);
    std::thread t2(print_thread, 2);
    t1.join();
    t2.join();
    return 0;
}

2. 递归锁(Recursive Mutex)

特点

  • 同一线程可以多次获取同一把锁,而不会造成死锁。
  • 每次加锁必须对应一次解锁。

实现方式

  • 在C++中,可以使用 std::recursive_mutex

适用场景

  • 当需要在同一线程中递归调用带锁的函数时。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <thread>
#include <mutex>

std::recursive_mutex rec_mtx;

void recursive_function(int count) {
    if (count <= 0) return;
    rec_mtx.lock();
    std::cout << "Recursion depth: " << count << std::endl;
    recursive_function(count - 1);
    rec_mtx.unlock();
}

int main() {
    std::thread t1(recursive_function, 5);
    t1.join();
    return 0;
}

3. 读写锁(Read-Write Lock, Shared Mutex)

特点

  • 支持多读单写:
    • 多个线程可以同时读取数据(读锁共享)。
    • 写操作是独占的,写锁会阻止其他读写操作。
  • 在读多写少的场景下性能优于互斥锁。

实现方式

  • 在C++中,可以使用 std::shared_mutexstd::shared_lock(C++17引入)。

适用场景

  • 当存在大量读取操作,且写操作较少时。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <vector>

std::shared_mutex rw_lock;
std::vector<int> data;

void read_data() {
    std::shared_lock<std::shared_mutex> lock(rw_lock);
    for (int num : data) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
}

void write_data(int value) {
    std::unique_lock<std::shared_mutex> lock(rw_lock);
    data.push_back(value);
}

int main() {
    std::thread writer1(write_data, 1);
    std::thread writer2(write_data, 2);
    std::thread reader1(read_data);
    std::thread reader2(read_data);

    writer1.join();
    writer2.join();
    reader1.join();
    reader2.join();
    return 0;
}

4. 自旋锁(Spin Lock)

特点

  • 线程在获取锁时会不断循环检查是否可以获取,而不是进入休眠。
  • 比较适合锁的持有时间短的场景,避免线程上下文切换的开销。

实现方式

  • 可以使用 C++ 的 std::atomic_flag 实现。

适用场景

  • 临界区执行时间短,且线程频繁获取锁。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <thread>
#include <atomic>

std::atomic_flag spin_lock = ATOMIC_FLAG_INIT;

void critical_section(int id) {
    while (spin_lock.test_and_set(std::memory_order_acquire));
    std::cout << "Thread " << id << " is working" << std::endl;
    spin_lock.clear(std::memory_order_release);
}

int main() {
    std::thread t1(critical_section, 1);
    std::thread t2(critical_section, 2);
    t1.join();
    t2.join();
    return 0;
}

5. 条件变量(Condition Variable)

特点

  • 用于线程之间的同步,通常配合互斥锁使用。
  • 线程可以等待某个条件满足后继续执行。

实现方式

  • 在C++中,可以使用 std::condition_variable

适用场景

  • 需要线程等待某种条件(如队列不为空)再继续执行时。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
#include <thread>
#include <condition_variable>
#include <queue>

std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;

void producer() {
    for (int i = 0; i < 5; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        data_queue.push(i);
        cv.notify_one(); // 通知消费者
    }
}

void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [] { return !data_queue.empty(); }); // 等待队列非空
        int value = data_queue.front();
        data_queue.pop();
        std::cout << "Consumed: " << value << std::endl;
        if (value == 4) break;
    }
}

int main() {
    std::thread prod(producer);
    std::thread cons(consumer);
    prod.join();
    cons.join();
    return 0;
}

总结

锁类型特点适用场景
互斥锁简单互斥独占资源的简单场景
递归锁支持同一线程多次加锁递归调用中的同步
读写锁多读单写读多写少的场景
自旋锁忙等待,占用 CPU临界区执行时间短的高频操作
条件变量等待条件满足再继续线程间需要条件同步的复杂场景

根据实际需求选择合适的锁,能有效提升程序性能并避免死锁。

内存池

内存池(Memory Pool) 是一种内存管理技术,用于高效地分配和释放内存块。它通过预分配一块大的内存区域(内存池),然后按照需要分配较小的内存块,避免频繁调用系统的动态内存分配函数(如 mallocnew)。


内存池的原理

  1. 预分配内存
    • 一次性向系统申请一块较大的内存(称为内存池)。
    • 内存池被分割成多个固定大小的小内存块(chunks)。
  2. 分配内存
    • 当程序需要内存时,从内存池中直接分配一个空闲的内存块,而不是调用系统的分配函数。
  3. 释放内存
    • 内存块释放时,内存池将其标记为空闲,方便后续重新使用,而不将内存归还给操作系统。
  4. 效率优化
    • 通过减少系统调用(如 malloc/free),降低分配和释放内存的开销。
    • 避免内存碎片问题,因为内存池通常管理固定大小的内存块。

内存池的优缺点

优点

  • 分配效率高:减少了调用系统内存分配函数的次数,分配和释放速度更快。
  • 避免内存碎片:固定大小的内存块分配,避免了碎片化。
  • 适用于小内存对象:对小而频繁分配的对象特别高效。
  • 可控性强:内存池的分配和释放逻辑完全由程序员控制。

缺点

  • 灵活性较差:通常为固定大小的内存块,不适合动态大小的内存需求。
  • 初始内存开销:需要预分配较大的内存块。
  • 管理复杂:需要实现分配和释放的管理逻辑。

内存池的应用场景

  1. 实时系统
    • 需要高效的内存分配,避免系统调用的不可预测性。
    • 如嵌入式系统、网络通信系统等。
  2. 高频内存分配场景
    • 频繁分配和释放小对象的应用。
    • 如游戏引擎中的对象管理、内存缓冲池。
  3. 对象复用
    • 对象池或连接池中,为避免频繁创建和销毁对象,使用内存池存储和复用对象。
  4. 网络通信
    • 缓冲区管理,如消息传输的缓冲区分配和释放。

内存池的实现

以下是一个简单的 C++ 内存池实现,管理固定大小的内存块:

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <iostream>
#include <vector>
#include <cstddef> // for std::size_t

class MemoryPool {
public:
    MemoryPool(std::size_t blockSize, std::size_t blockCount)
        : m_blockSize(blockSize), m_blockCount(blockCount) {
        allocatePool();
    }

    ~MemoryPool() {
        delete[] m_pool;
    }

    void* allocate() {
        if (m_freeList == nullptr) {
            throw std::bad_alloc();
        }
        void* freeBlock = m_freeList;
        m_freeList = *(reinterpret_cast<void**>(m_freeList));
        return freeBlock;
    }

    void deallocate(void* block) {
        *(reinterpret_cast<void**>(block)) = m_freeList;
        m_freeList = block;
    }

private:
    void allocatePool() {
        m_pool = new char[m_blockSize * m_blockCount];
        m_freeList = m_pool;

        char* current = m_pool;
        for (std::size_t i = 0; i < m_blockCount - 1; ++i) {
            *(reinterpret_cast<void**>(current)) = current + m_blockSize;
            current += m_blockSize;
        }
        *(reinterpret_cast<void**>(current)) = nullptr; // Last block
    }

    std::size_t m_blockSize;
    std::size_t m_blockCount;
    char* m_pool;
    void* m_freeList;
};

int main() {
    MemoryPool pool(32, 10); // 每块大小 32 字节,总共 10 块

    void* block1 = pool.allocate();
    void* block2 = pool.allocate();

    std::cout << "Block1: " << block1 << std::endl;
    std::cout << "Block2: " << block2 << std::endl;

    pool.deallocate(block1);
    pool.deallocate(block2);

    return 0;
}

实现说明

  1. 初始化阶段
    • 内存池创建时,预分配 blockSize * blockCount 的内存。
    • 初始化一个空闲链表,每个内存块存储指向下一个空闲块的指针。
  2. 分配内存
    • 从空闲链表中取出一个内存块,并将 m_freeList 指向下一个空闲块。
  3. 释放内存
    • 将释放的内存块重新插入空闲链表的头部。
  4. 效率
    • 内存分配和释放仅需修改指针,开销非常小。

扩展功能

  1. 支持不同大小的内存块
    • 可实现多个内存池,每个池管理不同大小的内存块。
  2. 线程安全
    • 使用锁(如 std::mutex)或原子操作,使内存池支持多线程环境。
  3. 动态扩展
    • 当内存池中的块用尽时,自动申请新的内存池。

总结

内存池通过高效管理内存分配和释放,可以显著提升程序性能,特别是在需要频繁分配和释放小对象的场景。然而,其实现需要程序员深刻理解内存管理的细节,并根据具体需求设计合适的内存池结构。

本文由作者按照 CC BY 4.0 进行授权