C++手撕线程池
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>#define LL_ADD(item, list) do{ \item->prev = NULL; \item->next = list; \if (list != NULL) list-> prev = item;\list = item; \
} while(0) #define LL_REMOVE(item, list) do{ \if(item-> prev != NULL) item -> prev -> next = item -> next; \if(item-> next != NULL) item -> next -> prev = item -> prev; \ if(list == item) list = item -> next; \item-> prev = item -> next = NULL; \
} while(0) struct NJOB
{void (*func)(void *arg); // 回调函数每一个任务需要那些参数void *user_data; // 任务执行需要什么参数struct NJOB *prev;struct NJOB *next; //双向链表 队列
};
// 若干任务组织到一起struct NWORKER
{pthread_t id; // 每一个worker相当于一个线程int terminate;struct NMANAGER *pool;struct NWORKER *prev;struct NWORKER *next;};typedef struct NMANAGER
{pthread_mutex_t mtx; //用于抓取任务pthread_cond_t cond; //当任务队列为空的时候做的struct NJOB *jobs;struct NWORKER *workers;
} nThreadPool;void *thread_cb(void *arg)
{struct NWORKER *worker = (struct NWORKER*) arg;// 线程从队列里面拿到一个任务去执行// 线程去争夺队列里面的资源// 不断去执行,是一个循环while(1){// 具体内容是由job的回调函数决定的pthread_mutex_lock(&worker->pool->mtx);while(worker->pool->jobs == NULL){if (worker-> terminate) break;pthread_cond_wait(&worker->pool->cond, &worker->pool-> mtx);}// 如果job为空就让线程休息// 检查终止条件if (worker->terminate) {pthread_mutex_unlock(&worker->pool->mtx);break;}struct NJOB *job = worker-> pool-> jobs;if(job != NULL){LL_REMOVE(job, worker-> pool-> jobs); // 取出头结点}pthread_mutex_unlock(&worker->pool->mtx);// 执行任务if (job != NULL && job -> func != NULL){job-> func(job->user_data);// 注意:这里不释放job,由任务函数负责释放user_datafree(job);// 释放任务结构体本身}}
}// sdkint nThreadPoolCreate(nThreadPool *pool, int numWorkers)
{// if (numWorkers < 1) numWorkers = 1;if (pool == NULL) return -1;memset(pool, 0, sizeof(nThreadPool));//pthread_mutex_t blank_mutex = PTHREAD_MUTEX_INITIALIZER;memcpy(&pool->mtx, &blank_mutex, sizeof(pthread_mutex_t));// pthread_cond_t blank_cond = PTHREAD_COND_INITIALIZER;memcpy(&pool->cond, &blank_cond, sizeof(pthread_cond_t));//int i = 0;for(i =0;i<numWorkers;i++){struct NWORKER *worker = (struct NWORKER *)malloc(sizeof(struct NWORKER));if (worker == NULL){perror("malloc");return -2;}memset(worker, 0 ,sizeof(struct NWORKER) );worker->pool = pool;pthread_create(&worker->id, NULL, thread_cb,worker);LL_ADD(worker, pool->workers);}return 0;
}int nThreadPoolDestroy(nThreadPool *pool)
{if(pool == NULL)return -1;// 设置所有worker的终止标志struct NWORKER *worker = pool-> workers;while (worker != NULL){worker->terminate = 1;worker = worker -> next;}// 广播条件变量唤醒所有线程pthread_mutex_lock(&pool->mtx);pthread_cond_broadcast(&pool->cond);pthread_mutex_unlock(&pool->mtx);// 等待所有worker线程退出worker = pool->workers;while (worker != NULL) {pthread_join(worker->id, NULL);struct NWORKER *next = worker->next;free(worker);worker = next;}// 销毁互斥锁和条件变量pthread_mutex_destroy(&pool-> mtx);pthread_cond_destroy(&pool->cond);// 清空任务队列(如果有剩余任务)struct NJOB *job = pool->jobs;while (job!= NULL){struct NJOB *next = job->next;free(job);job = next;}// 重置线程池状态pool-> workers = NULL;pool->jobs = NULL;return 0;}int nThreadPoolPushTask(nThreadPool *pool, struct NJOB *job)
{//如何往线程池丢任务// 往队列里面加一个节点(job)// 同时通知一个worker(线程)// 因为线程都在条件等待pthread_mutex_lock(&pool->mtx);LL_ADD(job, pool->jobs);pthread_cond_signal(pool->cond);pthread_mutex_unlock(&pool->mtx);
}// // 测试函数
void debug_test()
{nThreadPool pool;const int NUM_THREADS = 10;const int NUM_TASKS = 1000;printf("Creating thread pool with %d workers...\n", NUM_THREADS);if (nThreadPoolCreate(&pool, NUM_THREADS) != 0) {fprintf(stderr, "Failed to create thread pool\n");return;}printf("Adding %d tasks to the pool...\n", NUM_TASKS);srand(time(NULL));for (int i = 0; i < NUM_TASKS; i++) {struct NJOB *job = (struct NJOB *)malloc(sizeof(struct NJOB));TaskData *data = (TaskData *)malloc(sizeof(TaskData));data->task_id = i;data->value = rand() % 100; // 随机值0-99job->func = task_function;job->user_data = data;nThreadPoolPushTask(&pool, job);}// 等待所有任务完成printf("Waiting for tasks to complete...\n");sleep(3); // 简单等待3秒// 销毁线程池printf("Destroying thread pool...\n");nThreadPoolDestroy(&pool);printf("All done!\n");
}#if 1 //#if 0是完美注释 #if 1即可运行
int main()
{debug_test();return 0;
}
#endif
互斥锁
互斥锁(Mutex)详解
互斥锁(Mutual Exclusion Lock,简称 Mutex)是多线程编程中用于保护共享资源不被同时访问的核心同步机制。它的核心作用是确保在任何给定时刻,只有一个线程可以访问被保护的资源或代码区域。
互斥锁的核心概念
1. 基本工作原理
2. 关键特性
- 互斥性:同一时刻只允许一个线程持有锁
- 原子性:锁的获取和释放操作是原子的(不可中断)
- 阻塞性:未获得锁的线程会进入等待状态
- 所有权:锁由获取它的线程独占持有
互斥锁的使用场景
场景类型 | 示例 | 互斥锁的作用 |
---|---|---|
数据竞争 | 多个线程访问同一变量 | 防止并发修改导致数据不一致 |
临界区保护 | 文件I/O操作 | 确保操作序列的完整性 |
资源管理 | 数据库连接池 | 控制有限资源的分配 |
状态同步 | 缓存更新机制 | 保持状态的一致性 |
互斥锁的操作原语(以POSIX为例)
基本操作流程
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// 线程函数
void* thread_func(void* arg) {pthread_mutex_lock(&mutex); // 1. 尝试获取锁// 临界区代码 - 安全访问共享资源pthread_mutex_unlock(&mutex); // 2. 释放锁return NULL;
}
锁的类型
锁类型 | 特性 | 适用场景 |
---|---|---|
普通锁 | 默认类型,不可递归获取 | 简单临界区保护 |
递归锁 | 同一线程可多次获取 | 递归函数或可重入代码 |
检错锁 | 检测死锁和重复锁定 | 调试阶段 |
自适应锁 | 优化自旋等待时间 | 高竞争环境 |
互斥锁的底层实现原理
1. 硬件支持
现代CPU提供原子指令实现锁:
- Test-and-Set 指令
- Compare-and-Swap (CAS) 指令
- Load-Link/Store-Conditional (LL/SC) 指令
2. 用户空间实现(Futex)
Linux的Futex(Fast Userspace Mutex)机制:
// 简化版Futex实现
void futex_lock(int* lock) {while (atomic_compare_exchange(lock, 0, 1) != 0) {// 锁已被占用,进入内核等待syscall(SYS_futex, lock, FUTEX_WAIT, 1, NULL, NULL, 0);}
}void futex_unlock(int* lock) {atomic_set(lock, 0);// 唤醒等待线程syscall(SYS_futex, lock, FUTEX_WAKE, 1, NULL, NULL, 0);
}
3. 内核支持
当锁竞争激烈时,操作系统内核介入:
- 将等待线程放入阻塞队列
- 在锁释放时调度等待线程
- 处理优先级反转问题
互斥锁的使用注意事项
1. 避免死锁
死锁产生的四个必要条件:
- 互斥条件
- 持有并等待
- 不可抢占
- 循环等待
预防策略:
- 固定加锁顺序
- 使用超时机制
- 死锁检测算法
2. 性能优化
技术 | 描述 | 效果 |
---|---|---|
细粒度锁 | 为不同资源使用独立锁 | 减少竞争 |
读写锁 | 区分读/写操作 | 提高读并发性 |
无锁编程 | CAS操作替代锁 | 完全避免锁开销 |
锁分段 | 将大资源分段加锁 | 减少热点竞争 |
3. 最佳实践
// 推荐用法:RAII模式(资源获取即初始化)
void critical_section() {LockGuard guard(&mutex); // 构造时加锁// 临界区代码// ...} // 析构时自动解锁(即使发生异常)
互斥锁与其他同步机制对比
机制 | 特点 | 适用场景 |
---|---|---|
互斥锁 | 独占访问,简单直接 | 通用临界区保护 |
自旋锁 | 忙等待,不放弃CPU | 锁持有时间极短 |
信号量 | 计数器,控制访问数量 | 资源池管理 |
条件变量 | 基于条件等待/通知 | 生产者-消费者模式 |
读写锁 | 区分读/写访问权限 | 读多写少场景 |
互斥锁在不同语言中的实现
C++ (std::mutex)
#include <mutex>std::mutex mtx;void safe_increment(int& value) {std::lock_guard<std::mutex> lock(mtx);value++;
}
Java (synchronized)
public class Counter {private int value;private final Object lock = new Object();public void increment() {synchronized(lock) {value++;}}
}
Python (threading.Lock)
import threadinglock = threading.Lock()
counter = 0def increment():global counterwith lock:counter += 1
互斥锁的性能考量
性能指标
- 获取延迟:从请求到获取锁的时间
- 吞吐量:单位时间处理的操作数
- 可扩展性:随着线程数增加的性能变化
优化策略
- 减少临界区大小:最小化加锁代码范围
- 使用本地副本:在临界区外准备数据
- 锁消除:分析确定不需要的锁
- 锁粗化:合并相邻的锁操作
总结
互斥锁是并发编程的基石,核心价值在于:
- 提供线程安全的共享资源访问
- 确保数据一致性
- 维护程序正确性
正确使用互斥锁需要:
- 精确识别需要保护的共享资源
- 保持临界区尽可能短小
- 建立一致的加锁顺序
- 使用RAII模式管理锁生命周期
- 在高竞争场景考虑替代方案
理解互斥锁的底层原理和最佳实践,是构建高性能、高可靠性并发系统的关键基础。
C++程序从编译到变为.exe文件发生了什么
将 C++ 源代码编译为 Windows 的 .exe
文件是一个多阶段的复杂过程,涉及多个工具和转换步骤。以下是详细的编译过程:
完整的编译流程
详细步骤解析
1. 预处理阶段
- 输入:
.cpp
源文件 + 头文件 - 输出:预处理后的源文件(通常为
.i
或.ii
) - 处理工具:预处理器(如
cpp
或编译器内置预处理器) - 主要任务:
- 处理所有
#
开头的预处理指令#include
→ 插入头文件内容#define
→ 宏替换#if/#ifdef
→ 条件编译
- 删除所有注释
- 添加行号标记(用于调试)
- 处理特殊宏(如
__LINE__
,__FILE__
)
- 处理所有
示例:
// main.cpp
#include <iostream>
#define PI 3.14159int main() {std::cout << "Value: " << PI; // 注释
}
↓ 预处理后 ↓
// 插入<iostream>的全部内容
namespace std { ... } // 约数千行代码
#line 1 "main.cpp"
int main() {std::cout << "Value: " << 3.14159;
}
2. 编译阶段
- 输入:预处理后的源文件(
.i
) - 输出:汇编代码文件(
.s
或.asm
) - 处理工具:编译器核心(如
g++
的编译前端) - 主要任务:
- 词法分析:将源代码分解为 token(关键字、标识符、运算符等)
- 语法分析:构建抽象语法树(AST)
- 语义分析:类型检查、作用域分析
- 中间代码生成:生成平台无关的中间表示(如 LLVM IR)
- 优化:进行各种优化(常量折叠、死代码消除等)
- 目标代码生成:生成特定CPU架构的汇编代码
生成的汇编示例(x86):
_main:push ebpmov ebp, esppush OFFSET FLAT:.LC0 ; "Value: "push 3.14159call __ZNSolsEf ; std::cout<<float...
3. 汇编阶段
- 输入:汇编代码文件(
.s
) - 输出:目标文件(
.obj
或.o
) - 处理工具:汇编器(如
as
或ml
) - 主要任务:
- 将汇编指令转换为机器码
- 生成目标文件格式(COFF 或 ELF)
- 创建符号表(函数/变量地址)
- 生成重定位信息(供链接器使用)
目标文件内容:
- 机器码(二进制指令)
- 数据段(初始化的全局变量)
- BSS 段(未初始化的全局变量)
- 符号表
- 重定位记录
4. 链接阶段
- 输入:多个目标文件(
.obj
) + 库文件(.lib
) - 输出:可执行文件(
.exe
) - 处理工具:链接器(如
ld
或link.exe
) - 主要任务:
- 符号解析:解决跨文件的函数/变量引用
- 重定位:合并所有目标文件,调整地址
- 解析外部依赖:链接标准库和第三方库
- 生成PE头:创建Windows可执行文件结构
链接过程关键点:
PE文件结构:
├── DOS头
├── PE文件头
├── 节表
├── .text节(代码)
├── .data节(初始化数据)
├── .rdata节(只读数据)
├── .bss节(未初始化数据)
├── 导入表(依赖的DLL)
├── 资源节(图标/版本信息)
└── 重定位表
完整工具链示例(GCC/MinGW)
# 1. 预处理(生成 main.ii)
g++ -E main.cpp -o main.ii# 2. 编译(生成 main.s)
g++ -S main.ii -o main.s# 3. 汇编(生成 main.obj)
as main.s -o main.obj# 4. 链接(生成 program.exe)
g++ main.obj -o program.exe
或单步完成:
g++ main.cpp -o program.exe
关键概念详解
1. 目标文件格式(COFF)
+-------------------+
| 文件头 |
+-------------------+
| 节表 |
+-------------------+
| .text 节(代码) |
+-------------------+
| .data 节(数据) |
+-------------------+
| .bss 节 |
+-------------------+
| 符号表 |
+-------------------+
| 重定位记录 |
+-------------------+
| 调试信息 |
+-------------------+
2. PE文件结构
+-----------------------+
| DOS Stub |
+-----------------------+
| PE Header |
+-----------------------+
| Section Table |
+-----------------------+
| .text (Code) |
+-----------------------+
| .data (Initialized) |
+-----------------------+
| .rdata (Constants) |
+-----------------------+
| .idata (Imports) |
+-----------------------+
| .rsrc (Resources) |
+-----------------------+
| .reloc (Relocations) |
+-----------------------+
3. 动态链接 vs 静态链接
特性 | 静态链接 | 动态链接 |
---|---|---|
文件大小 | 较大(包含所有依赖库) | 较小(仅包含引用) |
内存占用 | 每个程序独立占用内存 | 共享库节省内存 |
更新难度 | 需重新编译整个程序 | 只需替换DLL文件 |
加载速度 | 较快(无运行时加载开销) | 较慢(需加载DLL) |
示例 | -static 选项 | 默认方式 |
常见问题诊断
1. 链接错误示例
undefined reference to `someFunction()'
原因:
- 函数声明但未定义
- 缺少链接库
- 名称修饰不匹配(C vs C++)
2. 运行时错误
The procedure entry point could not be located in DLL
原因:
- 运行时DLL版本不匹配
- 缺少依赖的DLL文件
- 导出函数签名变更
3. 调试信息
- 编译选项:
-g
生成调试信息 - 文件格式:
- Windows:PDB(Program Database)
- Linux:DWARF
- 调试工具:
- Windows:WinDbg, Visual Studio Debugger
- Linux:GDB
优化技术
-
编译器优化:
-O1
:基础优化(死代码消除)-O2
:中级优化(循环展开)-O3
:激进优化(向量化)
-
LTO(链接时优化):
- 跨模块内联
- 全局数据流分析
- 使用:
g++ -flto
-
PGO(性能引导优化):
总结
从 C++ 源代码到 .exe
文件的转换过程:
- 预处理:文本级转换(宏/头文件展开)
- 编译:高级语言 → 汇编语言
- 汇编:汇编语言 → 机器码(目标文件)
- 链接:合并目标文件 + 解析外部依赖 → 可执行文件
理解这个过程有助于:
- 诊断编译/链接错误
- 优化构建性能
- 进行底层调试
- 创建高效的构建系统
- 理解程序的内存布局和执行原理
pthread库的常见用法以及使用函数
pthread_t
pthread_mutex_t
pthread_cond_t
pthread_mutex_lock
pthread_mutex_unlock
pthread_cond_wait
pthread_create
pthread_cond_broadcast
pthread_join
pthread_mutex_destroy
pthread_cond_destroy
pthread_cond_signal
pthread_mutex_lock
pthread_mutex_unlock
所有用到的pthread函数
pthread 库详解:常见用法与核心函数指南
一、pthread 库概述
POSIX Threads (pthread) 是 Unix/Linux 系统上实现多线程编程的标准 API,提供了一套完整的线程创建、管理和同步机制。
核心特性:
- 跨平台:支持所有 POSIX 兼容系统(Linux, macOS, BSD 等)
- 轻量级:线程创建和切换开销远小于进程
- 共享内存:同一进程的线程共享地址空间
- 丰富同步机制:提供互斥锁、条件变量、读写锁等
编译链接:
gcc -pthread program.c -o program
二、核心线程管理函数
1. 线程创建 - pthread_create()
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void *), void *arg);
thread
:输出参数,存储新线程IDattr
:线程属性(NULL 为默认属性)start_routine
:线程入口函数arg
:传递给线程函数的参数
示例:
void *print_message(void *message) {printf("%s\n", (char*)message);return NULL;
}int main() {pthread_t tid;char *msg = "Hello from thread!";pthread_create(&tid, NULL, print_message, msg);pthread_join(tid, NULL); // 等待线程结束return 0;
}
2. 线程终止 - pthread_exit()
void pthread_exit(void *retval);
- 用于线程内部主动退出
retval
:线程返回值(可由 pthread_join 获取)
3. 线程等待 - pthread_join()
int pthread_join(pthread_t thread, void **retval);
- 阻塞调用线程,直到目标线程结束
retval
:接收目标线程的返回值
4. 线程分离 - pthread_detach()
int pthread_detach(pthread_t thread);
- 将线程标记为"可分离",结束后自动回收资源
- 分离线程不能被 join
5. 线程取消 - pthread_cancel()
int pthread_cancel(pthread_t thread);
- 请求取消指定线程
- 目标线程需要设置取消点(如 sleep, wait 等)
三、线程同步机制
1. 互斥锁 (Mutex)
// 初始化(静态)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// 初始化(动态)
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);// 加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);// 尝试加锁(非阻塞)
int pthread_mutex_trylock(pthread_mutex_t *mutex);// 解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);// 销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
使用模式:
pthread_mutex_t lock;void *thread_func(void *arg) {pthread_mutex_lock(&lock);// 临界区代码pthread_mutex_unlock(&lock);return NULL;
}
2. 条件变量 (Condition Variables)
// 初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);// 等待条件
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);// 定时等待
int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex,const struct timespec *abstime);// 发送信号(唤醒一个等待线程)
int pthread_cond_signal(pthread_cond_t *cond);// 广播信号(唤醒所有等待线程)
int pthread_cond_broadcast(pthread_cond_t *cond);// 销毁
int pthread_cond_destroy(pthread_cond_t *cond);
生产者-消费者示例:
pthread_mutex_t lock;
pthread_cond_t cond;
int count = 0;void *producer(void *arg) {while(1) {pthread_mutex_lock(&lock);count++;printf("Produced: %d\n", count);pthread_cond_signal(&cond); // 唤醒消费者pthread_mutex_unlock(&lock);sleep(1);}
}void *consumer(void *arg) {while(1) {pthread_mutex_lock(&lock);while(count == 0) {pthread_cond_wait(&cond, &lock); // 等待生产}count--;printf("Consumed: %d\n", count);pthread_mutex_unlock(&lock);}
}
3. 读写锁 (Read-Write Lock)
// 初始化
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;// 读锁定(共享)
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);// 写锁定(独占)
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);// 解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
适用场景:
- 读操作远多于写操作
- 允许多个读线程同时访问
四、线程属性控制
1. 初始化/销毁属性
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
2. 常用属性设置
// 设置分离状态
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
// PTHREAD_CREATE_JOINABLE (默认) / PTHREAD_CREATE_DETACHED// 设置栈大小
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);// 设置调度策略
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
// SCHED_FIFO, SCHED_RR, SCHED_OTHER// 设置调度参数
int pthread_attr_setschedparam(pthread_attr_t *attr,const struct sched_param *param);
示例:
pthread_attr_t attr;
pthread_attr_init(&attr);// 设置为分离线程
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);// 设置栈大小为2MB
pthread_attr_setstacksize(&attr, 2 * 1024 * 1024);pthread_t tid;
pthread_create(&tid, &attr, thread_func, NULL);pthread_attr_destroy(&attr);
五、线程特定数据 (Thread-Specific Data)
允许每个线程拥有自己的私有数据副本
// 创建键
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));// 删除键
int pthread_key_delete(pthread_key_t key);// 设置键的值
int pthread_setspecific(pthread_key_t key, const void *value);// 获取键的值
void *pthread_getspecific(pthread_key_t key);
示例:
pthread_key_t tls_key;void destructor(void *value) {free(value);
}void init_tls() {pthread_key_create(&tls_key, destructor);
}void *thread_func(void *arg) {// 为每个线程分配私有数据int *data = malloc(sizeof(int));*data = pthread_self(); // 示例值pthread_setspecific(tls_key, data);// 获取私有数据int *my_data = pthread_getspecific(tls_key);printf("Thread %lu: my data = %d\n", (unsigned long)pthread_self(), *my_data);return NULL;
}
六、高级同步机制
1. 屏障 (Barriers)
// 初始化
int pthread_barrier_init(pthread_barrier_t *barrier,const pthread_barrierattr_t *attr,unsigned int count);// 等待
int pthread_barrier_wait(pthread_barrier_t *barrier);// 销毁
int pthread_barrier_destroy(pthread_barrier_t *barrier);
使用场景:多线程同步点,所有线程到达屏障前阻塞
2. 自旋锁 (Spinlocks)
// 初始化
pthread_spinlock_t spinlock;
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);// 加锁/解锁
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);
特点:忙等待锁,适用于锁持有时间极短的场景
七、最佳实践与常见陷阱
1. 线程安全设计原则
- 优先使用不可变数据结构
- 限制共享数据范围
- 使用RAII模式管理锁
- 避免锁嵌套(如必须,保持固定顺序)
- 优先使用高级并发抽象(线程池、任务队列)
2. 常见错误
// 错误1:返回栈上变量的指针
void *thread_func(void *arg) {int local = 42;return &local; // 错误!线程退出后局部变量被销毁
}// 错误2:忘记解锁
void critical_section() {pthread_mutex_lock(&lock);// 可能提前返回或抛出异常if(error) return; // 忘记解锁!pthread_mutex_unlock(&lock);
}// 错误3:虚假唤醒处理不当
while(!condition) { // 必须用循环检查条件pthread_cond_wait(&cond, &lock);
}
3. 调试技巧
- 使用
gdb
的info threads
查看线程状态 - 使用
valgrind --tool=helgrind
检测数据竞争 - 添加调试日志(带线程ID):
printf("[Thread %lu] %s\n", (unsigned long)pthread_self(), message);
八、完整示例:线程池实现
#include <pthread.h>
#include <stdlib.h>#define THREAD_POOL_SIZE 4typedef struct {void (*task)(void *);void *arg;
} Task;typedef struct {pthread_mutex_t lock;pthread_cond_t task_cond;pthread_t threads[THREAD_POOL_SIZE];Task *task_queue;int queue_size;int queue_capacity;int shutdown;
} ThreadPool;void *worker_thread(void *arg) {ThreadPool *pool = (ThreadPool *)arg;while(1) {pthread_mutex_lock(&pool->lock);// 等待任务或关闭信号while(pool->queue_size == 0 && !pool->shutdown) {pthread_cond_wait(&pool->task_cond, &pool->lock);}if(pool->shutdown) {pthread_mutex_unlock(&pool->lock);pthread_exit(NULL);}// 取出任务Task task = pool->task_queue[--pool->queue_size];pthread_mutex_unlock(&pool->lock);// 执行任务task.task(task.arg);}return NULL;
}void thread_pool_init(ThreadPool *pool) {pthread_mutex_init(&pool->lock, NULL);pthread_cond_init(&pool->task_cond, NULL);pool->queue_capacity = 10;pool->queue_size = 0;pool->task_queue = malloc(pool->queue_capacity * sizeof(Task));pool->shutdown = 0;for(int i = 0; i < THREAD_POOL_SIZE; i++) {pthread_create(&pool->threads[i], NULL, worker_thread, pool);}
}void thread_pool_submit(ThreadPool *pool, void (*task)(void *), void *arg) {pthread_mutex_lock(&pool->lock);// 扩展任务队列(如果需要)if(pool->queue_size == pool->queue_capacity) {pool->queue_capacity *= 2;pool->task_queue = realloc(pool->task_queue, pool->queue_capacity * sizeof(Task));}// 添加任务pool->task_queue[pool->queue_size].task = task;pool->task_queue[pool->queue_size].arg = arg;pool->queue_size++;pthread_cond_signal(&pool->task_cond);pthread_mutex_unlock(&pool->lock);
}void thread_pool_shutdown(ThreadPool *pool) {pthread_mutex_lock(&pool->lock);pool->shutdown = 1;pthread_cond_broadcast(&pool->task_cond);pthread_mutex_unlock(&pool->lock);for(int i = 0; i < THREAD_POOL_SIZE; i++) {pthread_join(pool->threads[i], NULL);}free(pool->task_queue);pthread_mutex_destroy(&pool->lock);pthread_cond_destroy(&pool->task_cond);
}
九、性能调优建议
-
线程数量优化
- CPU密集型:线程数 ≈ CPU核心数
- I/O密集型:线程数 ≈ (等待时间/计算时间 + 1) × CPU核心数
- 使用
sysconf(_SC_NPROCESSORS_ONLN)
获取CPU核心数
-
锁竞争优化
- 减小临界区范围
- 使用读写锁替代互斥锁
- 尝试无锁数据结构(atomic操作)
- 使用线程本地存储减少共享
-
内存优化
- 设置合理的线程栈大小
- 避免false sharing(缓存行对齐)
- 使用内存池减少动态分配
十、跨平台注意事项
-
Windows 兼容性:
- 使用 pthreads-win32 或 Windows Thread API
- 关键类型对应:
pthread_t
↔HANDLE
pthread_mutex_t
↔CRITICAL_SECTION
-
C++11 线程库:
- 标准替代方案:
std::thread
,std::mutex
等 - 优点:跨平台、类型安全
- 缺点:功能不如 pthread 丰富
- 标准替代方案:
总结
pthread 库提供了强大灵活的多线程编程能力,核心要点:
- 理解线程生命周期管理(create/join/detach)
- 掌握同步机制(mutex/condvar/rwlock)
- 合理使用线程特定数据
- 遵循线程安全设计原则
- 重视资源管理和错误处理
通过合理使用 pthread,可以构建高性能、高并发的应用程序,充分利用多核处理器的计算能力。