线程与进程:概念、区别及实践

一、 进程与线程的基本概念

1.1 进程

进程(Process)是操作系统进行资源分配和调度的基本单位,是程序的一次执行过程。每个进程都有自己独立的内存空间、数据栈以及其他用于跟踪执行的辅助数据。

进程的特点:

  • 独立性:进程之间相互独立,一个进程崩溃通常不会影响其他进程

  • 资源分配:操作系统为每个进程分配独立的内存空间和系统资源

  • 开销大:创建和销毁进程需要较大的系统开销

  • 通信复杂:进程间通信(IPC)需要特定的机制

#include <stdio.h>
#include <unistd.h>

int main() {
    printf("当前进程PID: %d\n", getpid());
    printf("父进程PID: %d\n", getppid());
    return 0;
}

1.2 线程

线程(Thread)是进程中的一个执行单元,是CPU调度和分派的基本单位。一个进程可以包含多个线程,这些线程共享进程的内存空间和资源。

线程的特点:

  • 共享资源:同一进程内的线程共享内存和文件等资源

  • 轻量级:创建和销毁线程的开销比进程小得多

  • 通信简单:线程间可以直接读写进程数据段进行通信

  • 依赖性强:一个线程崩溃可能导致整个进程崩溃

#include <pthread.h>
#include <stdio.h>

void* thread_func(void* arg) {
    printf("线程ID: %lu\n", pthread_self());
    return NULL;
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, thread_func, NULL);
    pthread_join(tid, NULL);
    return 0;
}

二、进程和线程的区别

进程线程
基本单位资源分配的基本单位CPU调度的基本单位
内存空间独立的内存空间共享所属进程的内存空间
创建开销大,需要分配独立资源小,只需分配栈和寄存器等少量资源
通信方式IPC(管道、消息队列、共享内存等)直接读写共享变量
稳定性一个进程崩溃不影响其他进程一个线程崩溃可能导致整个进程崩溃
数量关系一个程序至少有一个进程一个进程至少有一个线程
上下文切换速度

三、操作详解

3.1 进程创建:fork()系统调用

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
    pid_t pid = fork();
    
    if (pid < 0) {
        perror("fork失败");
        return 1;
    } else if (pid == 0) {
        // 子进程代码
        printf("子进程PID: %d\n", getpid());
        sleep(2);
        printf("子进程结束\n");
    } else {
        // 父进程代码
        printf("父进程PID: %d, 创建的子进程PID: %d\n", getpid(), pid);
        wait(NULL);  // 等待子进程结束
        printf("父进程结束\n");
    }
    
    return 0;
}

3.2 进程间通信(IPC)技术

3.2.1 匿名管道通信
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main() {
    int pipefd[2];
    char buf[256];
    
    if (pipe(pipefd) == -1) {
        perror("pipe创建失败");
        return 1;
    }
    
    pid_t pid = fork();
    
    if (pid == 0) {
        // 子进程:读取数据
        close(pipefd[1]);  // 关闭写端
        read(pipefd[0], buf, sizeof(buf));
        printf("子进程收到: %s\n", buf);
        close(pipefd[0]);
    } else {
        // 父进程:写入数据
        close(pipefd[0]);  // 关闭读端
        const char* msg = "Hello from parent";
        write(pipefd[1], msg, strlen(msg)+1);
        close(pipefd[1]);
        wait(NULL);
    }
    
    return 0;
}

3.3 线程创建与管理

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

#define NUM_THREADS 5

void* print_hello(void* threadid) {
    long tid = (long)threadid;
    printf("线程 #%ld: 你好!\n", tid);
    sleep(1);
    printf("线程 #%ld: 再见!\n", tid);
    pthread_exit(NULL);
}

