首页/新闻资讯/正文详情

linux下c语言的多线程编程

发布时间:2026/11/5 2:54:05 来源:云帆数科 栏目:资讯中心
linux下c语言的多线程编程
前言在 Linux 上用 C 语言写多线程程序绕不开一个事实ISO C 标准直到 C11 才引入threads.h里的thrd_create等接口而且这套接口在 glibc 上的实现长期不完整实际项目几乎不用。Linux 上事实上的标准是 POSIX 线程POSIX Threads简称 pthread头文件是pthread.h由 glibc 的 NPTL 实现提供现代 glibc 已把 libpthread 合并进 libc但编译选项仍建议保留。本文讲的都是 pthread。初学者最常见的三个误解第一以为「开线程」就是调用一个函数然后各跑各的忽略了线程函数的固定签名和参数传递的生命周期第二以为加了printf就能看出竞态实际上竞态的窗口极窄看不到不等于没有第三以为volatile能代替锁这在 C 语言里同样是错的——C11 起标准明确了数据竞争data race是未定义行为undefined behavior, UB。本文从线程的创建与回收讲起依次说明互斥量、条件变量最后给出一个完整的有界缓冲区生产者—消费者示例。所有代码以 POSIX 接口为准编译时使用 GCC 的-pthread选项。一、线程的创建、回收与参数传递pthread 的核心接口只有几个接口原型要点作用pthread_create(pthread_t*, const pthread_attr_t*, void*(*)(void*), void*)创建线程pthread_join(pthread_t, void**)等待线程结束并取回返回值pthread_detach(pthread_t)标记为分离态结束后自动回收资源pthread_exit(void*)thread 主动退出并返回值pthread_self(void)取得当前线程 ID线程函数必须是void *func(void *arg)这一种签名返回值通过pthread_join的第二个参数取出。一个线程要么被join要么被detach两者只能选其一既没有 join 也没有 detach 的线程结束后其栈和线程描述符不会释放形成资源泄漏——这不会立刻报错长时间运行的服务进程会慢慢耗尽资源。向线程传参有个经典陷阱把循环变量的地址传进去。例如在一个for循环里反复pthread_create(tid, NULL, worker, i)所有线程拿到的都是同一个i的地址等线程真正调度起来时i早已改变甚至循环已经结束、变量生命周期已终止这时读取就是 UB。正确做法是每个线程传一份独立的数据例如用malloc分配一个小结构体或者直接传整数值并用intptr_t转换// 适用于 C11POSIX 线程为平台扩展#include pthread.h#include stdint.h#include stdio.h#include string.hstatic void *worker(void *arg){intptr_t id (intptr_t)arg; /* 值传递不依赖外部变量生命周期 */printf(worker %ld\n, (long)id);return NULL;}int main(void){pthread_t tid[4];for (intptr_t i 0; i 4; i) {int rc pthread_create(tid[i], NULL, worker, (void *)i);if (rc ! 0) {fprintf(stderr, pthread_create: %s\n, strerror(rc));return 1;}}for (int i 0; i 4; i) {pthread_join(tid[i], NULL);}return 0;}intptr_t保证能装下一个指针宽度(void *)i与(intptr_t)arg的往返转换在实现上可行如果嫌这种写法别扭就老老实实malloc一个结构体在线程里free。编译命令是gcc -stdc11 -Wall -Wextra -pthread demo.c -o demo-pthread同时影响预处理宏和链接比单独写-lpthread更稳妥。需要提醒pthread_create成功返回 0失败返回错误码它不设置errno所以不能用perror报告原因。示例把返回值交给strerror(rc)转成文字并因此包含了string.h。这一点在 pthread 系列函数里是统一的pthread_mutex_lock、pthread_join等都直接返回错误码。二、互斥量与共享数据多个线程读写同一块内存且至少有一个是写又没有同步就是数据竞争。C11 标准把它定为 UB不是「结果可能不对」这么轻——优化器可以假设不存在竞争从而做出让代码「看起来完全不合逻辑」的变换。保护共享数据用互斥量pthread_mutex_t静态初始化pthread_mutex_t m PTHREAD_MUTEX_INITIALIZER;动态初始化pthread_mutex_init(m, NULL);用完pthread_mutex_destroy(m);加锁pthread_mutex_lock(m);解锁pthread_mutex_unlock(m);关键纪律是「每条加锁路径都必须有对应的解锁」。C 语言没有析构函数函数中途return或跳goto时极易漏掉解锁之后所有线程都会永久阻塞。工程上的通用做法是单一出口 goto cleanup// 适用于 C11POSIX 线程为平台扩展#include pthread.h#include stdio.hstatic pthread_mutex_t g_lock PTHREAD_MUTEX_INITIALIZER;static long g_counter 0;static void *add_many(void *arg){long n *(long *)arg;for (long i 0; i n; i) {pthread_mutex_lock(g_lock);g_counter; /* 临界区尽量短 */pthread_mutex_unlock(g_lock);}return NULL;}这里g_counter是「读—改—写」三步不加锁时两个线程可能都读到旧值最后一次写入覆盖前一次总数偏小。注意临界区只包住真正共享的操作把printf、文件 IO 放进临界区会显著拉长持锁时间但这属于设计取舍不能靠「少加锁」来换正确性。pthread 互斥量默认不是递归的同一线程对同一把普通互斥量重复lock会死锁。需要递归语义必须用pthread_mutexattr_settype设置PTHREAD_MUTEX_RECURSIVE但递归锁往往意味着锁的粒度设计有问题优先重构而不是打开这个开关。三、条件变量与生产者—消费者条件变量解决的是「等某个条件成立」的问题。它必须和互斥量配合标准用法是pthread_mutex_lock(m);while (!condition) { /* 必须是 while不是 if */pthread_cond_wait(cv, m);}/* 此处持有锁条件成立 */pthread_mutex_unlock(m);pthread_cond_wait做三件事释放互斥量、阻塞等待、被唤醒后重新获取互斥量。必须用while而不是if因为存在虚假唤醒spurious wakeup也可能被唤醒后条件又被别的线程改掉。判断条件时一定是持有锁的。唤醒有pthread_cond_signal唤醒至少一个和pthread_cond_broadcast唤醒全部。当等待者等待的条件不同或者一次状态变化能满足多个等待者时用 broadcast。下面是一个完整的有界缓冲区示例一个生产者、一个消费者缓冲区满时生产者等待空时消费者等待// 适用于 C11POSIX 线程为平台扩展#include pthread.h#include stdio.h#include stdlib.h#define CAP 8#define TOTAL 32static int g_buf[CAP];static int g_head 0; /* 下一个写入位置 */static int g_tail 0; /* 下一个读取位置 */static int g_count 0;static pthread_mutex_t g_mtx PTHREAD_MUTEX_INITIALIZER;static pthread_cond_t g_not_full PTHREAD_COND_INITIALIZER;static pthread_cond_t g_not_empty PTHREAD_COND_INITIALIZER;static void *producer(void *arg){(void)arg;for (int i 0; i TOTAL; i) {pthread_mutex_lock(g_mtx);while (g_count CAP) {pthread_cond_wait(g_not_full, g_mtx);}g_buf[g_head] i;g_head (g_head 1) % CAP;g_count;pthread_cond_signal(g_not_empty);pthread_mutex_unlock(g_mtx);}return NULL;}static void *consumer(void *arg){(void)arg;for (int i 0; i TOTAL; i) {pthread_mutex_lock(g_mtx);while (g_count 0) {pthread_cond_wait(g_not_empty, g_mtx);}int v g_buf[g_tail];g_tail (g_tail 1) % CAP;--g_count;pthread_cond_signal(g_not_full);pthread_mutex_unlock(g_mtx);printf(consume %d\n, v);}return NULL;}int main(void){pthread_t tp, tc;int rc;rc pthread_create(tp, NULL, producer, NULL);if (rc ! 0) { fprintf(stderr, create producer: %d\n, rc); return 1; }rc pthread_create(tc, NULL, consumer, NULL);if (rc ! 0) { fprintf(stderr, create consumer: %d\n, rc); return 1; }pthread_join(tp, NULL);pthread_join(tc, NULL);pthread_mutex_destroy(g_mtx);pthread_cond_destroy(g_not_empty);pthread_cond_destroy(g_not_full);return 0;}这个例子里两个条件变量分别对应「不满」和「不空」是为了避免生产者把消费者唤醒后消费者发现还是空、又睡回去的无效唤醒。如果只用一个条件变量就必须用 broadcast正确性仍成立但效率差一些。常见坑点❌ 线程函数写成void worker(void *)或int worker(void *)。✅ 必须是void *worker(void *)返回值和参数都是void *否则pthread_create的类型检查通不过C 里会给出不兼容指针类型的警告强行转换则是 UB。❌ 把循环变量地址传给所有线程。✅ 用intptr_t值传递或为每个线程malloc独立的结构体后者要在线程内负责释放。❌ 创建线程后既不join也不detach。✅ 明确选择一种需要结果就join不需要就detach或者创建时把属性设为分离态。❌ 用if判断条件变量if (count 0) pthread_cond_wait(...)。✅ 必须用while循环重判条件以应对虚假唤醒和被抢先修改。❌ 把volatile int flag当作线程间同步。✅volatile只阻止编译器对该变量的部分优化不提供原子性和内存序C11 用stdatomic.h的_Atomic类型或用互斥量保护。❌ 在临界区里调用可能阻塞或无期限等待的函数如读网络、等另一个锁。✅ 临界区只做最短的必要操作把耗时工作移到锁外并避免锁的嵌套顺序不一致导致死锁。❌ 忘记-pthread只写gcc demo.c。✅ 编译和链接都加-pthread在某些平台上漏掉会在链接阶段报undefined reference to pthread_create。❌ 认为printf输出顺序就是线程执行顺序。✅ 线程调度由内核决定输出交错是正常现象需要顺序就显式同步。总结主题关键接口要点创建与回收pthread_create/pthread_join/pthread_detach签名固定必须 join 或 detach互斥pthread_mutex_lock/unlock每条路径都要解锁临界区尽量短条件等待pthread_cond_wait/signal/broadcast配互斥量用 while 重判条件原子操作stdatomic.hC11比手写锁轻但内存序要理解编译-pthread预处理与链接都要Linux 下的 C 多线程编程语法层面并不复杂难的是资源生命周期和同步纪律线程要有人收尸锁要有始有终条件要循环重判。把这三条守住绝大多数偶发的挂死和计数错误都能避免。真要排查竞态helgrind、ThreadSanitizer-fsanitizethread这类工具比盯着printf有效得多。

