Linux学习笔记4:进程和线程的区别
1. 引言
又是摸鱼几天~
在 Linux 系统编程中,进程(Process)与线程(Thread)是并发编程的两大基石。
2. 进程与线程的基本概念
2.1 进程
进程是操作系统分配资源的最小单位。每个进程都拥有独立的地址空间、文件描述符表、信号处理函数等资源。在 Linux 中,内核通过 task_struct 结构体描述每一个进程。进程间相互隔离,一个进程崩溃通常不会直接导致另一个进程崩溃。
2.2 线程
线程是操作系统调度的最小单位。一个进程内部可以包含多个线程,这些线程共享进程的地址空间、文件描述符等大部分资源,但拥有独立的栈、寄存器状态和程序计数器。在 Linux 中,线程以内核轻量级进程(LWP)的形式实现,通过 clone() 系统调用创建,指定共享哪些资源。
3. 深入对比:进程 vs 线程
| 对比维度 | 进程 | 线程 |
|---|---|---|
| 资源拥有 | 独立地址空间、独立资源 | 共享进程地址空间及资源 |
| 创建/销毁开销 | 大(需复制页表、文件描述符等) | 小(仅需分配栈、寄存器上下文) |
| 上下文切换 | 代价较高(切换页表、刷新 TLB) | 代价较低(同进程下切换无需切换地址空间) |
| 通信方式 | 管道、FIFO、共享内存、消息队列、Socket 等 | 可直接读写共享内存,配合锁/信号量 |
| 隔离性与健壮性 | 强隔离,一个进程崩溃不影响其他 | 弱隔离,一个线程崩溃可能拖垮整个进程 |
| 适合场景 | IO 密集型、多核并行、需要高可靠性 | 计算密集型、高并发网络服务、数据共享频繁 |
4. Linux 中的进程与线程实现
在 Linux 内核层,没有专门的“线程”数据结构,所有执行实体都通过 task_struct 表示。创建进程使用 fork(),创建线程通常使用 POSIX 线程库(pthread),底层会调用 clone() 并设置恰当的共享标志。例如,使用 pthread_create() 时,clone() 的参数会让新任务与父任务共享内存空间、文件系统信息等,从而实现线程的语义。
下面是一个简单的 pthread 示例:
#include <stdio.h>
#include <pthread.h>
void *thread_func(void *arg) {
printf("Hello from thread!\n");
return NULL;
}
int main() {
pthread_t tid;
printf("Main process pid: %d\n", getpid());
pthread_create(&tid, NULL, thread_func, NULL);
pthread_join(tid, NULL);
return 0;
}
getpid() 在进程中返回进程 ID,而在线程中(同一进程)返回的仍然是进程 ID;获取线程 ID 需要使用 pthread_self() 或 gettid() 系统调用。
5. 调度与上下文切换
Linux 的 CFS(完全公平调度器)以调度实体为单位进行调度,线程就是调度实体。当 CPU 从一个线程切换到另一个线程时,涉及保存当前寄存器、栈指针、程序计数器,并恢复下一个线程的状态。
- 进程间上下文切换:还需要切换地址空间,包括页表、刷新 TLB 缓存,开销明显高于线程切换。
- 线程上下文切换:由于共享同一地址空间,这部分操作被省略,因此线程切换更快。
6. 多线程编程的注意事项
虽然线程轻量、通信便捷,但编写正确的多线程程序需要注意以下几点:
- 数据竞争:共享数据的访问必须同步,使用互斥锁(
pthread_mutex_t)、读写锁、自旋锁等。 - 死锁:避免嵌套加锁顺序不一致、持有锁时调用可能阻塞的函数等。
- 线程安全:系统库函数(如
strtok、gmtime)可能内部使用静态缓冲区,需改用可重入版本(strtok_r、gmtime_r)。 - 惊群效应:多个线程同时等待同一事件时的唤醒风暴,可采用条件变量配合队列或使用
SO_REUSEPORT缓解。 - 资源清理:线程退出时应确保释放持有的锁及堆内存,推荐使用线程局部存储(TLS)或 RAII 风格封装。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)