int main() {
    pthread_t threads[NUM_THREADS];
    int rc;
    
    for (long t = 0; t < NUM_THREADS; t++) {
        printf("主线程: 创建线程 %ld\n", t);
        rc = pthread_create(&threads[t], NULL, print_hello, (void*)t);
        if (rc) {
            printf("错误: 无法创建线程, 返回码: %d\n", rc);
            return -1;
        }
    }
    
    // 等待所有线程完成
    for (long t = 0; t < NUM_THREADS; t++) {
        pthread_join(threads[t], NULL);
    }
    
    printf("主线程: 所有线程已完成\n");
    return 0;
}

3.4 线程同步机制

3.4.1 互斥锁(Mutex)

1. 保护共享资源

  • 确保同一时间只有一个线程可以访问共享资源(变量、数据结构、文件等)
  • 防止多个线程同时修改数据导致的不一致问题

2. 保证操作的原子性

  • 将一系列操作封装为不可分割的单元
  • 其他线程必须等待当前线程完成整个操作序列

3. 维持数据一致性

  • 避免因线程切换导致的中间状态被其他线程读取
  • 确保数据的完整性和正确性
#include <pthread.h>
#include <stdio.h>

pthread_mutex_t lock;
int counter = 0;

void* increment_counter(void* arg) {
    for (int i = 0; i < 100000; i++) {
        pthread_mutex_lock(&lock);
        counter++;
        pthread_mutex_unlock(&lock);
    }
    return NULL;
}

int main() {
    pthread_t t1, t2;
    
    if (pthread_mutex_init(&lock, NULL) != 0) {
        printf("互斥锁初始化失败\n");
        return 1;
    }
    
    pthread_create(&t1, NULL, increment_counter, NULL);
    pthread_create(&t2, NULL, increment_counter, NULL);
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    
    printf("最终计数器值: %d\n", counter);
    pthread_mutex_destroy(&lock);
    
    return 0;
}
3.4.2 条件变量
#include <pthread.h>
#include <stdio.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int ready = 0;    //共享标志变量

void* producer(void* arg) {
    pthread_mutex_lock(&mutex);
    printf("生产者: 生产数据...\n");
    ready = 1;
    pthread_cond_signal(&cond);    // 通知等待的消费者
    pthread_mutex_unlock(&mutex);
    return NULL;
}

void* consumer(void* arg) {
    pthread_mutex_lock(&mutex);
    while (!ready) {            // 检查数据是否就绪
        printf("消费者: 等待数据...\n");
        pthread_cond_wait(&cond, &mutex);       // 等待条件变量信号 
    }
    printf("消费者: 消费数据\n");
    pthread_mutex_unlock(&mutex);
    return NULL;
}

int main() {
    pthread_t t1, t2;
    
    pthread_create(&t1, NULL, consumer, NULL);
    sleep(1);  // 确保消费者先运行
    pthread_create(&t2, NULL, producer, NULL);
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    
    return 0;
}

四、选择

使用进程场景:

  • 需要高安全性和隔离性(如浏览器多标签页)
  • 需要利用多核CPU进行并行计算
  • 任务之间不需要频繁通信

使用线程场景:

  • 需要频繁共享数据(如GUI应用程序)
  • 需要快速响应I/O操作(如网络服务器)
  • 任务轻量且创建频繁

五、常见问题与实践

5.1 常见问题

1. 僵尸进程:子进程退出后父进程未调用wait()

// 解决方案:设置SIGCHLD处理函数
signal(SIGCHLD, SIG_IGN);  // 自动回收子进程

2. 线程安全问题:多个线程同时访问共享数据

// 解决方案:使用互斥锁保护临界区
pthread_mutex_lock(&mutex);
// 临界区代码
pthread_mutex_unlock(&mutex);

5.2 实践

资源清理:确保释放所有分配的资源

// 对于线程
pthread_join(thread, NULL);

// 对于进程
waitpid(pid, NULL, 0);

错误检查:检查所有系统调用的返回值

if (pthread_create(&thread, NULL, func, NULL) != 0) {
    perror("线程创建失败");
    // 错误处理
}

避免死锁:按固定顺序获取多个锁

编译包含线程的程序需要链接pthread库:

gcc program.c -o program -pthread

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值