进程和线程的基本关系概念,以及进程控制:fork、wait、exec 详解和线程控制详解
进程和线程的基本关系概念,以及进程控制:fork、wait、exec 详解和线程控制详解。
·
进程与线程:基本关系、概念与控制详解
一、进程与线程基础概念
1.1 进程(Process)
定义:资源分配的基本单位,程序的一次执行实例
组成:
- 代码段(Text):可执行指令
- 数据段(Data):全局变量、静态变量
- 堆(Heap):动态分配的内存
- 栈(Stack):局部变量、函数调用信息
- 进程控制块(PCB):包含进程状态、PID、资源信息等
- 文件描述符表:打开的文件和I/O设备
1.2 线程(Thread)
定义:CPU调度的基本单位,进程内的执行流
特点:
- 共享进程的大部分资源(代码、数据、文件)
- 拥有独立的栈、程序计数器、寄存器集
- 也称为"轻量级进程"
1.3 进程 vs 线程比较
| 特性 | 进程 | 线程 |
|---|---|---|
| 资源隔离 | 完全隔离,安全性高 | 共享资源,需要同步 |
| 创建开销 | 大(需复制资源) | 小(共享已有资源) |
| 上下文切换 | 开销大 | 开销小 |
| 通信方式 | 复杂(管道、IPC等) | 简单(共享内存) |
| 独立性 | 相互独立 | 相互依赖 |
| 崩溃影响 | 不影响其他进程 | 可能导致整个进程崩溃 |
1.4 进程与线程的关系图
进程 (Process)
├── 代码段 (共享)
├── 数据段 (共享)
├── 堆 (共享)
├── 文件描述符 (共享)
├── 信号处理 (共享)
└── 线程1 (Thread 1)
├── 栈 (私有)
├── 寄存器 (私有)
└── 程序计数器 (私有)
└── 线程2 (Thread 2)
├── 栈 (私有)
├── 寄存器 (私有)
└── 程序计数器 (私有)
二、进程控制详解
2.1 fork() - 进程创建
功能:创建当前进程的副本
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t pid;
printf("主进程 PID=%d\n", getpid());
// fork创建子进程
pid = fork();
if (pid < 0) {
// fork失败
perror("fork失败");
return 1;
} else if (pid == 0) {
// 子进程执行流
printf("子进程: PID=%d, 父进程PID=%d\n",
getpid(), getppid());
// 子进程可以修改自己的数据
// 由于写时复制,不影响父进程
// ...
_exit(0); // 子进程退出
} else {
// 父进程执行流
printf("父进程: 子进程PID=%d\n", pid);
// 父进程继续执行
// ...
}
return 0;
}
关键特性:
- 写时复制(Copy-On-Write):内存页面只在需要写入时才复制
- 共享文件描述符:子进程继承父进程打开的文件
- 两次返回:父进程返回子进程PID,子进程返回0
2.2 wait() 系列 - 进程等待
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main() {
pid_t pid;
int status;
// 创建3个子进程
for (int i = 0; i < 3; i++) {
pid = fork();
if (pid == 0) {
// 子进程
printf("子进程 %d 开始 (PID=%d)\n", i, getpid());
sleep(i + 1); // 每个子进程睡眠不同时间
printf("子进程 %d 结束\n", i);
exit(i); // 返回不同的退出码
}
}
// 父进程:等待所有子进程
printf("父进程 (PID=%d) 等待子进程...\n", getpid());
pid_t child_pid;
while ((child_pid = wait(&status)) != -1) {
if (WIFEXITED(status)) {
printf("子进程 %d 正常退出,退出码: %d\n",
child_pid, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("子进程 %d 被信号终止,信号: %d\n",
child_pid, WTERMSIG(status));
} else if (WIFSTOPPED(status)) {
printf("子进程 %d 被暂停,信号: %d\n",
child_pid, WSTOPSIG(status));
}
}
printf("所有子进程已结束\n");
return 0;
}
waitpid() 更精确的控制:
pid_t waitpid(pid_t pid, int *status, int options);
-
pid参数:
>0:等待特定PID的子进程-1:等待任意子进程(同wait)0:等待同一进程组的任意子进程<-1:等待进程组ID为|pid|的任意子进程
-
options参数:
0:阻塞等待WNOHANG:非阻塞,立即返回WUNTRACED:报告停止的子进程WCONTINUED:报告继续运行的子进程
2.3 exec() 系列 - 进程替换
六种变体比较:
| 函数 | 参数格式 | 环境变量 | PATH搜索 | 示例 |
|---|---|---|---|---|
execl |
列表 | 继承 | 否 | execl("/bin/ls", "ls", "-l", NULL) |
execlp |
列表 | 继承 | 是 | execlp("ls", "ls", "-l", NULL) |
execle |
列表 | 指定 | 否 | execle("/bin/sh", "sh", "-c", "cmd", NULL, envp) |
execv |
数组 | 继承 | 否 | execv("/bin/ls", args) |
execvp |
数组 | 继承 | 是 | execvp("ls", args) |
execvpe |
数组 | 指定 | 是 | execvpe("cmd", args, envp) |
综合示例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
void execute_command(char *cmd, char **args, char **env) {
pid_t pid = fork();
if (pid == 0) {
// 子进程:替换为命令
printf("执行命令: %s\n", cmd);
// 使用execvp执行(自动搜索PATH)
execvp(cmd, args);
// 如果execvp失败
perror("execvp失败");
exit(1);
} else if (pid > 0) {
// 父进程等待
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("命令退出码: %d\n", WEXITSTATUS(status));
}
} else {
perror("fork失败");
}
}
int main() {
// 示例1:执行ls -l
char *args1[] = {"ls", "-l", "/tmp", NULL};
execute_command("ls", args1, NULL);
// 示例2:自定义环境变量执行
char *env[] = {
"PATH=/usr/bin:/bin",
"MYVAR=自定义值",
NULL
};
pid_t pid = fork();
if (pid == 0) {
// 使用execle
execle("/usr/bin/printenv", "printenv", NULL, env);
perror("execle失败");
exit(1);
}
wait(NULL);
return 0;
}
三、线程控制详解
3.1 POSIX线程(pthread)基础
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// 线程函数原型
void *thread_function(void *arg) {
int thread_num = *(int *)arg;
printf("线程 %d 开始 (TID=%lu)\n",
thread_num, pthread_self());
// 线程执行的任务
for (int i = 0; i < 3; i++) {
printf("线程 %d: 计数 %d\n", thread_num, i);
sleep(1);
}
printf("线程 %d 结束\n", thread_num);
// 返回值可以通过pthread_join获取
int *result = malloc(sizeof(int));
*result = thread_num * 100;
return (void *)result;
}
int main() {
pthread_t threads[3];
int thread_args[3];
int *thread_results[3];
printf("主线程开始 (PID=%d)\n", getpid());
// 创建多个线程
for (int i = 0; i < 3; i++) {
thread_args[i] = i;
// 创建线程
int ret = pthread_create(&threads[i], NULL,
thread_function, &thread_args[i]);
if (ret != 0) {
perror("pthread_create失败");
exit(1);
}
printf("创建了线程 %d (TID=%lu)\n", i, threads[i]);
}
// 等待所有线程结束
for (int i = 0; i < 3; i++) {
pthread_join(threads[i], (void **)&thread_results[i]);
printf("线程 %d 返回值: %d\n", i, *thread_results[i]);
free(thread_results[i]); // 释放动态分配的内存
}
printf("所有线程已结束\n");
return 0;
}
3.2 线程属性设置
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void *detached_thread(void *arg) {
printf("分离线程运行中\n");
sleep(2);
printf("分离线程结束\n");
return NULL;
}
int main() {
pthread_t thread;
pthread_attr_t attr;
// 初始化线程属性
pthread_attr_init(&attr);
// 设置为分离状态(不可join)
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// 设置栈大小(1MB)
size_t stacksize = 1024 * 1024;
pthread_attr_setstacksize(&attr, stacksize);
// 创建分离线程
pthread_create(&thread, &attr, detached_thread, NULL);
// 销毁属性对象
pthread_attr_destroy(&attr);
// 主线程继续执行
printf("主线程继续执行...\n");
sleep(3);
// 注意:不能join分离线程
// pthread_join(thread, NULL); // 错误!
printf("主线程结束\n");
return 0;
}
3.3 线程同步机制
3.3.1 互斥锁(Mutex)
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS 5
#define COUNT_LIMIT 10000
// 共享数据
int shared_counter = 0;
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
void *increment_counter(void *arg) {
int thread_id = *(int *)arg;
for (int i = 0; i < COUNT_LIMIT; i++) {
// 加锁保护临界区
pthread_mutex_lock(&counter_mutex);
// 临界区开始
int temp = shared_counter;
temp++;
shared_counter = temp;
// 临界区结束
pthread_mutex_unlock(&counter_mutex);
}
printf("线程 %d 完成\n", thread_id);
return NULL;
}
int main() {
pthread_t threads[NUM_THREADS];
int thread_ids[NUM_THREADS];
// 创建线程
for (int i = 0; i < NUM_THREADS; i++) {
thread_ids[i] = i;
pthread_create(&threads[i], NULL,
increment_counter, &thread_ids[i]);
}
// 等待所有线程
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
// 销毁互斥锁
pthread_mutex_destroy(&counter_mutex);
printf("最终计数: %d (期望值: %d)\n",
shared_counter, NUM_THREADS * COUNT_LIMIT);
return 0;
}
3.3.2 条件变量(Condition Variable)
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// 生产者-消费者模型
#define BUFFER_SIZE 5
#define PRODUCERS 2
#define CONSUMERS 2
int buffer[BUFFER_SIZE];
int count = 0; // 缓冲区中项目数
int in = 0; // 生产者插入位置
int out = 0; // 消费者取出位置
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t not_empty = PTHREAD_COND_INITIALIZER;
pthread_cond_t not_full = PTHREAD_COND_INITIALIZER;
void *producer(void *arg) {
int id = *(int *)arg;
for (int i = 0; i < 10; i++) {
pthread_mutex_lock(&mutex);
// 等待缓冲区不满
while (count == BUFFER_SIZE) {
printf("生产者 %d: 缓冲区满,等待...\n", id);
pthread_cond_wait(¬_full, &mutex);
}
// 生产项目
int item = id * 100 + i;
buffer[in] = item;
in = (in + 1) % BUFFER_SIZE;
count++;
printf("生产者 %d: 生产 %d,缓冲区大小: %d\n",
id, item, count);
// 通知消费者缓冲区非空
pthread_cond_signal(¬_empty);
pthread_mutex_unlock(&mutex);
sleep(1); // 模拟生产时间
}
return NULL;
}
void *consumer(void *arg) {
int id = *(int *)arg;
for (int i = 0; i < 10; i++) {
pthread_mutex_lock(&mutex);
// 等待缓冲区不空
while (count == 0) {
printf("消费者 %d: 缓冲区空,等待...\n", id);
pthread_cond_wait(¬_empty, &mutex);
}
// 消费项目
int item = buffer[out];
out = (out + 1) % BUFFER_SIZE;
count--;
printf("消费者 %d: 消费 %d,缓冲区大小: %d\n",
id, item, count);
// 通知生产者缓冲区不满
pthread_cond_signal(¬_full);
pthread_mutex_unlock(&mutex);
sleep(2); // 模拟消费时间
}
return NULL;
}
int main() {
pthread_t producers[PRODUCERS];
pthread_t consumers[CONSUMERS];
int producer_ids[PRODUCERS];
int consumer_ids[CONSUMERS];
// 创建生产者线程
for (int i = 0; i < PRODUCERS; i++) {
producer_ids[i] = i;
pthread_create(&producers[i], NULL,
producer, &producer_ids[i]);
}
// 创建消费者线程
for (int i = 0; i < CONSUMERS; i++) {
consumer_ids[i] = i;
pthread_create(&consumers[i], NULL,
consumer, &consumer_ids[i]);
}
// 等待所有线程
for (int i = 0; i < PRODUCERS; i++) {
pthread_join(producers[i], NULL);
}
for (int i = 0; i < CONSUMERS; i++) {
pthread_join(consumers[i], NULL);
}
// 清理资源
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(¬_empty);
pthread_cond_destroy(¬_full);
printf("生产者-消费者模型运行完成\n");
return 0;
}
3.3.3 读写锁(Read-Write Lock)
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// 共享数据
int shared_data = 0;
int read_count = 0;
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;
void *reader(void *arg) {
int id = *(int *)arg;
for (int i = 0; i < 5; i++) {
// 获取读锁
pthread_rwlock_rdlock(&rwlock);
// 读取数据
printf("读者 %d: 读取数据 = %d\n", id, shared_data);
// 释放读锁
pthread_rwlock_unlock(&rwlock);
sleep(1); // 模拟读取时间
}
return NULL;
}
void *writer(void *arg) {
int id = *(int *)arg;
for (int i = 0; i < 3; i++) {
// 获取写锁
pthread_rwlock_wrlock(&rwlock);
// 修改数据
shared_data++;
printf("写者 %d: 写入数据 = %d\n", id, shared_data);
// 释放写锁
pthread_rwlock_unlock(&rwlock);
sleep(2); // 模拟写入时间
}
return NULL;
}
int main() {
pthread_t readers[3];
pthread_t writers[2];
int reader_ids[3];
int writer_ids[2];
// 创建读者线程
for (int i = 0; i < 3; i++) {
reader_ids[i] = i;
pthread_create(&readers[i], NULL,
reader, &reader_ids[i]);
}
// 创建写者线程
for (int i = 0; i < 2; i++) {
writer_ids[i] = i;
pthread_create(&writers[i], NULL,
writer, &writer_ids[i]);
}
// 等待所有线程
for (int i = 0; i < 3; i++) {
pthread_join(readers[i], NULL);
}
for (int i = 0; i < 2; i++) {
pthread_join(writers[i], NULL);
}
// 清理资源
pthread_rwlock_destroy(&rwlock);
pthread_mutex_destroy(&count_mutex);
printf("最终数据: %d\n", shared_data);
return 0;
}
3.4 线程特定数据(Thread-Specific Data)
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 创建线程特定数据键
pthread_key_t thread_log_key;
// 写线程日志的函数
void write_to_thread_log(const char *message) {
FILE *thread_log = (FILE *)pthread_getspecific(thread_log_key);
fprintf(thread_log, "%s\n", message);
}
// 关闭线程日志
void close_thread_log(void *thread_log) {
fclose((FILE *)thread_log);
}
void *thread_function(void *arg) {
char thread_log_filename[256];
FILE *thread_log;
// 为每个线程创建独立的日志文件
sprintf(thread_log_filename, "thread_%ld.log",
(long)pthread_self());
thread_log = fopen(thread_log_filename, "w");
// 设置线程特定数据
pthread_setspecific(thread_log_key, thread_log);
// 使用线程特定数据
write_to_thread_log("线程开始");
write_to_thread_log("执行任务...");
write_to_thread_log("线程结束");
return NULL;
}
int main() {
pthread_t threads[3];
// 创建线程特定数据键
pthread_key_create(&thread_log_key, close_thread_log);
// 创建多个线程
for (int i = 0; i < 3; i++) {
pthread_create(&threads[i], NULL,
thread_function, NULL);
}
// 等待所有线程
for (int i = 0; i < 3; i++) {
pthread_join(threads[i], NULL);
}
// 删除线程特定数据键
pthread_key_delete(thread_log_key);
printf("所有线程已完成,请查看生成的日志文件\n");
return 0;
}
四、进程与线程编程模型对比
4.1 多进程模型(Apache风格)
// 主进程监听连接,为每个连接创建子进程处理
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/wait.h>
void handle_connection(int client_fd) {
// 处理客户端连接
printf("进程 %d 处理连接\n", getpid());
// ... 业务逻辑
close(client_fd);
exit(0);
}
int main() {
int server_fd, client_fd;
// 创建服务器socket...
while (1) {
client_fd = accept(server_fd, NULL, NULL);
pid_t pid = fork();
if (pid == 0) {
// 子进程处理连接
close(server_fd); // 子进程不需要监听socket
handle_connection(client_fd);
} else {
// 父进程继续监听
close(client_fd); // 父进程不需要客户端socket
// 回收僵尸子进程(非阻塞)
while (waitpid(-1, NULL, WNOHANG) > 0);
}
}
return 0;
}
4.2 多线程模型(Nginx风格)
// 线程池处理连接
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#define THREAD_POOL_SIZE 10
pthread_t thread_pool[THREAD_POOL_SIZE];
pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER;
int connection_queue[100];
int queue_front = 0, queue_rear = 0, queue_count = 0;
void *worker_thread(void *arg) {
while (1) {
int client_fd;
// 从队列获取连接
pthread_mutex_lock(&queue_mutex);
while (queue_count == 0) {
pthread_cond_wait(&queue_cond, &queue_mutex);
}
client_fd = connection_queue[queue_front];
queue_front = (queue_front + 1) % 100;
queue_count--;
pthread_mutex_unlock(&queue_mutex);
// 处理连接
printf("线程 %lu 处理连接\n", pthread_self());
// ... 业务逻辑
close(client_fd);
}
return NULL;
}
int main() {
int server_fd, client_fd;
// 创建服务器socket...
// 创建线程池
for (int i = 0; i < THREAD_POOL_SIZE; i++) {
pthread_create(&thread_pool[i], NULL,
worker_thread, NULL);
}
while (1) {
client_fd = accept(server_fd, NULL, NULL);
// 将连接加入队列
pthread_mutex_lock(&queue_mutex);
if (queue_count < 100) {
connection_queue[queue_rear] = client_fd;
queue_rear = (queue_rear + 1) % 100;
queue_count++;
// 通知工作线程
pthread_cond_signal(&queue_cond);
} else {
// 队列满,拒绝连接
close(client_fd);
}
pthread_mutex_unlock(&queue_mutex);
}
return 0;
}
五、编程实践建议与常见陷阱
5.1 进程编程建议
- 检查返回值:所有系统调用都可能失败
- 避免僵尸进程:父进程必须wait子进程
- 清理资源:子进程退出前关闭不需要的文件描述符
- 信号处理:考虑子进程被信号终止的情况
5.2 线程编程建议
- 最小化临界区:只保护必要的共享数据
- 避免死锁:按固定顺序获取锁
- 使用RAII模式管理锁:确保锁在异常时也能释放
- 线程安全函数:使用可重入函数或进行同步保护
5.3 常见陷阱
// 陷阱1:竞争条件
int counter = 0;
void *increment(void *arg) {
for (int i = 0; i < 1000; i++) {
counter++; // 非原子操作!
}
return NULL;
}
// 陷阱2:共享指针问题
void *thread_func(void *arg) {
char *message = (char *)arg;
printf("%s\n", message); // 如果message被释放,会导致段错误
return NULL;
}
// 陷阱3:未初始化的互斥锁
pthread_mutex_t mutex; // 未初始化!
void *thread_func(void *arg) {
pthread_mutex_lock(&mutex); // 未定义行为!
// ...
pthread_mutex_unlock(&mutex);
return NULL;
}
六、总结对比表
| 方面 | 进程 | 线程 |
|---|---|---|
| 创建开销 | 高 | 低 |
| 通信成本 | 高(需要IPC) | 低(共享内存) |
| 隔离性 | 完全隔离 | 共享地址空间 |
| 容错性 | 高(一个进程崩溃不影响其他) | 低(一个线程崩溃可能影响整个进程) |
| 适用场景 | 需要高隔离性、安全性 | 需要高性能、频繁数据共享 |
| 调试难度 | 相对容易 | 相对困难(竞态条件) |
| 可移植性 | 高(所有Unix/Linux都支持) | 高(POSIX标准) |
掌握进程和线程的编程技术,能够根据具体需求选择合适的并发模型,是成为高级Linux/C程序员的必备技能。建议从简单示例开始,逐步实践更复杂的场景,理解不同模型的特性和适用情况。
更多推荐

所有评论(0)