上一篇我们掌握了多进程协作的四种IPC机制。多进程虽好,但切换开销大、共享数据不便。本篇将进入多线程世界,用pthread库实现轻量级并发,并逐步构建一个可复用的线程池。所有代码均交叉编译在ARM开发板上验证通过。

一、引言
嵌入式设备通常只有一个CPU核心(或少数几个),如何同时处理传感器采集、网络通信、用户交互?多线程是答案。相比多进程,线程共享地址空间,数据交换零成本,创建和切换开销极小。
但共享内存也带来了竞争条件的陷阱。本文将从线程创建开始,逐步引入互斥锁、条件变量,最终实现一个生产可用的线程池,让你彻底掌握Linux应用层多线程编程。
二、线程 vs 进程
| 特性 | 进程 | 线程 |
|---|---|---|
| 地址空间 | 独立 | 共享(除栈、寄存器等) |
| 通信方式 | IPC(管道、共享内存等) | 直接读写全局变量 |
| 创建/切换开销 | 大 | 小 |
| 健壮性 | 一个崩溃不影响其他 | 一个崩溃可能导致整个进程终止 |
| 身份标识 | PID | TID(线程ID) |
Linux内核中,线程和进程都用task_struct表示,通过clone系统调用创建。pthread库是用户态的线程标准。
三、核心API精讲
3.1 线程创建与终止
c
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
void pthread_exit(void *retval);
int pthread_join(pthread_t thread, void **retval);
int pthread_detach(pthread_t thread);
参数说明:
thread:传出线程IDattr:线程属性,通常传NULL使用默认属性start_routine:线程入口函数,原型为void* func(void *arg)arg:传递给入口函数的参数pthread_exit:在线程内调用,终止当前线程pthread_join:阻塞等待指定线程结束,回收资源(类似于wait)pthread_detach:分离线程,结束后自动回收资源,无需join
重要 :链接时必须加上
-lpthread。
3.2 互斥锁(Mutex)
c
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 静态初始化
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 互斥锁保证同一时刻只有一个线程进入临界区
- 忘记解锁会导致死锁,务必配对使用
3.3 条件变量(Condition Variable)
条件变量用于线程间的等待-通知机制,必须配合互斥锁使用。
c
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 静态初始化
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_signal(pthread_cond_t *cond); // 唤醒一个等待线程
int pthread_cond_broadcast(pthread_cond_t *cond); // 唤醒所有等待线程
使用范式:
c
// 等待端
pthread_mutex_lock(&mutex);
while (条件不满足) {
pthread_cond_wait(&cond, &mutex); // 原子地解锁并等待,被唤醒后重新加锁
}
// 条件满足,执行操作
pthread_mutex_unlock(&mutex);
// 通知端
pthread_mutex_lock(&mutex);
// 修改条件
pthread_cond_signal(&cond); // 或 broadcast
pthread_mutex_unlock(&mutex);
为什么用
while而非if? 防止虚假唤醒(spurious wakeup)------线程可能因信号或调度被唤醒,但条件并未真正满足。
四、实战案例
实验1:线程创建与参数传递
c
/**
* thread_create_demo.c ------ 演示线程创建、传参与join回收
* 编译: arm-linux-gnueabihf-gcc -Wall -g -o thread_create_demo thread_create_demo.c -lpthread
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
/* 线程入口函数 */
void *worker(void *arg)
{
int num = *(int *)arg;
printf("[线程 %ld] 收到参数: %d\n", (long)pthread_self(), num);
sleep(1);
int *result = malloc(sizeof(int));
*result = num * num; // 返回参数的平方
return (void *)result;
}
int main(void)
{
pthread_t tid[3];
int args[3] = {1, 2, 3};
int *retval;
/* 创建3个线程 */
for (int i = 0; i < 3; i++) {
if (pthread_create(&tid[i], NULL, worker, &args[i]) != 0) {
perror("pthread_create");
exit(EXIT_FAILURE);
}
printf("[主线程] 创建线程 %ld\n", (long)tid[i]);
}
/* 等待所有线程结束并获取返回值 */
for (int i = 0; i < 3; i++) {
pthread_join(tid[i], (void **)&retval);
printf("[主线程] 线程 %ld 返回: %d\n", (long)tid[i], *retval);
free(retval);
}
printf("[主线程] 全部线程结束\n");
return 0;
}
预期输出(顺序可能不同):
[主线程] 创建线程 ...
[线程 ...] 收到参数: 1
[线程 ...] 收到参数: 2
[线程 ...] 收到参数: 3
[主线程] 线程 ... 返回: 1
[主线程] 线程 ... 返回: 4
[主线程] 线程 ... 返回: 9
[主线程] 全部线程结束
实验2:互斥锁保护全局计数器
c
/**
* mutex_demo.c ------ 互斥锁保护共享资源
* 编译: arm-linux-gnueabihf-gcc -Wall -g -o mutex_demo mutex_demo.c -lpthread
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#define THREADS 10
#define ITERATIONS 100000
int counter = 0; // 共享资源
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void *increment(void *arg)
{
for (int i = 0; i < ITERATIONS; i++) {
pthread_mutex_lock(&lock);
counter++;
pthread_mutex_unlock(&lock);
}
return NULL;
}
int main(void)
{
pthread_t threads[THREADS];
/* 创建10个线程,每个自增100000次 */
for (int i = 0; i < THREADS; i++) {
pthread_create(&threads[i], NULL, increment, NULL);
}
for (int i = 0; i < THREADS; i++) {
pthread_join(threads[i], NULL);
}
printf("预期: %d, 实际: %d\n", THREADS * ITERATIONS, counter);
pthread_mutex_destroy(&lock);
return 0;
}
预期结果 :实际: 1000000。如果注释掉pthread_mutex_lock/unlock,结果将是不确定的错误值,这就是竞争条件。
实验3:生产者-消费者(条件变量)
c
/**
* producer_consumer_demo.c ------ 生产者-消费者模型
* 编译: arm-linux-gnueabihf-gcc -Wall -g -o producer_consumer_demo producer_consumer_demo.c -lpthread
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
int count = 0; // 缓冲区当前数据数量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond_producer = PTHREAD_COND_INITIALIZER; // 缓冲区不满时通知生产者
pthread_cond_t cond_consumer = PTHREAD_COND_INITIALIZER; // 缓冲区不空时通知消费者
void *producer(void *arg)
{
for (int i = 0; i < 10; i++) {
pthread_mutex_lock(&mutex);
while (count == BUFFER_SIZE) { // 缓冲区满,等待
pthread_cond_wait(&cond_producer, &mutex);
}
buffer[count] = i;
printf("[生产者] 放入: %d, 缓冲区数量: %d\n", i, count + 1);
count++;
pthread_cond_signal(&cond_consumer); // 通知消费者
pthread_mutex_unlock(&mutex);
sleep(1); // 模拟生产耗时
}
return NULL;
}
void *consumer(void *arg)
{
for (int i = 0; i < 10; i++) {
pthread_mutex_lock(&mutex);
while (count == 0) { // 缓冲区空,等待
pthread_cond_wait(&cond_consumer, &mutex);
}
count--;
int item = buffer[count];
printf("[消费者] 取出: %d, 缓冲区数量: %d\n", item, count);
pthread_cond_signal(&cond_producer); // 通知生产者
pthread_mutex_unlock(&mutex);
sleep(1); // 模拟消费耗时
}
return NULL;
}
int main(void)
{
pthread_t prod, cons;
pthread_create(&prod, NULL, producer, NULL);
pthread_create(&cons, NULL, consumer, NULL);
pthread_join(prod, NULL);
pthread_join(cons, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond_producer);
pthread_cond_destroy(&cond_consumer);
printf("生产者-消费者演示结束\n");
return 0;
}
实验4:简易线程池(C语言实现)
线程池是一种预创建一组线程,重复执行任务队列中任务的并发模型,避免了频繁创建销毁线程的开销。
c
/**
* threadpool.c ------ 简易线程池实现
* 编译: arm-linux-gnueabihf-gcc -Wall -g -o threadpool_demo threadpool.c -lpthread
*
* 线程池结构:一个任务队列、一组工作线程、互斥锁与条件变量
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
/* ============ 任务节点 ============ */
typedef struct task {
void (*function)(void *arg);
void *arg;
struct task *next;
} task_t;
/* ============ 线程池结构 ============ */
typedef struct {
task_t *task_head; // 任务队列头
task_t *task_tail; // 任务队列尾
pthread_mutex_t mutex; // 互斥锁
pthread_cond_t cond; // 条件变量(有新任务或销毁时发信号)
pthread_t *threads; // 工作线程数组
int thread_count; // 线程数
int shutdown; // 销毁标志
} threadpool_t;
/* 工作线程函数 */
static void *worker_thread(void *arg)
{
threadpool_t *pool = (threadpool_t *)arg;
while (1) {
pthread_mutex_lock(&pool->mutex);
/* 等待任务到来或销毁信号(使用 while 防虚假唤醒) */
while (pool->task_head == NULL && !pool->shutdown) {
pthread_cond_wait(&pool->cond, &pool->mutex);
}
if (pool->shutdown && pool->task_head == NULL) {
pthread_mutex_unlock(&pool->mutex);
break;
}
/* 取出队头任务 */
task_t *task = pool->task_head;
pool->task_head = task->next;
if (pool->task_head == NULL)
pool->task_tail = NULL;
pthread_mutex_unlock(&pool->mutex);
/* 执行任务 */
task->function(task->arg);
free(task);
}
return NULL;
}
/* 创建线程池 */
threadpool_t *threadpool_create(int num_threads)
{
threadpool_t *pool = malloc(sizeof(threadpool_t));
if (pool == NULL) return NULL;
pool->task_head = NULL;
pool->task_tail = NULL;
pool->shutdown = 0;
pool->thread_count = num_threads;
pthread_mutex_init(&pool->mutex, NULL);
pthread_cond_init(&pool->cond, NULL);
pool->threads = malloc(sizeof(pthread_t) * num_threads);
for (int i = 0; i < num_threads; i++) {
pthread_create(&pool->threads[i], NULL, worker_thread, pool);
}
return pool;
}
/* 添加任务到线程池 */
int threadpool_add_task(threadpool_t *pool, void (*function)(void *), void *arg)
{
task_t *task = malloc(sizeof(task_t));
if (task == NULL) return -1;
task->function = function;
task->arg = arg;
task->next = NULL;
pthread_mutex_lock(&pool->mutex);
if (pool->task_head == NULL) {
pool->task_head = task;
pool->task_tail = task;
} else {
pool->task_tail->next = task;
pool->task_tail = task;
}
pthread_cond_signal(&pool->cond); // 通知一个等待线程
pthread_mutex_unlock(&pool->mutex);
return 0;
}
/* 销毁线程池 */
void threadpool_destroy(threadpool_t *pool)
{
pthread_mutex_lock(&pool->mutex);
pool->shutdown = 1;
pthread_cond_broadcast(&pool->cond); // 唤醒所有线程
pthread_mutex_unlock(&pool->mutex);
/* 等待所有线程退出 */
for (int i = 0; i < pool->thread_count; i++) {
pthread_join(pool->threads[i], NULL);
}
free(pool->threads);
pthread_mutex_destroy(&pool->mutex);
pthread_cond_destroy(&pool->cond);
free(pool);
}
/* ============ 测试用例 ============ */
void sample_task(void *arg)
{
int id = *(int *)arg;
printf("[任务 %d] 由线程 %ld 执行\n", id, (long)pthread_self());
usleep(100000); // 模拟工作耗时
}
int main(void)
{
/* 创建一个4线程的线程池 */
threadpool_t *pool = threadpool_create(4);
/* 添加10个任务 */
int ids[10];
for (int i = 0; i < 10; i++) {
ids[i] = i + 1;
threadpool_add_task(pool, sample_task, &ids[i]);
}
/* 等待一段时间后销毁(销毁时会处理队列中剩余任务) */
sleep(2);
threadpool_destroy(pool);
printf("线程池演示结束\n");
return 0;
}
交叉编译与测试:
bash
arm-linux-gnueabihf-gcc -Wall -g -o threadpool_demo threadpool.c -lpthread
scp threadpool_demo root@192.168.1.100:/root/
./threadpool_demo
五、多线程编程陷阱与最佳实践
| 陷阱 | 现象 | 对策 |
|---|---|---|
| 数据竞争(无锁保护) | 结果不确定,偶发崩溃 | 用互斥锁保护共享数据 |
| 死锁(多锁顺序不一致) | 程序卡死 | 统一加锁顺序,或使用pthread_mutex_trylock |
| 忘记解锁 | 其他线程永久阻塞 | 使用goto或__attribute__((cleanup))确保解锁 |
| 虚假唤醒 | 条件变量被意外唤醒 | 始终用while而非if检查条件 |
| 线程泄漏(未join/detach) | 资源无法回收 | join或detach,或使用线程池 |
| 信号处理函数中使用锁 | 死锁 | 信号处理函数中禁止使用锁 |
| fork后多线程状态不一致 | 子进程只剩调用fork的线程 |
多线程程序fork后子进程应立即exec |
六、总结与思考题
本篇系统讲解了Linux多线程编程的核心:pthread创建、互斥锁保护、条件变量同步,并最终实现了一个简易但完整的线程池。
核心要点:
- 线程共享地址空间,通信直接但需同步
- 互斥锁保护临界区,条件变量实现等待-通知
- 条件变量必须配合
while检查条件,防止虚假唤醒 - 线程池预先创建线程,任务入队后由空闲线程执行
- 编译链接务必加上
-lpthread
思考题:
- 如果生产者-消费者示例中只有一个条件变量(生产者消费者共用),会有什么问题?请尝试修改代码验证。
- 线程池示例中,如果一个任务运行很久,会不会阻塞其他任务?如何改进以支持任务优先级?
- 多线程程序中调用
fork,子进程继承了多少线程?可能导致什么问题?
欢迎在评论区留下你的思考。下一篇我们将踏入网络编程------Socket、select/poll/epoll与高并发服务器的核心地带。
参考资料
- 《UNIX环境高级编程(APUE)》第11、12章 Threads
man pthread_create,man pthread_mutex_init,man pthread_cond_wait- POSIX Threads Programming (Blaise Barney, LLNL)