Linux学习笔记4:进程和线程的区别

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");returnNULL;}intmain(){pthread_ttid;printf("Main process pid: %d\n",getpid());pthread_create(&tid,NULL,thread_func,NULL);pthread_join(tid,NULL);return0;}

getpid()在进程中返回进程 ID,而在线程中(同一进程)返回的仍然是进程 ID;获取线程 ID 需要使用pthread_self()gettid()系统调用。

5. 调度与上下文切换

Linux 的 CFS(完全公平调度器)以调度实体为单位进行调度,线程就是调度实体。当 CPU 从一个线程切换到另一个线程时,涉及保存当前寄存器、栈指针、程序计数器,并恢复下一个线程的状态。

  • 进程间上下文切换:还需要切换地址空间,包括页表、刷新 TLB 缓存,开销明显高于线程切换。
  • 线程上下文切换:由于共享同一地址空间,这部分操作被省略,因此线程切换更快。

6. 多线程编程的注意事项

虽然线程轻量、通信便捷,但编写正确的多线程程序需要注意以下几点:

  1. 数据竞争:共享数据的访问必须同步,使用互斥锁(pthread_mutex_t)、读写锁、自旋锁等。
  2. 死锁:避免嵌套加锁顺序不一致、持有锁时调用可能阻塞的函数等。
  3. 线程安全:系统库函数(如strtokgmtime)可能内部使用静态缓冲区,需改用可重入版本(strtok_rgmtime_r)。
  4. 惊群效应:多个线程同时等待同一事件时的唤醒风暴,可采用条件变量配合队列或使用SO_REUSEPORT缓解。
  5. 资源清理:线程退出时应确保释放持有的锁及堆内存,推荐使用线程局部存储(TLS)或 RAII 风格封装。