在Linux系统编程中,线程和进程是最核心的执行单元。理解它们的区别和联系,是进行多线程编程的基础。
线程(Thread)是轻量级的执行单元,通常被称为"轻量级进程"。每个线程都拥有:
但同一进程内的所有线程共享:
这种共享特性使得线程间通信比进程间通信更加高效,但也带来了数据同步的挑战。
进程(Process)作为资源分配的基本单位,具有:
进程创建需要复制父进程的地址空间,开销远大于线程创建。在Linux中,通过fork()创建的进程最初与父进程共享内存,采用写时复制(Copy-On-Write)技术优化性能。
| 特性 | 线程 | 进程 |
|---|---|---|
| 创建开销 | 小(仅需分配栈空间) | 大(需复制地址空间) |
| 通信方式 | 直接共享全局变量 | 需IPC机制(管道、共享内存等) |
| 上下文切换 | 快(仅需切换寄存器) | 慢(需切换地址空间) |
| 独立性 | 依赖所属进程 | 完全独立 |
| 资源分配 | 共享进程资源 | 独立资源分配 |
| 崩溃影响 | 导致整个进程终止 | 仅影响自身 |
实际编程中选择线程还是进程,需考虑:资源共享需求、容错要求、扩展性等因素。CPU密集型任务通常更适合多进程,I/O密集型任务则更适合多线程。
创建线程的基本流程:
c复制#include <pthread.h>
void* thread_func(void* arg) {
// 线程执行代码
return NULL;
}
int main() {
pthread_t tid;
int ret = pthread_create(&tid, NULL, thread_func, NULL);
if(ret != 0) {
perror("pthread_create failed");
exit(EXIT_FAILURE);
}
// 等待线程结束
pthread_join(tid, NULL);
return 0;
}
关键函数说明:
pthread_create(): 创建新线程,参数包括线程ID、属性、入口函数和参数pthread_join(): 阻塞等待指定线程结束,并回收资源pthread_exit(): 线程主动退出pthread_cancel(): 请求取消另一个线程线程参数传递需要注意生命周期管理:
c复制typedef struct {
char name[32];
int age;
} Person;
void* thread_func(void* arg) {
Person* p = (Person*)arg;
printf("Name: %s, Age: %d\n", p->name, p->age);
return NULL;
}
int main() {
pthread_t tid;
Person person = {"Alice", 25};
// 传递栈变量地址是危险的,除非确保线程在变量有效期内结束
pthread_create(&tid, NULL, thread_func, &person);
// 更安全的做法是动态分配
Person* p = malloc(sizeof(Person));
strcpy(p->name, "Bob");
p->age = 30;
pthread_create(&tid, NULL, thread_func, p);
pthread_join(tid, NULL);
free(p); // 确保在适当时候释放内存
return 0;
}
设置分离属性的线程在结束后会自动回收资源:
c复制void* detached_thread(void* arg) {
printf("Detached thread running\n");
sleep(1);
printf("Detached thread exiting\n");
return NULL;
}
int main() {
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, detached_thread, NULL);
pthread_attr_destroy(&attr);
// 不需要也不能调用pthread_join
sleep(2); // 确保线程有足够时间执行
return 0;
}
分离线程的特点:
互斥锁(Mutex)是最基础的同步机制,用于保护临界区:
c复制pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int shared_counter = 0;
void* thread_func(void* arg) {
for(int i = 0; i < 100000; i++) {
pthread_mutex_lock(&mutex);
shared_counter++; // 临界区
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, thread_func, NULL);
pthread_create(&t2, NULL, thread_func, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("Final counter: %d\n", shared_counter);
return 0;
}
互斥锁使用要点:
信号量(Semaphore)是更灵活的同步原语,可用于线程间同步:
c复制#include <semaphore.h>
sem_t sem;
int shared_data;
void* producer(void* arg) {
for(int i = 0; i < 5; i++) {
shared_data = i;
printf("Produced: %d\n", i);
sem_post(&sem); // V操作
sleep(1);
}
return NULL;
}
void* consumer(void* arg) {
for(int i = 0; i < 5; i++) {
sem_wait(&sem); // P操作
printf("Consumed: %d\n", shared_data);
}
return NULL;
}
int main() {
sem_init(&sem, 0, 0); // 初始值为0
pthread_t prod, cons;
pthread_create(&prod, NULL, producer, NULL);
pthread_create(&cons, NULL, consumer, NULL);
pthread_join(prod, NULL);
pthread_join(cons, NULL);
sem_destroy(&sem);
return 0;
}
信号量使用场景:
计数信号量可以管理有限数量的资源:
c复制#define MAX_RESOURCES 3
sem_t resource_sem;
void* worker(void* arg) {
int id = *(int*)arg;
sem_wait(&resource_sem);
printf("Worker %d acquired resource\n", id);
sleep(rand() % 3 + 1);
printf("Worker %d releasing resource\n", id);
sem_post(&resource_sem);
return NULL;
}
int main() {
sem_init(&resource_sem, 0, MAX_RESOURCES);
pthread_t workers[5];
int ids[5];
for(int i = 0; i < 5; i++) {
ids[i] = i + 1;
pthread_create(&workers[i], NULL, worker, &ids[i]);
}
for(int i = 0; i < 5; i++) {
pthread_join(workers[i], NULL);
}
sem_destroy(&resource_sem);
return 0;
}
计数信号量的特点:
无名管道是亲缘进程间通信的经典方式:
c复制int main() {
int pipefd[2];
pid_t pid;
char buf[256];
if(pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
pid = fork();
if(pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if(pid == 0) { // 子进程
close(pipefd[1]); // 关闭写端
ssize_t n = read(pipefd[0], buf, sizeof(buf));
if(n > 0) {
printf("Child received: %.*s\n", (int)n, buf);
}
close(pipefd[0]);
exit(EXIT_SUCCESS);
} else { // 父进程
close(pipefd[0]); // 关闭读端
const char* msg = "Hello from parent";
write(pipefd[1], msg, strlen(msg));
close(pipefd[1]);
wait(NULL);
}
return 0;
}
无名管道的特点:
有名管道通过文件系统实现,可用于任意进程间通信:
c复制// writer.c
int main() {
mkfifo("/tmp/myfifo", 0666);
int fd = open("/tmp/myfifo", O_WRONLY);
if(fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
const char* msg = "Hello from writer";
write(fd, msg, strlen(msg));
close(fd);
return 0;
}
// reader.c
int main() {
int fd = open("/tmp/myfifo", O_RDONLY);
if(fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
char buf[256];
ssize_t n = read(fd, buf, sizeof(buf));
if(n > 0) {
printf("Received: %.*s\n", (int)n, buf);
}
close(fd);
unlink("/tmp/myfifo");
return 0;
}
有名管道使用要点:
| 通信方式 | 适用场景 | 特点 | 复杂度 |
|---|---|---|---|
| 无名管道 | 亲缘进程 | 简单高效,单向通信 | 低 |
| 有名管道 | 任意进程 | 文件系统可见,双向通信 | 中 |
| 共享内存 | 大数据量 | 最高效,需同步机制 | 高 |
| 消息队列 | 结构化数据 | 内核维护,支持优先级 | 中 |
| 信号 | 简单通知 | 异步,信息量有限 | 低 |
在实际项目中,选择IPC机制应考虑:
减少锁竞争:
线程池实现:
c复制typedef struct {
pthread_t *threads;
int thread_count;
task_queue_t queue;
pthread_mutex_t lock;
pthread_cond_t cond;
int shutdown;
} thread_pool_t;
void* worker_thread(void* arg) {
thread_pool_t* pool = (thread_pool_t*)arg;
while(1) {
pthread_mutex_lock(&pool->lock);
while(pool->queue.size == 0 && !pool->shutdown) {
pthread_cond_wait(&pool->cond, &pool->lock);
}
if(pool->shutdown) {
pthread_mutex_unlock(&pool->lock);
pthread_exit(NULL);
}
task_t task = dequeue(&pool->queue);
pthread_mutex_unlock(&pool->lock);
// 执行任务
task.function(task.arg);
}
return NULL;
}
资源管理原则:
同步策略选择:
调试建议:
可移植性考虑: