在前端开发内容学习中,c语言多线程编程实例:用pthread完成创建、同步与退出是常见主题。很多人在阅读时会遇到概念分散、步骤不清和注意点难以归纳的问题。本文按照基础概念、操作流程和关键细节,对相关内容进行整理。

c语言多线程编程实例常用于学习线程创建、参数传递与同步控制。本文用一个可编译的小案例说明pthread的基本写法,并解释互斥锁、等待退出和调试时最容易出错的地方。
多线程编程的核心,不是单纯把一个函数放进线程里运行,而是把任务拆开后,让多个执行流在同一进程内协同工作。入门实例最好同时覆盖创建线程、共享数据和主线程回收结果这三个环节。
如果示例只演示pthread_create,却没有展示共享变量如何保护,读者很容易误以为多个线程直接改同一份数据也没问题。真正可用的例子,必须把同步控制一起讲清楚。
下面这个例子创建4个工作线程,每个线程对全局计数器累加100000次。由于多个线程同时访问同一变量,所以使用互斥锁保护临界区,最后由主线程等待全部线程结束并输出结果。
完整示例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define THREAD_COUNT 4
#define LOOP_COUNT 100000
static long long counter = 0;
static pthread_mutex_t lock;
typedef struct {
int thread_id;
int loop_count;
} ThreadArg;
void *worker(void *arg) {
ThreadArg *data = (ThreadArg *)arg;
for (int i = 0; i < data->loop_count; i++) {
pthread_mutex_lock(&lock);
counter++;
pthread_mutex_unlock(&lock);
}
printf("thread %d finishedn", data->thread_id);
return NULL;
}
int main(void) {
pthread_t threads[THREAD_COUNT];
ThreadArg args[THREAD_COUNT];
if (pthread_mutex_init(&lock, NULL) != 0) {
fprintf(stderr, "mutex init failedn");
return 1;
}
for (int i = 0; i < THREAD_COUNT; i++) {
args[i].thread_id = i + 1;
args[i].loop_count = LOOP_COUNT;
if (pthread_create(&threads[i], NULL, worker, &args[i]) != 0) {
fprintf(stderr, "pthread_create failedn");
pthread_mutex_destroy(&lock);
return 1;
}
}
for (int i = 0; i < THREAD_COUNT; i++) {
pthread_join(threads[i], NULL);
}
printf("final counter = %lldn", counter);
pthread_mutex_destroy(&lock);
return 0;
}cc -std=c11 demo.c -o demo -pthread./demo线程函数的签名必须符合pthread要求,也就是接收一个void指针并返回一个void指针。这样做是为了统一接口,便于把不同类型的参数通过结构体包装后传入线程。
主线程里定义args数组而不是循环内临时变量,是为了保证传入各线程的参数地址始终有效。若把同一个循环变量地址直接传给多个线程,往往会出现线程编号重复或读取错值的问题。
pthread_create负责启动线程,参数里包含线程标识、属性、线程函数和传入数据。pthread_mutex_lock与pthread_mutex_unlock成对使用,用来保护共享变量counter。pthread_join让主线程等待子线程结束,避免主线程提前退出导致工作线程来不及完成。多个线程同时执行counter++时,这条语句并不是一次不可分割的动作。它通常包含读取旧值、计算新值、再写回内存几个步骤,所以并发执行时会发生竞争,结果往往小于理论值。
加锁的目的不是让程序变慢,而是让共享数据在关键时刻只允许一个线程修改。对教学实例来说,先把正确性建立起来,再去讨论性能优化,学习路径会更清晰。
很多人看到pthread_join,就会把线程退出理解成主线程等待结束。其实退出和等待是两个动作:子线程先结束自己的执行,主线程再选择是否回收它的状态。把这两个概念分开,才不容易在真实项目里写错。
在当前示例里,工作线程在函数末尾自然结束,并把空指针结果交给主线程获取。对线程函数来说,这和在线程内部主动调用退出接口并传回空指针,整体含义是一致的,都是让当前线程正常结束并把结果交给主线程处理。区别在于,自然执行到结尾更适合常规流程,按条件提前调用退出接口则更适合中途结束。
pthread_join(thread, NULL) 单纯等待结束。如果还想拿到线程退出值,可以传入一个 void 指针变量地址,例如把 ret 的地址传给 pthread_join。pthread_exit(ptr);,那么 ptr 会作为该线程的退出状态返回给 pthread_join。这个返回值通常用来表示处理结果、错误码或动态分配的数据地址。pthread_join,再结束 main。pthread_exit(NULL);,这样进程不会因为 main 返回而立即退出。教学示例里更常见、也更直观的做法,仍然是用 pthread_join 把线程全部回收完。初学者遇到的报错,很多不是语法问题,而是线程生命周期、参数地址和同步范围没有处理好。把这些问题提前排查,往往比盲目改代码更有效。
如果程序偶尔正确、偶尔错误,优先怀疑竞态条件;如果线程输出顺序不稳定,这通常是正常现象,不应把并发调度顺序误判成业务逻辑错误。
pthread_join,或者在线程尚未完成时就释放了相关资源。遇到这种情况,先看 main 是否过早 return,再看互斥锁和参数内存是否在 join 前被销毁。pthread.h,再确认编译命令不是只写了 cc demo.c,而是写成 cc -std=c11 demo.c -o demo -pthread。错误示例:复用同一个循环变量地址
for (int i = 0; i < THREAD_COUNT; i++) {
pthread_create(&threads[i], NULL, worker, &i);
}修正示例:为每个线程准备独立参数
for (int i = 0; i < THREAD_COUNT; i++) {
args[i].thread_id = i + 1;
args[i].loop_count = LOOP_COUNT;
pthread_create(&threads[i], NULL, worker, &args[i]);
}pthread_mutex_lock 和 pthread_mutex_unlock。掌握创建与同步之后,下一步可以把单一计数器例子升级成生产者消费者模型,或者把任务拆成多个线程分别处理不同数据块。这样能更直观地理解线程协作而不是只记住函数名。
如果想进一步提高性能,可以比较互斥锁版本和无共享数据版本的差异,再学习条件变量、读写锁或线程池。但这些扩展都应建立在当前这个基础实例已经完全吃透的前提上。
这个c语言多线程编程实例的重点,在于把线程创建、共享数据保护和结束等待连成一套完整流程。先把示例亲自编译运行,再尝试修改线程数和循环次数,更容易真正理解多线程程序的行为。