c语言多线程编程|pthread创建同步与回收示例
时间:2026-08-10 | 作者:半糖攻略君 | 阅读:0C语言多线程编程在很多场景下实际是指基于POSIX Threads(pthread)的并发编程方式,主要适用于Linux、Unix及其他支持pthreads的环境。本文围绕pthread_create、参数传递、线程同步、资源回收与常见问题展开,给出可编译运行的示例代码,帮助读者把线程创建、共享数据保护、失败路径处理和调试排查真正落到代码层面;如果是在Windows原生环境中开发,通常需要改用Windows线程API或使用相应的pthread兼容库。
理解线程与适用场景
线程是进程中的执行单元,同一进程内的线程共享代码区、全局变量和打开的文件等资源,但各自拥有独立的栈和执行状态。多线程适合处理相互独立且可以并发推进的任务,例如网络连接处理、后台计算、文件读写和生产者消费者模型。线程并不一定让程序运行得更快,若任务规模较小、共享数据过多或同步频繁,线程管理成本反而可能降低效率。开始设计前,应先明确哪些任务可以并行、哪些数据需要共享,以及程序如何结束所有线程。本文示例使用POSIX线程库pthread,主要面向Linux、Unix及其他兼容环境;若读者使用的是Windows原生线程模型,需要改用CreateThread等API,或使用兼容层后再参考相同思路。
创建线程并传递参数
在支持POSIX线程的环境中,可通过pthread_create创建线程。调用时需要提供线程标识、属性、入口函数和入口参数。入口函数通常接收一个void指针并返回一个void指针,因此可以借助结构体一次传递多个参数。传入的数据必须在线程使用期间保持有效,不能把即将离开作用域的局部变量地址交给仍在运行的线程。pthread系列函数大多直接返回错误码而不是设置errno,因此处理失败时应保存返回值,再结合strerror打印错误信息。下面给出一个完整示例,演示结构体参数传递、返回值检查、只回收已成功创建的线程,以及统一清理逻辑。
示例代码:
#include
#include
#include
#include
typedef struct {
int index;
const char *message;
} ThreadArg;
static void *worker(void *arg) {
ThreadArg *data = (ThreadArg *)arg;
printf("thread %d: %sn", data->index, data->message);
return NULL;
}
int main(void) {
enum { THREAD_COUNT = 3 };
pthread_t threads[THREAD_COUNT];
ThreadArg *args = calloc(THREAD_COUNT, sizeof(ThreadArg));
int created = 0;
int rc = 0;
if (args == NULL) {
perror("calloc");
return 1;
}
for (int i = 0; i < THREAD_COUNT; ++i) {
args[i].index = i;
args[i].message = "hello from pthread";
rc = pthread_create(&threads[i], NULL, worker, &args[i]);
if (rc != 0) {
fprintf(stderr, "pthread_create failed at %d: %sn", i, strerror(rc));
break;
}
created++;
}
for (int i = 0; i < created; ++i) {
rc = pthread_join(threads[i], NULL);
if (rc != 0) {
fprintf(stderr, "pthread_join failed at %d: %sn", i, strerror(rc));
}
}
free(args);
return created == THREAD_COUNT 0 : 1;
} 编译命令:
gcc demo_create.c -pthread -o demo_create可能输出:
thread 0: hello from pthread
thread 1: hello from pthread
thread 2: hello from pthread这个示例的重点是:创建线程失败后不再继续启动新线程,只对已经创建成功的线程执行pthread_join,再统一释放动态分配的参数内存。这样即使中途失败,也不会错误地回收未创建的线程。
- 检查
pthread_create和pthread_join的返回值,pthread函数失败时应根据返回码配合strerror输出错误信息 - 为每个线程准备独立且生命周期足够长的参数数据,避免传入即将失效的局部变量地址
- 线程部分创建成功时,记录成功数量并只回收已创建线程,避免错误地join无效线程
保护共享数据
多个线程同时读写同一数据时,可能发生数据竞争,导致结果不稳定或内存状态损坏。互斥锁可以保证同一时刻只有一个线程进入临界区,条件变量适合在线程之间等待某个状态成立。实际编码时,至少要明确初始化、加锁、等待、通知、解锁和销毁这些步骤,并且让等待线程始终在循环中检查条件,避免虚假唤醒。下面给出一个最小同步示例:两个工作线程累加共享计数,主线程等待计数达到目标值。
示例代码:
#include
#include
#include
#include
typedef struct {
pthread_mutex_t mutex;
pthread_cond_t cond;
int counter;
int target;
} SharedState;
static void cleanup_state(SharedState *state) {
pthread_cond_destroy(&state->cond);
pthread_mutex_destroy(&state->mutex);
}
static void *counter_worker(void *arg) {
SharedState *state = (SharedState *)arg;
for (int i = 0; i < 50000; ++i) {
pthread_mutex_lock(&state->mutex);
state->counter++;
if (state->counter >= state->target) {
pthread_cond_signal(&state->cond);
}
pthread_mutex_unlock(&state->mutex);
}
return NULL;
}
int main(void) {
SharedState state;
pthread_t t1, t2;
int rc;
state.counter = 0;
state.target = 100000;
rc = pthread_mutex_init(&state.mutex, NULL);
if (rc != 0) {
fprintf(stderr, "pthread_mutex_init failed: %sn", strerror(rc));
return 1;
}
rc = pthread_cond_init(&state.cond, NULL);
if (rc != 0) {
fprintf(stderr, "pthread_cond_init failed: %sn", strerror(rc));
pthread_mutex_destroy(&state.mutex);
return 1;
}
rc = pthread_create(&t1, NULL, counter_worker, &state);
if (rc != 0) {
fprintf(stderr, "pthread_create t1 failed: %sn", strerror(rc));
cleanup_state(&state);
return 1;
}
rc = pthread_create(&t2, NULL, counter_worker, &state);
if (rc != 0) {
fprintf(stderr, "pthread_create t2 failed: %sn", strerror(rc));
pthread_join(t1, NULL);
cleanup_state(&state);
return 1;
}
pthread_mutex_lock(&state.mutex);
while (state.counter < state.target) {
pthread_cond_wait(&state.cond, &state.mutex);
}
printf("counter reached %dn", state.counter);
pthread_mutex_unlock(&state.mutex);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
cleanup_state(&state);
return 0;
} 编译命令:
gcc demo_sync.c -pthread -o demo_sync这段代码覆盖了pthread_mutex_init、pthread_mutex_lock、pthread_mutex_unlock、pthread_cond_init、pthread_cond_wait、pthread_cond_signal和销毁流程。临界区内只执行共享计数修改与条件通知,避免把无关耗时操作放进锁内;一旦需要多个锁,还应规定统一的加锁顺序,以减少死锁风险。
- 共享状态的读取和修改应采用一致的同步策略,不能一部分加锁一部分裸读写
- 等待条件变量时使用
while循环反复检查状态,避免虚假唤醒造成逻辑错误 - 互斥锁、条件变量在不再使用后应调用
pthread_mutex_destroy和pthread_cond_destroy完成清理
线程结束、失败路径与错误排查
线程入口函数返回或调用pthread_exit后,该线程结束,但可连接线程仍需由其他线程执行pthread_join回收。若线程无需返回结果,可将其设置为分离状态,使系统在线程退出后自动释放资源。资源回收不只包括线程本身,也包括动态参数、互斥锁、条件变量等辅助对象。实际开发时,建议采用统一清理逻辑:记录已成功创建的线程数量,任何一步失败都跳转到同一清理出口,只回收已创建的线程,再释放锁和动态内存。若线程函数内部持锁后可能提前返回,应保证所有分支都能正确解锁,否则极易留下死锁。
并发问题排查可以直接借助ThreadSanitizer。它适合发现未加锁共享读写、错误的同步假设和部分使用后失效问题。常见做法是:
gcc race_demo.c -pthread -fsanitize=thread -g -o race_demo
./race_demo如果程序存在数据竞争,ThreadSanitizer通常会报告两个线程在同一内存地址上的冲突访问,并标出读写发生的函数、代码位置和线程创建栈。排查时可重点看三类信息:哪一个共享变量发生冲突、冲突访问是否缺少同一把锁保护、线程是从哪里创建出来的。若报告显示一个线程写入共享变量,另一个线程同时读取同一地址,而两边都没有经过同一同步原语,这通常就说明需要补充互斥锁、原子操作或条件同步。
- 避免主线程过早返回并终止整个进程,必要时等待所有可连接线程结束
- 不要对同一线程重复执行
pthread_join,失败路径中也应只回收已成功创建的线程 - 使用-
fsanitize=thread配合压力测试运行程序,优先定位数据竞争、锁使用不一致和线程退出顺序问题
可靠的pthread多线程程序依赖清楚的任务划分、严格的共享数据管理和完整的资源回收。先从少量线程和简单同步关系开始,逐步补齐返回值检查、失败路径清理和检测工具验证,再扩展到更复杂的并发模型,通常更容易控制复杂度并定位潜在问题。
免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。
相关文章
更多-
- c 语言基础教程怎么入门?语法、流程与示例说明
- 时间:2026-08-13
-
- c实现多线程 pthread创建回收与互斥锁同步
- 时间:2026-08-10
-
- c++11 正则表达式 std::regex写法与异常处理
- 时间:2026-08-10
-
- 多线程c语言实现 pthread创建同步与完整示例
- 时间:2026-08-10
-
- vc 正则表达式 std::regex与Boost.Regex选择
- 时间:2026-08-10
-
- c++ 正则表达 regex匹配提取替换示例
- 时间:2026-08-10
-
- c的多线程怎么实现原理 pthread原理与加锁示例
- 时间:2026-08-10
-
- 正则表达式c语言
- 时间:2026-08-10
精选合集
更多大家都在玩
大家都在看
更多-
- 糖尿病完全不能吃糖吗
- 时间:2026-09-15
-
- 蚂蚁庄园小课堂2026年9月16日最新题目答案
- 时间:2026-09-15
-
- 小鸡答题今天的答案是什么2026年9月16日
- 时间:2026-09-15
-
- 蚂蚁庄园每日答题答案2026年9月16日
- 时间:2026-09-15
-
- 以下哪种粮食是酿造绍兴黄酒的主要原料 蚂蚁庄园今日答案9月16日
- 时间:2026-09-15
-
- 劝学名句“及时当勉励,岁月不待人”出自哪位诗人 蚂蚁庄园今日答案9.16
- 时间:2026-09-15
-
- 蚂蚁庄园今天答题答案2026年9月16日
- 时间:2026-09-15
-
- 蚂蚁庄园答题今日答案2026年9月16日
- 时间:2026-09-15