在Linux环境中,多线程编程是提升程序并发能力的核心手段。通过在一个进程内创建多个执行流,可以充分利用多核CPU的计算资源,尤其适合处理高并发网络服务、批量数据处理等场景。POSIX线程(通常称为pthread)是Linux系统上最标准的线程实现,几乎所有发行版都自带了对应的开发库。

一、线程的创建与基本结构
Linux多线程编程依赖POSIX线程库,头文件为<pthread.h>。编译时需要显式链接动态库,例如使用gcc时添加-lpthread参数。线程的入口函数有固定签名:返回值为void*,参数也为void*,这允许我们传递任意类型的指针数据。
使用pthread_create函数可以新建线程,其原型为int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg)。第一个参数用于接收线程ID,第二个一般为空表示默认属性,第三个是线程函数,第四个是传入参数。下面是一段最简单的创建示例:
#include <pthread.h>
#include <stdio.h>
void* worker(void* arg) {
int id = *(int*)arg;
printf("线程 %d 正在运行n", id);
return NULL;
}
int main() {
pthread_t tid;
int num = 1;
pthread_create(&tid, NULL, worker, &num);
pthread_join(tid, NULL);
return 0;
}
上述代码中,主线程通过pthread_join等待子线程结束,否则进程可能提前退出导致线程来不及执行。在实际工程中,常把多个线程ID存入数组,循环创建与回收。
需要注意,传递给线程的参数如果是栈上局部变量,必须确保在线程使用期间该变量依然有效。更安全的做法是在堆上分配参数,或传递全局变量。此外,线程函数内部若需修改外部状态,要考虑后续提到的同步问题。
二、线程间的数据共享与竞态条件
同一进程内的线程天然共享地址空间,这意味着全局变量、堆内存对所有线程可见。这种共享特性带来便利,也引入了竞态条件:当多个线程未加保护地修改同一数据时,最终结果依赖调度顺序,往往产生错误数值。
例如两个线程各对全局计数器累加十万次,理论上结果应为二十万,但运行时经常得到更小的值。原因在于i++在汇编层面分为读取、加一、写回三步,线程可能在写回前被切换,导致彼此覆盖。下面的代码演示了这一问题:
#include <pthread.h>
#include <stdio.h>
int counter = 0;
void* add(void* arg) {
for (int i = 0; i < 100000; i++) {
counter++;
}
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, add, NULL);
pthread_create(&t2, NULL, add, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("counter = %dn", counter);
return 0;
}
运行这段程序,打印的counter经常小于200000。要解决这个问题,必须采用同步机制把“读-改-写”过程变成原子操作,或者让同一时间只有一个线程进入临界区。
三、使用互斥锁保护临界区
互斥锁(mutex)是最常用的线程同步工具。它如同洗手间的门锁:谁先进去就持有钥匙,出来后才轮到下一个。在pthread中,类型为pthread_mutex_t,使用前需初始化,可以用PTHREAD_MUTEX_INITIALIZER静态初始化,也可调用pthread_mutex_init。
加锁与解锁分别对应pthread_mutex_lock和pthread_mutex_unlock。把counter自增放在锁内,就能彻底消除竞态。改进代码如下:
#include <pthread.h>
#include <stdio.h>
int counter = 0;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void* safe_add(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;
pthread_create(&t1, NULL, safe_add, NULL);
pthread_create(&t2, NULL, safe_add, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("counter = %dn", counter);
pthread_mutex_destroy(&lock);
return 0;
}
此时无论运行多少次,结果都稳定为200000。不过频繁加锁会带来微小性能开销,若临界区极短,可考虑将循环移入锁内以减少锁操作次数,但需权衡代码清晰度和并发度。
使用互斥锁要防止死锁:例如同一线程连续两次pthread_mutex_lock同一把非递归锁会阻塞自身。复杂系统中应按固定顺序获取多把锁,或改用读写锁、自旋锁等更合适的原语。
四、条件变量与线程协作
除了互斥锁,条件变量(condition variable)用于在线程间传递“某状态已改变”的信号。典型场景是生产者消费者模型:消费者在缓冲区空时等待,生产者放入数据后通知消费者。条件变量必须与互斥锁配合使用,类型为pthread_cond_t。
等待方调用pthread_cond_wait,该函数会自动释放锁并休眠,被唤醒后重新获得锁;通知方调用pthread_cond_signal或pthread_cond_broadcast。以下片段展示基础用法:
#include <pthread.h>
#include <stdio.h>
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t c = PTHREAD_COND_INITIALIZER;
int ready = 0;
void* consumer(void* arg) {
pthread_mutex_lock(&m);
while (ready == 0) {
pthread_cond_wait(&c, &m);
}
printf("收到通知,开始处理n");
pthread_mutex_unlock(&m);
return NULL;
}
void* producer(void* arg) {
pthread_mutex_lock(&m);
ready = 1;
pthread_cond_signal(&c);
pthread_mutex_unlock(&m);
return NULL;
}
注意等待处用while而非if判断条件,这是因为存在虚假唤醒可能。条件变量不保存状态,只负责通知,真实条件仍需用户自己维护。
在真实服务开发中,条件变量常配合队列使用,避免忙等待浪费CPU。它和互斥锁组合,构成了大多数线程池、任务调度框架的底层支撑。
五、线程资源回收与取消
线程结束后的资源回收有两种方式:一是主线程调用pthread_join阻塞等待并获取退出值;二是设置分离属性pthread_detach,让线程结束后自动释放资源,无需他人回收。若既不join也不detach,线程会变成僵尸线程,占用少量内核结构。
有时需要主动终止线程,可调用pthread_cancel向目标线程发送取消请求,但对方可能在不可取消点停留。更推荐的做法是设置标志位,让线程函数在合适位置自行退出,这样能安全释放锁和内存。
#include <pthread.h>
#include <stdio.h>
#include <stdbool.h>
volatile bool running = true;
void* loop_task(void* arg) {
while (running) {
// 执行周期工作
}
printf("线程干净退出n");
return NULL;
}
通过共享running变量,外部将其置为false即可温和停止线程。相比强制取消,这种协作式退出能避免破坏数据一致性。
总体来看,Linux多线程编程的实现路径十分清晰:引入pthread库、创建线程、通过互斥锁与条件变量管理共享数据、最后妥善回收。掌握这些基础组件,就能构建出稳定高效的并发程序。