Linux POSIX 线程学习(基础版)

第一章 前置基础:必懂概念与环境准备

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 核心执行流程

  1. 程序启动,系统自动创建主线程,执行main函数
  2. 主线程调用pthread_create创建子线程,子线程立刻开始执行my_thread_func函数
  3. 两个线程并发独立运行:主线程循环休眠,子线程每秒打印一次日志
  4. 按下Ctrl + C,系统终止整个进程,所有线程自动关闭

2.5 线程基础核心函数详解

1. pthread_create:创建线程

函数原型

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);
参数序号参数名详细说明
1thread传出参数,必须传pthread_t类型变量的地址,用于保存新线程的唯一 ID
2attr线程属性,一般传NULL使用默认属性;可用于设置线程栈大小、分离状态、优先级等
3start_routine线程入口函数,必须遵守void* 函数名(void* arg)的格式,线程创建成功后立刻执行
4arg传给线程入口函数的参数,无参数时传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);

参数序号参数名详细说明
1thread要等待的线程 ID
2retval传出参数,用于接收线程退出的返回值,不关心返回值时传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 执行流程

  1. 程序启动,主线程创建子线程,子线程进入忙等循环,检查g_hasData标志位
  2. 主线程等待键盘输入,你输入文字按下回车后,fgets将内容存入g_buf
  3. 主线程设置g_hasData=1,通知子线程有新数据
  4. 子线程检测到标志位变化,跳出循环,打印数据,然后清空标志位,回到等待状态
  5. 循环往复,直到按下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_initint sem_init(sem_t *sem, int pshared, unsigned int value);初始化信号量,设置共享范围和初始计数值
sem_waitint sem_wait(sem_t *sem);P 操作,等待信号量,无通知时线程休眠
sem_postint sem_post(sem_t *sem);V 操作,发送信号量,唤醒休眠的线程
sem_destroyint sem_destroy(sem_t *sem);销毁信号量,释放系统资源

4.6 本方案的优缺点

  • ✅ 优点:彻底解决忙等问题,无数据时线程休眠,CPU 占用率极低,逻辑简单
  • ❌ 缺点:没有保护共享数据,多线程同时读写g_buf时,极端情况下会出现数据错乱,需要配合互斥锁使用

第五章 Linux 标准方案:互斥锁 + 条件变量

5.1 核心目标

  1. 互斥锁保护共享数据,彻底解决多线程数据竞争问题
  2. 条件变量实现线程的等待 / 唤醒,是 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_initint pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);动态初始化互斥锁,attr 传 NULL 使用默认属性
pthread_mutex_lockint pthread_mutex_lock(pthread_mutex_t *mutex);加锁,锁被占用时线程阻塞休眠
pthread_mutex_unlockint pthread_mutex_unlock(pthread_mutex_t *mutex);解锁,释放锁,唤醒等待的线程
pthread_mutex_destroyint pthread_mutex_destroy(pthread_mutex_t *mutex);销毁互斥锁,释放系统资源
2. 条件变量核心函数

表格

函数名函数原型核心作用
pthread_cond_initint pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);动态初始化条件变量
pthread_cond_waitint pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);等待条件变量,线程休眠,自动处理锁的释放与重持
pthread_cond_signalint pthread_cond_signal(pthread_cond_t *cond);唤醒一个休眠的线程
pthread_cond_broadcastint pthread_cond_broadcast(pthread_cond_t *cond);唤醒所有休眠的线程
pthread_cond_destroyint 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 条铁律:

  1. 读 + 读共享:多个线程可以同时持有读锁,并发读取数据,互不影响
  2. 写 + 写互斥:同一时间只能有一个线程持有写锁
  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

第七章 所有线程同步方案横向对比

同步方案核心特点优点缺点适用场景
全局变量 + 忙等死循环检查标志位逻辑极简,无需额外 APICPU 占用 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释放读写锁
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值