相关推荐

电梯双机并联系统故障诊断与预防性维护实战
电梯双机并联系统故障诊断与预防性维护实战

1. 问题现象与初步分析"13#东梯和西梯同时掉线"这个故障现象在设备运维领域属于典型的双机并联系统突发性失效案例。我去年在负责某商业综合体电梯群控系统改造时就遇到过几乎一模一样的场景——同一井道内互为备份的两台电梯在早高峰时段毫无征兆地同时停止运行&… · 2026/11/5 2:54:05

大数据可视化:认知基础与工程实践全解析
大数据可视化:认知基础与工程实践全解析

1. 大数据可视化的认知基础与价值定位在信息爆炸的时代,我们每天产生的数据量已经达到惊人的2.5万亿字节。面对如此庞大的数据海洋,人类大脑的认知系统却依然保持着原始的运作方式——MIT的研究表明,我们处理视觉信息的速度比处理文字快6万倍… · 2026/11/5 2:54:05

实时流处理中的状态管理:挑战与最佳实践
实时流处理中的状态管理:挑战与最佳实践

1. 实时流处理状态管理核心挑战在金融交易系统中,每秒需要处理数十万笔订单数据的状态跟踪;在智能电网监控场景下,要实时维护数百万传感器节点的运行状态记录。这些典型场景揭示了实时流处理中状态管理的三个本质难题:状态一致性困… · 2026/11/5 2:54:05

