Linux多线程编程是服务端开发、高性能计算场景中常用的技术,通过pthread库可以在Linux系统中创建和管理多个线程,实现任务的并发执行,提升程序的运行效率。合理的多线程设计能够充分利用多核CPU资源,同时需要注意线程间的同步与通信,避免出现数据竞争、死锁等问题。

线程创建基础实例
使用pthread库创建线程需要包含<pthread.h>头文件,编译时需要链接pthread库,添加-lpthread参数。下面是一个最简单的线程创建示例,主线程创建一个子线程,子线程执行自定义的任务函数。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
// 子线程执行的任务函数
void* thread_task(void* arg) {
// 将传入的参数转换为int类型
int thread_id = *(int*)arg;
printf("子线程 %d 开始执行n", thread_id);
sleep(2); // 模拟任务执行耗时
printf("子线程 %d 执行结束n", thread_id);
return NULL;
}
int main() {
pthread_t tid; // 线程ID变量
int thread_arg = 1; // 传递给子线程的参数
// 创建线程,第一个参数是线程ID指针,第二个是线程属性,第三个是任务函数,第四个是参数
int ret = pthread_create(&tid, NULL, thread_task, &thread_arg);
if (ret != 0) {
printf("线程创建失败,错误码:%dn", ret);
return -1;
}
printf("主线程等待子线程执行完成n");
// 等待子线程结束,回收资源
pthread_join(tid, NULL);
printf("主线程执行结束n");
return 0;
}
上述代码中,pthread_create函数用于创建线程,第一个参数&tid用来存储新创建线程的ID,第二个参数为线程属性,传入NULL表示使用默认属性,第三个参数是线程启动后执行的函数指针,第四个参数是传递给任务函数的参数。主线程调用pthread_join等待子线程执行完成,避免主线程提前退出导致子线程未执行完毕。
线程间参数传递与返回值
线程任务函数可以接收参数并返回结果,参数和返回值都是void*类型,需要根据实际类型进行转换。下面的示例演示了如何传递结构体参数,并获取子线程的返回结果。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
// 定义传递给线程的参数结构体
typedef struct {
int task_id;
char task_name[32];
} ThreadArg;
// 定义线程返回结果结构体
typedef struct {
int status;
char result_msg[64];
} ThreadResult;
void* thread_work(void* arg) {
ThreadArg* t_arg = (ThreadArg*)arg;
printf("接收到任务:id=%d, name=%sn", t_arg->task_id, t_arg->task_name);
// 分配返回结果内存,注意需要动态分配,否则线程结束后内存会被释放
ThreadResult* res = (ThreadResult*)malloc(sizeof(ThreadResult));
res->status = 0;
strcpy(res->result_msg, "任务执行成功");
// 释放参数内存,参数如果是动态分配的需要在合适的时机释放
free(t_arg);
return (void*)res;
}
int main() {
pthread_t tid;
// 动态分配参数内存
ThreadArg* arg = (ThreadArg*)malloc(sizeof(ThreadArg));
arg->task_id = 1001;
strcpy(arg->task_name, "数据处理任务");
int ret = pthread_create(&tid, NULL, thread_work, arg);
if (ret != 0) {
printf("线程创建失败n");
free(arg);
return -1;
}
ThreadResult* thread_res = NULL;
// 获取线程返回结果
pthread_join(tid, (void**)&thread_res);
if (thread_res != NULL) {
printf("线程返回结果:status=%d, msg=%sn", thread_res->status, thread_res->result_msg);
free(thread_res);
}
return 0;
}
需要注意的是,传递给线程的参数如果是局部变量,可能会出现主线程修改参数值或者局部变量销毁的情况,因此建议动态分配参数内存,在子线程中使用完成后释放。线程的返回结果也需要动态分配内存,主线程通过pthread_join获取结果后再释放,避免内存泄漏。
线程同步:互斥锁的使用
当多个线程同时操作共享资源时,会出现数据竞争问题,需要使用互斥锁保证同一时间只有一个线程可以访问共享资源。下面的示例演示了互斥锁的基本使用场景。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int shared_count = 0; // 共享资源
pthread_mutex_t count_mutex; // 互斥锁变量
void* increment_count(void* arg) {
int thread_id = *(int*)arg;
for (int i = 0; i < 10000; i++) {
// 加锁,获取互斥锁
pthread_mutex_lock(&count_mutex);
shared_count++; // 操作共享资源
// 解锁,释放互斥锁
pthread_mutex_unlock(&count_mutex);
}
printf("线程 %d 执行完成n", thread_id);
return NULL;
}
int main() {
pthread_t tids[4];
int thread_ids[4] = {1,2,3,4};
// 初始化互斥锁
pthread_mutex_init(&count_mutex, NULL);
// 创建4个线程同时操作共享变量
for (int i = 0; i < 4; i++) {
pthread_create(&tids[i], NULL, increment_count, &thread_ids[i]);
}
// 等待所有线程执行完成
for (int i = 0; i < 4; i++) {
pthread_join(tids[i], NULL);
}
// 销毁互斥锁
pthread_mutex_destroy(&count_mutex);
printf("最终共享计数结果:%dn", shared_count);
return 0;
}
代码中首先通过pthread_mutex_init初始化互斥锁,线程在操作共享变量shared_count前调用pthread_mutex_lock加锁,操作完成后调用pthread_mutex_unlock解锁,保证同一时间只有一个线程可以修改shared_count。最后使用pthread_mutex_destroy销毁互斥锁,释放相关资源。如果不使用互斥锁,4个线程各累加10000次,最终的结果大概率会小于40000,因为存在多个线程同时修改共享变量的问题。
线程同步:条件变量的使用
条件变量通常和互斥锁配合使用,用于线程间的等待和通知机制,比如一个线程需要等待某个条件满足后再继续执行。下面的示例模拟生产者消费者场景,生产者线程生产数据后通知消费者线程消费。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
// 共享队列,最多存放5个数据
int queue[5];
int queue_size = 0;
int queue_front = 0;
int queue_rear = 0;
pthread_mutex_t queue_mutex;
pthread_cond_t queue_not_full; // 队列不满条件变量
pthread_cond_t queue_not_empty; // 队列不空条件变量
// 生产者线程函数
void* producer(void* arg) {
int data = 0;
while (1) {
pthread_mutex_lock(&queue_mutex);
// 等待队列不满的条件
while (queue_size >= 5) {
printf("队列已满,生产者等待n");
pthread_cond_wait(&queue_not_full, &queue_mutex);
}
// 生产数据放入队列
queue[queue_rear] = data;
queue_rear = (queue_rear + 1) % 5;
queue_size++;
printf("生产者生产数据:%d,当前队列大小:%dn", data, queue_size);
data++;
// 通知消费者队列不空
pthread_cond_signal(&queue_not_empty);
pthread_mutex_unlock(&queue_mutex);
sleep(1); // 模拟生产耗时
}
return NULL;
}
// 消费者线程函数
void* consumer(void* arg) {
while (1) {
pthread_mutex_lock(&queue_mutex);
// 等待队列不空的条件
while (queue_size <= 0) {
printf("队列已空,消费者等待n");
pthread_cond_wait(&queue_not_empty, &queue_mutex);
}
// 从队列取出数据
int data = queue[queue_front];
queue_front = (queue_front + 1) % 5;
queue_size--;
printf("消费者消费数据:%d,当前队列大小:%dn", data, queue_size);
// 通知生产者队列不满
pthread_cond_signal(&queue_not_full);
pthread_mutex_unlock(&queue_mutex);
sleep(2); // 模拟消费耗时
}
return NULL;
}
int main() {
pthread_t prod_tid, cons_tid;
// 初始化互斥锁和条件变量
pthread_mutex_init(&queue_mutex, NULL);
pthread_cond_init(&queue_not_full, NULL);
pthread_cond_init(&queue_not_empty, NULL);
// 创建生产者和消费者线程
pthread_create(&prod_tid, NULL, producer, NULL);
pthread_create(&cons_tid, NULL, consumer, NULL);
// 等待线程(这里实际不会结束,仅作示例)
pthread_join(prod_tid, NULL);
pthread_join(cons_tid, NULL);
// 销毁互斥锁和条件变量
pthread_mutex_destroy(&queue_mutex);
pthread_cond_destroy(&queue_not_full);
pthread_cond_destroy(&queue_not_empty);
return 0;
}
条件变量的pthread_cond_wait函数会先释放互斥锁,然后阻塞等待条件满足,当其他线程调用pthread_cond_signal或者pthread_cond_broadcast通知时,该函数会重新获取互斥锁再返回。这里使用while循环判断条件而不是if,是为了避免虚假唤醒问题,确保条件真正满足后再继续执行后续逻辑。
常见问题与注意事项
- 编译pthread相关代码时,必须添加
-lpthread链接选项,否则会出现函数未定义的编译错误。 - 线程任务函数的参数和返回值都是
void*类型,转换时需要注意类型匹配,避免内存访问错误。 - 互斥锁必须成对使用,加锁后一定要解锁,否则会导致死锁,同时不要重复解锁同一个互斥锁。
- 线程结束后如果不调用
pthread_join回收,会产生僵尸线程,浪费系统资源;如果不想阻塞主线程,可以设置线程为分离状态,使用pthread_detach函数。 - 条件变量必须和互斥锁配合使用,等待条件时先获取互斥锁,再调用等待函数,避免错过通知信号。