最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
c语言多线程编程实例:用pthread完成创建、同步与退出
时间:2026-09-09 17:00:50 编辑:袖梨 来源:一聚教程网
在前端开发内容学习中,c语言多线程编程实例:用pthread完成创建、同步与退出是常见主题。很多人在阅读时会遇到概念分散、步骤不清和注意点难以归纳的问题。本文按照基础概念、操作流程和关键细节,对相关内容进行整理。

c语言多线程编程实例常用于学习线程创建、参数传递与同步控制。本文用一个可编译的小案例说明pthread的基本写法,并解释互斥锁、等待退出和调试时最容易出错的地方。
一、先明确这个实例要解决什么问题
多线程编程的核心,不是单纯把一个函数放进线程里运行,而是把任务拆开后,让多个执行流在同一进程内协同工作。入门实例最好同时覆盖创建线程、共享数据和主线程回收结果这三个环节。
如果示例只演示pthread_create,却没有展示共享变量如何保护,读者很容易误以为多个线程直接改同一份数据也没问题。真正可用的例子,必须把同步控制一起讲清楚。
二、一个完整可编译的c语言多线程编程实例
下面这个例子创建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; }- 编译前确认系统支持pthread,且头文件与链接参数写法正确。不同平台的开发环境可能已经内置线程库,但示例代码本身的结构是相同的。
- 编译命令:
cc -std=c11 demo.c -o demo -pthread - 运行命令:
./demo - 这个程序的理论结果应为 4 × 100000 = 400000,所以最终输出里的 final counter 必须等于 400000,才说明加锁后的共享计数逻辑是正确的。
- 运行时4条 thread finished 日志的先后顺序可能每次都不同,这属于线程调度的正常现象。真正要核对的重点不是打印顺序,而是 final counter 是否稳定等于 400000。
三、这个实例的关键写法应该怎么理解
线程函数的签名必须符合pthread要求,也就是接收一个void指针并返回一个void指针。这样做是为了统一接口,便于把不同类型的参数通过结构体包装后传入线程。
主线程里定义args数组而不是循环内临时变量,是为了保证传入各线程的参数地址始终有效。若把同一个循环变量地址直接传给多个线程,往往会出现线程编号重复或读取错值的问题。
pthread_create负责启动线程,参数里包含线程标识、属性、线程函数和传入数据。pthread_mutex_lock与pthread_mutex_unlock成对使用,用来保护共享变量counter。pthread_join让主线程等待子线程结束,避免主线程提前退出导致工作线程来不及完成。
四、为什么这个例子一定要加互斥锁
多个线程同时执行counter++时,这条语句并不是一次不可分割的动作。它通常包含读取旧值、计算新值、再写回内存几个步骤,所以并发执行时会发生竞争,结果往往小于理论值。
加锁的目的不是让程序变慢,而是让共享数据在关键时刻只允许一个线程修改。对教学实例来说,先把正确性建立起来,再去讨论性能优化,学习路径会更清晰。
五、线程退出到底该怎么理解
很多人看到pthread_join,就会把线程退出理解成主线程等待结束。其实退出和等待是两个动作:子线程先结束自己的执行,主线程再选择是否回收它的状态。把这两个概念分开,才不容易在真实项目里写错。
在当前示例里,工作线程在函数末尾自然结束,并把空指针结果交给主线程获取。对线程函数来说,这和在线程内部主动调用退出接口并传回空指针,整体含义是一致的,都是让当前线程正常结束并把结果交给主线程处理。区别在于,自然执行到结尾更适合常规流程,按条件提前调用退出接口则更适合中途结束。
- 如果线程函数里写 return NULL;,主线程可以用
pthread_join(thread, NULL) 单纯等待结束。如果还想拿到线程退出值,可以传入一个 void 指针变量地址,例如把 ret 的地址传给pthread_join。 - 2. 若线程内部调用
pthread_exit(ptr);,那么 ptr 会作为该线程的退出状态返回给pthread_join。这个返回值通常用来表示处理结果、错误码或动态分配的数据地址。 - 3. 主线程自己如果在创建完子线程后直接 return 0;,整个进程会立刻结束,其他还没跑完的线程也会一起被终止。所以示例里必须先
pthread_join,再结束 main。 - 4. 如果主线程只想结束自己而让进程内其他线程继续运行,可以在特定场景调用
pthread_exit(NULL);,这样进程不会因为 main 返回而立即退出。教学示例里更常见、也更直观的做法,仍然是用pthread_join把线程全部回收完。
六、调试多线程程序时最常见的几个问题
初学者遇到的报错,很多不是语法问题,而是线程生命周期、参数地址和同步范围没有处理好。把这些问题提前排查,往往比盲目改代码更有效。
如果程序偶尔正确、偶尔错误,优先怀疑竞态条件;如果线程输出顺序不稳定,这通常是正常现象,不应把并发调度顺序误判成业务逻辑错误。
- 线程创建成功但结果异常,先检查传入线程函数的参数地址是否被重复复用。最直接的排查办法,是在线程函数里打印 thread_id 和参数地址,确认每个线程拿到的都是不同的结构体地址。
- 计数结果不稳定,先检查共享变量读写是否都放进了同一把锁保护范围。可以先故意把循环次数降到1000并重复运行几十次,如果 final counter 仍然偶发变化,通常说明临界区范围写错了。
- 主线程提前结束,通常是缺少
pthread_join,或者在线程尚未完成时就释放了相关资源。遇到这种情况,先看 main 是否过早 return,再看互斥锁和参数内存是否在 join 前被销毁。 - 编译时报未定义引用,通常是链接参数遗漏了-pthread。排查时先确认头文件已经包含
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); }- 错误原因是4个线程拿到的都是同一个 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]); }- 如果怀疑锁范围不对,也可以临时把 counter++ 前后各打印一次线程编号。虽然这样会让输出更乱,但能帮助确认是否真的每次修改都经过了
pthread_mutex_lock和pthread_mutex_unlock。
七、学会这个实例后可以继续练什么
掌握创建与同步之后,下一步可以把单一计数器例子升级成生产者消费者模型,或者把任务拆成多个线程分别处理不同数据块。这样能更直观地理解线程协作而不是只记住函数名。
如果想进一步提高性能,可以比较互斥锁版本和无共享数据版本的差异,再学习条件变量、读写锁或线程池。但这些扩展都应建立在当前这个基础实例已经完全吃透的前提上。
这个c语言多线程编程实例的重点,在于把线程创建、共享数据保护和结束等待连成一套完整流程。先把示例亲自编译运行,再尝试修改线程数和循环次数,更容易真正理解多线程程序的行为。
相关文章
- 交错战线控制角色谁更强-强度排行和选择建议 09-09
- c语言求最大公约数方法怎么写 09-09
- 斗破苍穹角色强度排行榜有哪些-常见内容和使用场景 09-09
- tplink无线网卡怎么发射wifi(tplink无线网卡发射wifi方法) 09-09
- c语言软件安装教程 09-09
- 无限轮回破解版免广告V1.07版怎么下载-安装方式和注意事项 09-09