第一章 前置基础:必懂概念与环境准备
1.1 核心概念(通俗比喻版)
表格
| 概念 | 通俗解释 | 核心特性 |
|---|---|---|
| 进程 | 一个独立的「工厂」,有独立的内存空间,进程之间互不干扰 | 系统资源分配的最小单位 |
| 线程 | 工厂里的「工人」,同一个工厂的所有工人共享工厂的全部资源,可以同时并行干活 | 系统调度的最小单位,同一个进程的所有线程共享全局变量、堆内存、文件等资源 |
| 线程同步 | 多个工人同时操作同一个工具 / 材料时,制定的规则,避免抢资源导致数据错乱、程序崩溃 | 多线程开发的核心难点 |
1.2 环境要求
- 运行环境:Ubuntu/Debian 系列 Linux 系统(含 100ask、正点原子等嵌入式开发板 Linux 环境)
- 编译工具:系统自带
gcc编译器(可通过gcc -v命令验证是否安装)
1.3 新手铁律:编译规则
所有用到#include <pthread.h>的线程代码,编译命令结尾必须加-lpthread
- 原因:
pthread不是 C 语言标准库,是 Linux 的 POSIX 线程库,GCC 不会默认链接,必须手动指定 - 新手 90% 的入门报错:
undefined reference to pthread_create,都是因为没加-lpthread
第二章 线程基础:创建你的第一个线程
2.1 核心目标
学会线程的创建方法,理解主线程与子线程的并发执行关系,掌握线程核心基础函数。
2.2 完整代码(带超详细注释)
文件名:pthread1.c
#include <pthread.h> // 线程库核心头文件:所有线程函数都依赖它
#include <stdio.h> // 标准输入输出头文件:用于printf打印日志
#include <unistd.h> // 系统调用头文件:用于sleep休眠函数
/**********************************************************************
* 函数名:my_thread_func
* 功能:子线程的入口函数(线程创建成功后,立刻执行这个函数)
* 参数:data - 主线程传给子线程的入参(本例传NULL,未使用)
* 返回值:void* - 线程退出时的返回值(本例返回NULL)
* 强制规则:POSIX线程的入口函数,格式必须是 void* 函数名(void* 入参),不可修改
**********************************************************************/
static void *my_thread_func (void *data)
{
// 子线程主逻辑:无限循环,每秒打印一次运行状态
while (1)
{
printf("子线程运行中...\n"); // 打印子线程状态
sleep(1); // 休眠1秒,避免CPU占用过高
}
return NULL; // 线程正常退出(本例中永远执行不到,因为上面是死循环)
}
/**********************************************************************
* 函数名:main
* 功能:程序入口函数,系统启动程序时,自动创建「主线程」执行main函数
* 参数:argc - 命令行参数个数;argv - 命令行参数内容数组
* 返回值:int - 程序退出码,0=正常退出,非0=异常退出
**********************************************************************/
int main(int argc, char **argv)
{
pthread_t tid; // 定义线程ID变量:存储子线程的唯一标识(相当于线程的身份证)
int ret; // 定义返回值变量:存储函数执行结果,判断是否成功
/**********************************************************************
* 函数名:pthread_create
* 功能:创建一个新的子线程
* 参数详解:
* 参数1:&tid - 传出参数,将新创建的线程ID保存到tid变量中
* 参数2:NULL - 线程属性,传NULL表示使用默认属性(默认栈大小、非分离状态)
* 参数3:my_thread_func - 线程入口函数,线程创建成功后立刻执行该函数
* 参数4:NULL - 传给线程入口函数的入参,无参数时传NULL
* 返回值:成功返回0,失败返回非0的错误码
**********************************************************************/
ret = pthread_create(&tid, NULL, my_thread_func, NULL);
if (ret != 0) // 返回值非0,说明线程创建失败
{
printf("线程创建失败!错误码:%d\n", ret); // 打印错误信息
return -1; // 程序异常退出
}
// 主线程主逻辑:先打印状态,再无限循环休眠,保持程序运行
printf("主线程运行中...\n");
while (1)
{
// 关键:如果主线程退出,整个进程会被系统终止,所有子线程都会被强制杀死
// 因此必须让主线程保持运行,这里用无限休眠实现
sleep(1);
}
return 0; // 程序正常退出(本例永远执行不到)
}
2.3 编译与运行
# 1. 编译代码(必须加-lpthread)
gcc -o pthread1 pthread1.c -lpthread
# 2. 运行程序
./pthread1
# 3. 关闭程序:按下 Ctrl + C 终止整个进程
2.4 核心执行流程
- 程序启动,系统自动创建主线程,执行
main函数 - 主线程调用
pthread_create创建子线程,子线程立刻开始执行my_thread_func函数 - 两个线程并发独立运行:主线程循环休眠,子线程每秒打印一次日志
- 按下
Ctrl + C,系统终止整个进程,所有线程自动关闭
2.5 线程基础核心函数详解
1. pthread_create:创建线程
函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
| 参数序号 | 参数名 | 详细说明 |
|---|---|---|
| 1 | thread | 传出参数,必须传pthread_t类型变量的地址,用于保存新线程的唯一 ID |
| 2 | attr | 线程属性,一般传NULL使用默认属性;可用于设置线程栈大小、分离状态、优先级等 |
| 3 | start_routine | 线程入口函数,必须遵守void* 函数名(void* arg)的格式,线程创建成功后立刻执行 |
| 4 | arg | 传给线程入口函数的参数,无参数时传NULL;可传递任意类型数据,需要强制类型转换 |
返回值:成功返回0,失败返回非 0 的错误码。 |
2. pthread_exit:线程主动退出
函数原型:void pthread_exit(void *retval);
- 参数
retval:线程退出的返回值,可被pthread_join函数获取,无返回值时传NULL - 作用:线程主动终止自己,不会影响同进程的其他线程运行。
3. pthread_join:等待线程退出并回收资源
函数原型:int pthread_join(pthread_t thread, void **retval);
| 参数序号 | 参数名 | 详细说明 |
|---|---|---|
| 1 | thread | 要等待的线程 ID |
| 2 | retval | 传出参数,用于接收线程退出的返回值,不关心返回值时传NULL |
- 作用:主线程阻塞等待指定的子线程完全退出,回收线程的系统资源,避免内存泄漏;是线程优雅退出的核心函数。
第三章 线程间通信:用全局变量实现数据交互
3.1 核心目标
理解线程的内存共享规则,实现主线程与子线程之间的数据传递,掌握线程间通信的基础方式。
3.2 核心前提:线程的内存规则
| 内存区域 | 共享 / 私有 | 说明 |
|---|---|---|
| 全局变量 / 静态变量 | 所有线程共享 | 存储在进程的全局数据区,所有线程都能读写,是线程间通信最简单的方式 |
| 堆内存(malloc 分配) | 所有线程共享 | 只要传递内存地址,所有线程都能访问 |
| 栈内存(函数内局部变量) | 线程私有 | 只有当前线程能访问,其他线程无法访问,不能用于线程间通信 |
3.3 完整代码(带超详细注释)
文件名:pthread2.c
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
/**********************************************************************
* 全局共享变量:
* 所有线程都能访问,用于主线程和子线程之间传递数据
**********************************************************************/
static char g_buf[1000]; // 共享数据缓冲区:存放主线程从键盘输入的文字
static int g_hasData = 0; // 状态标志位:0=缓冲区无新数据,1=缓冲区有新数据待处理
/**********************************************************************
* 函数名:my_thread_func
* 功能:子线程入口函数,循环等待数据,收到后打印内容
**********************************************************************/
static void *my_thread_func (void *data)
{
while (1) // 无限循环,持续等待数据
{
/**********************************************************************
* 忙等(Busy Waiting):
* 无数据时,子线程会一直死循环检查标志位,CPU占用率100%,极度浪费系统资源
* 这是本方案的致命缺陷,后续章节会通过信号量、条件变量彻底解决
**********************************************************************/
while (g_hasData == 0); // 标志位为0时,一直循环等待
// 标志位变为1,说明有新数据,打印缓冲区内容
printf("recv: %s\n", g_buf);
// 处理完成后,清空标志位,等待下一次数据
g_hasData = 0;
}
return NULL;
}
int main(int argc, char **argv)
{
pthread_t tid;
int ret;
// 创建子线程
ret = pthread_create(&tid, NULL, my_thread_func, NULL);
if (ret != 0)
{
printf("线程创建失败!\n");
return -1;
}
// 主线程主逻辑:无限循环读取键盘输入
while (1)
{
/**********************************************************************
* 函数名:fgets
* 功能:从标准输入(键盘)读取一行文字
* 参数1:g_buf - 目标缓冲区,将读取的内容存入这里
* 参数2:1000 - 最大读取字节数,防止缓冲区溢出
* 参数3:stdin - 标准输入流(对应键盘)
**********************************************************************/
fgets(g_buf, 1000, stdin);
// 数据存入缓冲区后,设置标志位为1,通知子线程有新数据
g_hasData = 1;
}
return 0;
}
3.4 编译与运行
# 编译
gcc -o pthread2 pthread2.c -lpthread
# 运行
./pthread2
运行效果:输入任意文字按下回车,子线程会立刻打印recv: 你输入的内容。
3.5 执行流程
- 程序启动,主线程创建子线程,子线程进入忙等循环,检查
g_hasData标志位 - 主线程等待键盘输入,你输入文字按下回车后,
fgets将内容存入g_buf - 主线程设置
g_hasData=1,通知子线程有新数据 - 子线程检测到标志位变化,跳出循环,打印数据,然后清空标志位,回到等待状态
- 循环往复,直到按下
Ctrl+C终止程序
3.6 本方案的核心问题
- 致命缺陷:忙等浪费 CPU:子线程无数据时,会一直死循环检查标志位,CPU 占用率 100%,完全无法用于正式项目
- 隐藏问题:数据竞争:主线程写数据、子线程读数据没有保护,极端情况下会出现数据错乱
第四章 信号量:解决忙等问题的线程同步工具
4.1 核心目标
用信号量实现「线程无数据时休眠,有数据时唤醒」,彻底解决忙等浪费 CPU 的问题,掌握信号量的核心用法。
4.2 通俗概念
信号量是一个计数器 + 线程等待队列,核心是两个操作:
- P 操作(sem_wait):计数器 - 1,如果计数器 < 0,线程阻塞休眠,释放 CPU,直到被唤醒
- V 操作(sem_post):计数器 + 1,如果有线程正在休眠等待,唤醒其中一个线程
- 通俗比喻:信号量就是「门铃」,子线程在屋里睡觉,主线程按门铃叫醒子线程干活
4.3 完整代码(带超详细注释)
文件名:pthread_sem.c
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h> // 信号量核心头文件
#include <string.h> // 字符串操作头文件,用于memcpy内存复制
static char g_buf[1000]; // 共享数据缓冲区
static sem_t g_sem; // 信号量变量:用于线程间的通知
/**********************************************************************
* 函数名:my_thread_func
* 功能:子线程入口函数,等待信号量通知,唤醒后打印数据
**********************************************************************/
static void *my_thread_func (void *data)
{
while (1)
{
/**********************************************************************
* 函数名:sem_wait
* 功能:信号量P操作,等待通知
* 执行逻辑:
* 1. 信号量计数器-1
* 2. 如果计数器≥0,函数直接返回,继续执行后续代码
* 3. 如果计数器<0,线程阻塞休眠,释放CPU,直到被sem_post唤醒
* 解决的问题:彻底替代忙等,无数据时线程休眠,不浪费CPU
**********************************************************************/
sem_wait(&g_sem);
// 被信号量唤醒后,打印共享缓冲区的数据
printf("recv: %s\n", g_buf);
}
return NULL;
}
int main(int argc, char **argv)
{
pthread_t tid;
int ret;
char buf[1000]; // 主线程私有局部变量:临时存放键盘输入的内容
/**********************************************************************
* 函数名:sem_init
* 功能:初始化信号量
* 参数详解:
* 参数1:&g_sem - 要初始化的信号量变量的地址
* 参数2:0 - 共享范围:0=仅本进程内的线程使用,非0=跨进程共享
* 参数3:0 - 信号量初始计数值:初始为0,子线程调用sem_wait时会直接休眠
**********************************************************************/
sem_init(&g_sem, 0, 0);
// 创建子线程
ret = pthread_create(&tid, NULL, my_thread_func, NULL);
if (ret != 0)
{
printf("线程创建失败!\n");
return -1;
}
while (1)
{
// 从键盘读取输入,存入主线程私有变量buf
fgets(buf, 1000, stdin);
/**********************************************************************
* 函数名:memcpy
* 功能:内存复制,将输入的内容复制到共享缓冲区
* 参数1:g_buf - 目标内存地址
* 参数2:buf - 源内存地址
* 参数3:1000 - 要复制的字节数
**********************************************************************/
memcpy(g_buf, buf, 1000);
/**********************************************************************
* 函数名:sem_post
* 功能:信号量V操作,发送通知,唤醒休眠的线程
* 执行逻辑:
* 1. 信号量计数器+1
* 2. 如果有线程正在sem_wait上休眠,唤醒其中一个线程
**********************************************************************/
sem_post(&g_sem);
}
return 0;
}
4.4 编译与运行
# 编译
gcc -o pthread_sem pthread_sem.c -lpthread
# 运行
./pthread_sem
4.5 核心函数详解
表格
| 函数名 | 函数原型 | 核心作用 |
|---|---|---|
| sem_init | int sem_init(sem_t *sem, int pshared, unsigned int value); | 初始化信号量,设置共享范围和初始计数值 |
| sem_wait | int sem_wait(sem_t *sem); | P 操作,等待信号量,无通知时线程休眠 |
| sem_post | int sem_post(sem_t *sem); | V 操作,发送信号量,唤醒休眠的线程 |
| sem_destroy | int sem_destroy(sem_t *sem); | 销毁信号量,释放系统资源 |
4.6 本方案的优缺点
- ✅ 优点:彻底解决忙等问题,无数据时线程休眠,CPU 占用率极低,逻辑简单
- ❌ 缺点:没有保护共享数据,多线程同时读写
g_buf时,极端情况下会出现数据错乱,需要配合互斥锁使用
第五章 Linux 标准方案:互斥锁 + 条件变量
5.1 核心目标
- 用互斥锁保护共享数据,彻底解决多线程数据竞争问题
- 用条件变量实现线程的等待 / 唤醒,是 Linux 多线程开发工业级标准方案,绝大多数正式项目都采用这个组合
5.2 通俗概念
- 互斥锁:共享数据的「门锁」,同一时间只能有一个线程持有锁,保证只有一个线程能操作共享数据,从根源上避免数据错乱
- 条件变量:线程的「闹钟」,必须和互斥锁配合使用,线程无数据时休眠,有数据时被精准唤醒
5.3 完整代码(带超详细注释)
文件名:pthread_cond.c
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
// 共享数据缓冲区:主线程写入,子线程读取
static char g_buf[1000];
/**********************************************************************
* 互斥锁:静态初始化
* 作用:保护共享数据g_buf,同一时间只能有一个线程持有锁
* 静态初始化直接使用PTHREAD_MUTEX_INITIALIZER,无需调用pthread_mutex_init函数
**********************************************************************/
static pthread_mutex_t g_tMutex = PTHREAD_MUTEX_INITIALIZER;
/**********************************************************************
* 条件变量:静态初始化
* 作用:实现线程的等待/唤醒,必须和互斥锁配合使用
* 静态初始化直接使用PTHREAD_COND_INITIALIZER,无需调用pthread_cond_init函数
**********************************************************************/
static pthread_cond_t g_tConVar = PTHREAD_COND_INITIALIZER;
/**********************************************************************
* 函数名:my_thread_func
* 功能:子线程入口函数,等待条件变量通知,安全读取并打印共享数据
**********************************************************************/
static void *my_thread_func (void *data)
{
while (1)
{
/**********************************************************************
* 第一步:加互斥锁
* 必须先持有锁,才能调用pthread_cond_wait,这是强制规则
**********************************************************************/
pthread_mutex_lock(&g_tMutex);
/**********************************************************************
* 函数名:pthread_cond_wait
* 功能:等待条件变量,线程休眠
* 参数详解:
* 参数1:&g_tConVar - 要等待的条件变量
* 参数2:&g_tMutex - 已经持有的互斥锁
* 核心执行逻辑(新手必背):
* 1. 自动释放持有的互斥锁(让主线程能操作共享数据)
* 2. 线程阻塞休眠,释放CPU,不浪费系统资源
* 3. 被唤醒后,自动重新持有互斥锁(保证操作共享数据的安全性)
**********************************************************************/
pthread_cond_wait(&g_tConVar, &g_tMutex);
// 被唤醒后,持有互斥锁,安全操作共享数据,打印内容
printf("recv: %s\n", g_buf);
/**********************************************************************
* 第四步:解锁互斥锁
* 释放锁,让其他线程可以持有锁操作共享数据
**********************************************************************/
pthread_mutex_unlock(&g_tMutex);
}
return NULL;
}
int main(int argc, char **argv)
{
pthread_t tid;
int ret;
char buf[1000]; // 主线程私有变量,临时存放键盘输入
// 创建子线程
ret = pthread_create(&tid, NULL, my_thread_func, NULL);
if (ret != 0)
{
printf("线程创建失败!\n");
return -1;
}
while (1)
{
// 读取键盘输入,存入私有变量buf
fgets(buf, 1000, stdin);
/**********************************************************************
* 主线程标准操作流程:加锁 → 写入共享数据 → 发通知 → 解锁
**********************************************************************/
pthread_mutex_lock(&g_tMutex); // 加锁,独占共享数据
memcpy(g_buf, buf, 1000); // 将输入的内容复制到共享缓冲区
/**********************************************************************
* 函数名:pthread_cond_signal
* 功能:唤醒一个正在条件变量上休眠的线程
* 配套函数:pthread_cond_broadcast - 唤醒所有正在条件变量上休眠的线程
**********************************************************************/
pthread_cond_signal(&g_tConVar); // 发送通知,唤醒子线程
pthread_mutex_unlock(&g_tMutex); // 解锁,释放共享数据
}
return 0;
}
5.4 编译与运行
# 编译
gcc -o pthread_cond pthread_cond.c -lpthread
# 运行
./pthread_cond
5.5 核心函数详解
1. 互斥锁核心函数
表格
| 函数名 | 函数原型 | 核心作用 |
|---|---|---|
| pthread_mutex_init | int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); | 动态初始化互斥锁,attr 传 NULL 使用默认属性 |
| pthread_mutex_lock | int pthread_mutex_lock(pthread_mutex_t *mutex); | 加锁,锁被占用时线程阻塞休眠 |
| pthread_mutex_unlock | int pthread_mutex_unlock(pthread_mutex_t *mutex); | 解锁,释放锁,唤醒等待的线程 |
| pthread_mutex_destroy | int pthread_mutex_destroy(pthread_mutex_t *mutex); | 销毁互斥锁,释放系统资源 |
2. 条件变量核心函数
表格
| 函数名 | 函数原型 | 核心作用 |
|---|---|---|
| pthread_cond_init | int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); | 动态初始化条件变量 |
| pthread_cond_wait | int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); | 等待条件变量,线程休眠,自动处理锁的释放与重持 |
| pthread_cond_signal | int pthread_cond_signal(pthread_cond_t *cond); | 唤醒一个休眠的线程 |
| pthread_cond_broadcast | int pthread_cond_broadcast(pthread_cond_t *cond); | 唤醒所有休眠的线程 |
| pthread_cond_destroy | int pthread_cond_destroy(pthread_cond_t *cond); | 销毁条件变量,释放系统资源 |
5.6 本方案的优势
- ✅ 互斥锁保证共享数据的绝对安全,彻底解决数据竞争问题
- ✅ 条件变量实现线程的精准休眠 / 唤醒,完全不浪费 CPU
- ✅ 是 Linux 多线程开发的工业级标准方案,兼容性、稳定性拉满,适用于绝大多数业务场景
第六章 高级同步锁:自旋锁与读写锁
6.1 自旋锁(Spin Lock)
核心概念
自旋锁和互斥锁的核心区别在于抢不到锁时的行为:
- 互斥锁:抢不到锁 → 线程休眠等待,释放 CPU
- 自旋锁:抢不到锁 → 线程原地循环(自旋)等待,一直占用 CPU,直到抢到锁
- 通俗比喻:互斥锁是在厕所门口睡觉等,自旋锁是在厕所门口原地转圈等
- 适用场景:锁内的操作极快、耗时极短(比如仅修改一个变量),等待时间远小于线程切换的耗时,此时自旋锁效率远高于互斥锁
完整代码(带超详细注释)
文件名:pthread_spin.c
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
// 自旋锁变量
pthread_spinlock_t spinlock;
// 共享数据:10个线程共同修改
int shared_data = 0;
/**********************************************************************
* 函数名:my_thread_func
* 功能:子线程入口函数,用自旋锁保护共享数据的修改
**********************************************************************/
static void *my_thread_func (void *data)
{
int i = (int)data; // 接收主线程传来的线程编号(0~9)
while (1)
{
/**********************************************************************
* 函数名:pthread_spin_lock
* 功能:加自旋锁
* 逻辑:如果锁已被其他线程持有,当前线程原地循环自旋,不释放CPU
**********************************************************************/
pthread_spin_lock(&spinlock);
// 持有锁,安全修改共享数据
shared_data++;
// 打印线程编号和修改后的数据值
printf("Thread %d 修改共享数据,当前值:%d\n", i, shared_data);
/**********************************************************************
* 函数名:pthread_spin_unlock
* 功能:释放自旋锁
**********************************************************************/
pthread_spin_unlock(&spinlock);
sleep(1); // 休眠1秒,方便观察输出
}
return NULL;
}
int main(int argc, char **argv)
{
pthread_t tid;
int ret;
int i;
/**********************************************************************
* 函数名:pthread_spin_init
* 功能:初始化自旋锁
* 参数1:&spinlock - 要初始化的自旋锁地址
* 参数2:PTHREAD_PROCESS_PRIVATE - 共享范围,仅本进程内的线程使用
**********************************************************************/
pthread_spin_init(&spinlock, PTHREAD_PROCESS_PRIVATE);
// 循环创建10个子线程
for (i =0; i < 10; i++)
{
// 将线程编号i强制转换为void*,传递给子线程
ret = pthread_create(&tid, NULL, my_thread_func, (void *)i);
if (ret != 0)
{
printf("线程创建失败!\n");
return -1;
}
}
// 主线程无限休眠,保持程序运行
while (1) sleep(100);
// 销毁自旋锁,释放系统资源
pthread_spin_destroy(&spinlock);
return 0;
}
编译与运行
# 编译
gcc -o pthread_spin pthread_spin.c -lpthread
# 运行
./pthread_spin
6.2 读写锁(Read-Write Lock)
核心概念
读写锁专门为读多写少的场景设计,遵守 3 条铁律:
- 读 + 读共享:多个线程可以同时持有读锁,并发读取数据,互不影响
- 写 + 写互斥:同一时间只能有一个线程持有写锁
- 读 + 写互斥:读锁和写锁不能同时持有,写的时候不能读,读的时候不能写
- 通俗比喻:读写锁是图书馆,看书(读)的人可以一起进,修改书籍(写)的人只能一个人进,且修改时其他人不能进
- 适用场景:数据读取频率远高于修改频率的场景(比如配置文件读取、日志查询、状态上报),效率远超普通互斥锁
完整代码(带超详细注释)
文件名:pthread_rwlock.c
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
/**********************************************************************
* 读写锁:静态初始化
* 核心规则:读共享、写独占、读写互斥
**********************************************************************/
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
// 共享数据:写线程修改,读线程读取
int shared_data = 0;
/**********************************************************************
* 函数名:my_thread_write_func
* 功能:写线程入口函数,用写锁保护共享数据的修改
**********************************************************************/
static void *my_thread_write_func (void *data)
{
int i = (int)data; // 写线程编号(0~9)
while (1)
{
/**********************************************************************
* 函数名:pthread_rwlock_wrlock
* 功能:加写锁(独占锁)
* 逻辑:如果有其他线程持有读锁/写锁,当前线程阻塞等待
**********************************************************************/
pthread_rwlock_wrlock(&rwlock);
// 持有写锁,安全修改共享数据
shared_data++;
printf("写线程%d 修改数据,当前值:%d\n", i, shared_data);
/**********************************************************************
* 函数名:pthread_rwlock_unlock
* 功能:通用解锁函数,读锁和写锁都用这个函数解锁
**********************************************************************/
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
return NULL;
}
/**********************************************************************
* 函数名:my_thread_read_func
* 功能:读线程入口函数,用读锁安全读取共享数据
**********************************************************************/
static void *my_thread_read_func (void *data)
{
int i = (int)data; // 读线程编号(0~9)
while (1)
{
/**********************************************************************
* 函数名:pthread_rwlock_rdlock
* 功能:加读锁(共享锁)
* 逻辑:如果没有其他线程持有写锁,当前线程可以加读锁;多个读线程可同时持有读锁
**********************************************************************/
pthread_rwlock_rdlock(&rwlock);
// 持有读锁,安全读取共享数据
printf("读线程%d 读取数据,当前值:%d\n", i, shared_data);
// 解锁
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
return NULL;
}
int main(int argc, char **argv)
{
pthread_t tid;
int ret;
int i;
// 循环创建10个写线程
for (i =0; i < 10; i++)
{
ret = pthread_create(&tid, NULL, my_thread_write_func, (void *)i);
if (ret != 0)
{
printf("写线程创建失败!\n");
return -1;
}
}
// 循环创建10个读线程
for (i =0; i < 10; i++)
{
ret = pthread_create(&tid, NULL, my_thread_read_func, (void *)i);
if (ret != 0)
{
printf("读线程创建失败!\n");
return -1;
}
}
// 主线程无限休眠,保持程序运行
while (1) sleep(100);
// 销毁读写锁,释放系统资源
pthread_rwlock_destroy(&rwlock);
return 0;
}
编译与运行
# 编译
gcc -o pthread_rwlock pthread_rwlock.c -lpthread
# 运行
./pthread_rwlock
第七章 所有线程同步方案横向对比
| 同步方案 | 核心特点 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 全局变量 + 忙等 | 死循环检查标志位 | 逻辑极简,无需额外 API | CPU 占用 100%,极度浪费资源 | 仅教学演示,绝对禁止用于正式项目 |
| 信号量 | 计数器 + 等待队列,实现线程通知 | 解决忙等问题,支持跨进程同步 | 无法精细保护共享数据,易出现逻辑漏洞 | 简单的线程 / 进程通知场景 |
| 互斥锁 + 条件变量 | 锁保护数据,条件变量实现等待唤醒 | 安全、灵活、高效,Linux 工业级标准方案 | 逻辑相对复杂 | 绝大多数多线程同步场景,首选方案 |
| 自旋锁 | 抢不到锁原地自旋,不释放 CPU | 短等待场景下效率极高,无线程切换开销 | 长等待场景下极度浪费 CPU | 锁内操作极快、等待时间极短的内核 / 驱动场景 |
| 读写锁 | 读共享、写独占 | 读多写少场景下效率远超互斥锁 | 写锁优先级低时可能出现写饥饿 | 读多写少的业务场景(如配置读取、数据查询) |
第八章 新手常见问题排错指南
1. 编译报错:undefined reference to pthread_create
- 原因:编译命令没有加
-lpthread - 解决:在编译命令的结尾加上
-lpthread
2. 程序运行后,子线程不执行
- 原因 1:主线程提前退出,整个进程被系统终止,子线程被强制杀死
- 解决:让主线程保持运行,比如添加
while(1) sleep(1);循环 - 原因 2:子线程创建后,主线程立刻退出,子线程还没来得及执行
- 解决:主线程调用
pthread_join等待子线程,或添加短暂休眠
3. 打印数据乱码、内容错乱
- 原因:共享数据没有加互斥锁保护,多线程同时读写导致数据竞争
- 解决:所有对共享数据的读写操作,都用互斥锁包裹,保证同一时间只有一个线程操作
4. 程序运行后卡死、死锁
- 常见原因 1:加锁后忘记解锁,导致其他线程永远抢不到锁
- 解决:保证
加锁↔解锁严格成对出现,即使函数中途返回也要先解锁 - 常见原因 2:两个线程互相等待对方的锁(嵌套加锁顺序不一致)
- 解决:所有线程都按照相同的顺序加锁,避免嵌套加锁
5. 条件变量唤醒后,数据不符合预期
- 原因:条件变量存在「虚假唤醒」,被唤醒后数据可能已经被其他线程修改
- 解决:
pthread_cond_wait必须放在while(条件不满足)循环中,唤醒后再次检查条件
附录 核心函数速查表
| 分类 | 函数名 | 核心作用 |
|---|---|---|
| 线程基础 | pthread_create | 创建线程 |
| 线程基础 | pthread_exit | 线程主动退出 |
| 线程基础 | pthread_join | 等待线程退出,回收资源 |
| 信号量 | sem_init | 初始化信号量 |
| 信号量 | sem_wait | 等待信号量,线程休眠 |
| 信号量 | sem_post | 发送信号量,唤醒线程 |
| 互斥锁 | pthread_mutex_lock | 加互斥锁 |
| 互斥锁 | pthread_mutex_unlock | 释放互斥锁 |
| 条件变量 | pthread_cond_wait | 等待条件变量,线程休眠 |
| 条件变量 | pthread_cond_signal | 唤醒一个休眠的线程 |
| 自旋锁 | pthread_spin_lock | 加自旋锁 |
| 自旋锁 | pthread_spin_unlock | 释放自旋锁 |
| 读写锁 | pthread_rwlock_rdlock | 加读锁 |
| 读写锁 | pthread_rwlock_wrlock | 加写锁 |
| 读写锁 | pthread_rwlock_unlock | 释放读写锁 |
&spm=1001.2101.3001.5002&articleId=159613652&d=1&t=3&u=30cf112e72814418af46d4dd6a876bd9)
1481

被折叠的 条评论
为什么被折叠?



