Table of contents
Open Table of contents
258. 0.1 + 0.2 !== 0.3
259. 线程 vs 进程
线程和进程是操作系统中管理任务执行的两个基本概念。
定义
-
进程(Process):
- 定义:进程是一个正在运行的程序实例,它包含了程序代码和当前活动。每个进程都有自己的内存空间、文件描述符等系统资源。
- 独立性:进程之间是独立的,它们有各自的地址空间,一个进程的崩溃不会影响另一个进程。
- 开销:创建和销毁进程的开销较大,因为需要分配和回收大量资源。
-
线程(Thread):
- 定义:线程是进程内的一个执行流。一个进程可以包含多个线程,它们共享进程的资源(如内存、文件句柄等),但每个线程有自己的栈和寄存器。
- 共享资源:线程之间可以直接共享进程的资源,因此通信和数据共享更加高效。
- 开销:创建和销毁线程的开销相对较小,因为线程之间共享大量资源。
比较
特性 | 进程 | 线程 |
---|---|---|
地址空间 | 独立的 | 共享的 |
资源共享 | 不共享 | 共享 |
创建开销 | 较大 | 较小 |
通信开销 | 通过 IPC(管道、消息队列等) | 直接共享内存 |
独立性 | 独立 | 非独立,同一进程的线程影响相互 |
崩溃影响 | 一个进程崩溃不会影响其他进程 | 一个线程崩溃可能导致整个进程崩溃 |
使用场景 | 独立应用程序 | 同一应用的多个任务 |
适用场景
-
进程:
- 需要完全独立的运行环境。
- 需要更好的隔离性和安全性。
- 进程间通信开销不大或可以接受。
-
线程:
- 需要频繁的数据共享和通信。
- 需要更轻量级的并发执行。
- 任务之间相互依赖较多,且在同一进程内可以处理。
总结
- 进程:适用于需要高隔离性和独立性,且资源较为充足的场景。进程之间通过 IPC(进程间通信)进行数据交换,创建和销毁的开销较大。
- 线程:适用于需要轻量级并发执行和频繁数据共享的场景。线程之间直接共享进程内的资源,创建和销毁的开销较小,但线程之间的错误处理需要更加谨慎。
260. 内存中的堆(Heap)和栈(Stack)
栈(Stack)
特点
- 内存分配:栈内存由编译器自动管理。当函数被调用时,其局部变量、函数参数和返回地址等信息会被压入栈中。当函数调用结束时,这些数据会从栈中弹出。
- 存储数据类型:通常用于存储局部变量和函数调用信息。
- 内存管理:栈的内存分配和释放速度快,因为其采用的是后进先出(LIFO, Last In First Out)原则。
- 生命周期:栈上的数据在其所属的函数调用结束时会自动销毁。
- 大小限制:栈的大小通常较小,由操作系统设定,过大的数据存储可能导致栈溢出(stack overflow)。
void function() {
int x = 10; // x 在栈上分配
}
堆(Heap)
特点
- 内存分配:堆内存由程序员手动管理,使用动态内存分配函数(如 在 C 语言中
malloc
、free
,在 C++ 语言中new
、delete
,或在 JavaScript 中new
、delete
)进行分配和释放。 - 存储数据类型:通常用于存储需要在函数调用结束后仍然存在的数据,或者需要在运行时决定大小的数据。
- 内存管理:堆的内存分配和释放较为复杂,可能会导致碎片化。程序员需要显式地释放不再使用的内存,否则会造成内存泄漏。
- 生命周期:堆上的数据在程序员显式释放内存之前会一直存在。
- 大小限制:堆的大小通常较大,由系统的虚拟内存大小限制。
void function() {
int* x = (int*)malloc(sizeof(int)); // x 在堆上分配
*x = 10;
free(x); // 手动释放堆内存
}
比较
特性 | 栈(Stack) | 堆(Heap) |
---|---|---|
内存分配 | 由编译器自动管理 | 由程序员手动管理 |
存储数据类型 | 局部变量、函数参数、返回地址 | 动态分配的数据,需要在函数外部存在的数据 |
分配/释放速度 | 快(LIFO 原则) | 慢(需要查找合适的内存块) |
内存管理 | 自动(函数结束时自动释放) | 手动(需要程序员显式释放) |
生命周期 | 短暂(函数调用结束时释放) | 由程序员控制 |
碎片化 | 无(连续分配) | 可能有(内存块的分配和释放导致) |
大小限制 | 较小(由操作系统设定) | 较大(由系统虚拟内存限制) |
适用场景
- 栈:适用于局部变量、函数调用和小型短期存储的数据。因为栈的分配和释放速度快且不需要程序员管理。
- 堆:适用于需要动态分配内存的大型数据或需要在函数调用结束后仍然存在的数据。因为堆允许在运行时决定内存的大小,但需要程序员显式管理内存的分配和释放。