进程与线程:基本关系、概念与控制详解

一、进程与线程基础概念

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;
}

关键特性

  1. 写时复制(Copy-On-Write):内存页面只在需要写入时才复制
  2. 共享文件描述符:子进程继承父进程打开的文件
  3. 两次返回:父进程返回子进程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(&not_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(&not_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(&not_empty, &mutex);
        }
        
        // 消费项目
        int item = buffer[out];
        out = (out + 1) % BUFFER_SIZE;
        count--;
        
        printf("消费者 %d: 消费 %d,缓冲区大小: %d\n", 
               id, item, count);
        
        // 通知生产者缓冲区不满
        pthread_cond_signal(&not_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(&not_empty);
    pthread_cond_destroy(&not_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 进程编程建议

  1. 检查返回值:所有系统调用都可能失败
  2. 避免僵尸进程:父进程必须wait子进程
  3. 清理资源:子进程退出前关闭不需要的文件描述符
  4. 信号处理:考虑子进程被信号终止的情况

5.2 线程编程建议

  1. 最小化临界区:只保护必要的共享数据
  2. 避免死锁:按固定顺序获取锁
  3. 使用RAII模式管理锁:确保锁在异常时也能释放
  4. 线程安全函数:使用可重入函数或进行同步保护

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程序员的必备技能。建议从简单示例开始,逐步实践更复杂的场景,理解不同模型的特性和适用情况。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