数据湖存储架构与性能优化实战指南
数据湖存储架构与性能优化实战指南

1. 数据湖存储架构深度解析数据湖作为大数据领域的核心基础设施,其存储架构与传统数据仓库有着本质区别。我曾在金融和电商行业主导过多个PB级数据湖建设项目,发现最关键的差异在于数据湖采用"先存储后处理"的范式。这种设计允许我们以原始格式… · 2026/11/5 4:42:54

实时语音转文字工具开发:技术选型与优化实践
实时语音转文字工具开发:技术选型与优化实践

1. 项目概述:语音转文字工具的核心价值在会议室里手忙脚乱记笔记的日子我受够了。去年参加一场产品需求讨论会时,我尝试用手机录音后转文字,结果发现三个致命问题:专业术语识别错误、多人对话分不清说话者、实时性差到需要等待处理… · 2026/11/5 4:42:54

TDengine时序数据库TSMA与RSMA机制解析与应用指南
TDengine时序数据库TSMA与RSMA机制解析与应用指南

1. TSMA与RSMA概念解析在TDengine时序数据库的实际应用中,TSMA(Time Series Merge Append)和RSMA(Rollup Summary Merge Append)是两种核心的数据合并机制。作为TDengine的深度使用者,我经常遇到开发者混淆… · 2026/11/5 4:42:54

scRNA-seq数据分析:高变基因筛选优化scTenifoldKnk计算效率
scRNA-seq数据分析:高变基因筛选优化scTenifoldKnk计算效率

1. 项目背景与核心问题单细胞RNA测序(scRNA-seq)数据分析中,scTenifoldKnk是一种常用的基因调控网络(GRN)构建和差异分析工具。但在处理大规模数据集时,计算资源消耗成为显著瓶颈。一个典型的10x Genomics数… · 2026/11/5 4:42:54

Maestro自动化测试工具:低代码YAML脚本实践指南
Maestro自动化测试工具:低代码YAML脚本实践指南

1. Maestro自动化测试工具概述 Maestro是一款新兴的跨平台自动化测试框架,专为现代应用测试需求设计。作为一名长期从事测试自动化的工程师,我发现它最大的优势在于用YAML这种人类可读的格式来描述测试流程,这比传统基于代码的测试脚本要直观… · 2026/11/5 4:42:54

量子神经混合计算:72维超智能系统架构解析
量子神经混合计算:72维超智能系统架构解析

1. 项目概述:量子-神经混合超智能生态系统的诞生背景在当代科技发展的最前沿,量子计算与神经科学的交叉融合正在催生新一代智能系统架构。7FLOOR Seventy-Two-Pointed Star(72星)超维工程系统正是这一趋势下的代表性产物。这个系统… · 2026/11/5 4:41:54

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38

多智能体协作系统架构设计与实践优化
多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中… · 2026/11/5 0:00:39

大模型系统性入门实战指南:从原理到部署微调的全链路路线
大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#… · 2026/11/5 0:00:39

鸿蒙文件系统沙箱机制解析与开发实践
鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的… · 2026/11/5 0:00:39

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码