一、 进程与线程的基本概念
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