深入理解pthread多线程编程:从基础到生产者-消费者模型

前言

在多核处理器普及的今天,多线程编程已成为提高程序性能的重要手段。POSIX线程(pthread)是Unix/Linux系统下广泛使用的多线程API。本文将系统介绍pthread的关键概念,包括线程初始化、死锁预防、递归锁使用,并通过一个完整的生产者-消费者模型示例展示多线程同步的实际应用。

一、pthread基础与静态初始化

1.1 pthread的两种初始化方式

pthread提供了两种初始化互斥锁的方式:

动态初始化:

 

pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);

静态初始化(推荐):

 

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

静态初始化的优势在于:

  • 代码更简洁

  • 线程安全

  • 编译期即完成初始化

1.2 静态初始化的内部实现

PTHREAD_MUTEX_INITIALIZER实际上是一个宏定义,展开后会对互斥锁的所有字段进行初始化。这种方式避免了运行时调用初始化函数的开销。

二、死锁分析与预防

2.1 死锁产生的四个必要条件

  1. 互斥条件:资源一次只能被一个线程占用

  2. 占有并等待:线程持有资源并等待其他资源

  3. 不可抢占:资源只能由持有者释放

  4. 循环等待:多个线程形成等待环路

2.2 典型死锁示例

 

// 线程A
pthread_mutex_lock(&mutex1);
pthread_mutex_lock(&mutex2);
// ...
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);// 线程B
pthread_mutex_lock(&mutex2);
pthread_mutex_lock(&mutex1);
// ...
pthread_mutex_unlock(&mutex1);
pthread_mutex_unlock(&mutex2);

2.3 死锁预防策略

  1. 固定加锁顺序:所有线程按相同顺序获取锁

  2. 使用trylockpthread_mutex_trylock避免阻塞

  3. 超时机制pthread_mutex_timedlock

  4. 锁层次结构:为锁定义严格的获取层次

三、递归互斥锁

3.1 为什么需要递归锁?

当同一线程需要多次获取同一个锁时,普通互斥锁会导致死锁。递归互斥锁允许同一线程多次加锁。

3.2 递归锁使用示例

 

pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);void recursive_function() {pthread_mutex_lock(&mutex);// 可以安全地再次调用需要同一锁的函数pthread_mutex_unlock(&mutex);
}

四、信号量与生产者-消费者模型

4.1 信号量基础

信号量是一种更灵活的同步机制,核心操作:

  • sem_wait():P操作,信号量减1

  • sem_post():V操作,信号量加1

4.2 生产者-消费者模型实现

以下是经过完善的实现代码:

 

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>#define BUFFER_SIZE 10
#define THREAD_NUM 4sem_t semEmpty, semFull;
pthread_mutex_t mutexBuffer;
int buffer[BUFFER_SIZE];
int count = 0;
int should_stop = 0;void* producer(void* args) {while (!should_stop) {int x = rand() % 100;sem_wait(&semEmpty);pthread_mutex_lock(&mutexBuffer);buffer[count] = x;count++;printf("Produced %d\n", x);pthread_mutex_unlock(&mutexBuffer);sem_post(&semFull);sleep(1);}return NULL;
}void* consumer(void* args) {while (!should_stop) {int y;sem_wait(&semFull);pthread_mutex_lock(&mutexBuffer);if (count > 0) {y = buffer[count - 1];count--;printf("Consumed %d\n", y);}pthread_mutex_unlock(&mutexBuffer);sem_post(&semEmpty);sleep(1);}return NULL;
}int main() {srand(time(NULL));pthread_t th[THREAD_NUM];// 初始化同步对象sem_init(&semEmpty, 0, BUFFER_SIZE);sem_init(&semFull, 0, 0);pthread_mutex_init(&mutexBuffer, NULL);// 创建线程for (int i = 0; i < THREAD_NUM; i++) {if (i % 2 == 0) {pthread_create(&th[i], NULL, producer, NULL);} else {pthread_create(&th[i], NULL, consumer, NULL);}}// 运行20秒后停止sleep(20);should_stop = 1;// 等待线程结束for (int i = 0; i < THREAD_NUM; i++) {pthread_join(th[i], NULL);}// 清理资源sem_destroy(&semEmpty);sem_destroy(&semFull);pthread_mutex_destroy(&mutexBuffer);return 0;
}

4.3 关键点解析

  1. 双信号量设计

    • semEmpty:缓冲区空位数量,初始为缓冲区大小

    • semFull:缓冲区数据数量,初始为0

  2. 互斥锁保护

    • 确保对缓冲区的操作是原子的

  3. 终止机制

    • 使用should_stop标志优雅停止线程

  4. 边界检查

    • 消费者检查count > 0避免缓冲区下溢

五、常见问题与调试技巧

  1. 线程不退出

    • 确保所有线程都有退出条件

    • 检查是否有线程阻塞在同步原语上

  2. 数据竞争

    • 使用工具如Valgrind的Helgrind检测

    • 确保所有共享数据都有适当的保护

  3. 性能优化

    • 减少临界区范围

    • 考虑读写锁替代互斥锁

结语

多线程编程既强大又复杂。通过合理使用pthread提供的同步原语,可以构建高效可靠的并发程序。生产者-消费者模型是多线程编程的经典范式,理解其实现原理对掌握并发编程至关重要。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/899795.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springboot 对接马来西亚数据源API等多个国家的数据源

使用Spring Boot对接StockTV全球金融数据API指南 StockTV提供了覆盖股票、外汇、期货和加密货币的全球化金融数据接口。本文将通过Spring Boot实现对这些API的快速对接&#xff0c;并提供完整的代码示例。 一、前期准备 1. 获取API Key 访问StockTV官网联系客服获取API Key…

软件测试常用设计模式

设计模式的重要原则就是&#xff1a;高内聚、低耦合&#xff1b;通常程序结构中各模块的内聚程度越高&#xff0c;模块间的耦合程度就越低。 数据驱动测试&#xff1a;Data Driven Testing&#xff0c;简称DDT&#xff1b; 数据驱动指的是从数据文件&#xff08;如数据库、Ex…

基于 Fluent-Bit 和 Fluentd 的分布式日志采集与处理方案

#作者&#xff1a;任少近 文章目录 需求描述系统目标系统组件Fluent BitFluentdKafka 数据流与处理流程日志采集日志转发到 Fluentd日志处理与转发到 KafkaKafka 作为消息队列 具体配置Fluent-Bit的CM配置Fluent-Bit的DS配置Fluentd的CM配置Fluentd的DS配置Kafka查询结果 需求…

正则表达式(Regular Expression,简称 Regex)

一、5w2h&#xff08;七问法&#xff09;分析正则表达式 是的&#xff0c;5W2H 完全可以应用于研究 正则表达式&#xff08;Regular Expressions&#xff09;。通过回答 5W2H 的七个问题&#xff0c;我们可以全面理解正则表达式的定义、用途、使用方法、适用场景等&#xff0c…

爬虫获取1688关键字搜索接口的实战指南

在当今电商行业竞争激烈的环境下&#xff0c;数据的重要性不言而喻。1688作为国内领先的B2B电商平台&#xff0c;拥有海量的商品信息&#xff0c;这些数据对于商家的市场分析、选品决策、价格策略制定等都有着重要的价值。本文将详细介绍如何通过爬虫技术获取1688关键字搜索接口…

如何快速解决django存储session变量时出现的django.db.utils.DatabaseError错误

我们在学习django进行web编程的时候&#xff0c;有时需要将一些全局变量信息存储在session中&#xff0c;但使用过程中&#xff0c;却发现会引起数据库的报错。通过查看django源码信息&#xff0c;发现其对session信息进行了ORM映射&#xff0c;如果数据库中不存在对应的表信息…

C语言复习--assert断言

assert.h 头⽂件定义了宏 assert() &#xff0c;⽤于在运⾏时确保程序符合指定条件&#xff0c;如果不符合&#xff0c;就报错终止运行。这个宏常常被称为“断⾔”。 assert(p ! NULL); 代码在程序运⾏到这⼀⾏语句时&#xff0c;验证变量 p 是否等于 NULL 。如果确实不等于 NU…

STL新增内容

文章目录 C11 中的 STL 新增内容容器算法 C14 中的 STL 新增内容容器算法 C17 中的 STL 新增内容容器算法 C20 中的 STL 新增内容容器算法 C11 中的 STL 新增内容 容器 std::array&#xff1a;这是一个固定大小的数组容器&#xff0c;和原生数组类似&#xff0c;但具备更好的…

C#测试Excel开源组件ExcelDataReader

使用微软的com组件Microsoft.office.Interop.Excel读写Excel文件虽然可用&#xff0c;但是列多、行多的时候速度很慢&#xff0c;之前测试过Sylvan.Data.Excel包的用法&#xff0c;如果只是读取Excel文件内容的话&#xff0c;还可以使用ExcelDataReader包&#xff0c;后者是C#开…

位置编码汇总 # 持续更新

看了那么多还没有讲特别好的&#xff0c;GPT老师讲的不错关于三角函数编码。 一、 手撕transformer常用三角位置编码 GPT说&#xff1a;“低维度的编码&#xff08;例如&#xff0c;第一个维度&#xff09;可以捕捉到大的位置差异&#xff0c;而高维度的编码则可以捕捉到小的细…

Java 模块系统深度解析

Java 模块系统深度解析 Java 模块系统&#xff08;Java Platform Module System, JPMS&#xff09;是 Java 9 引入的一项重要特性&#xff0c;它从根本上改变了 Java 应用程序的打包和依赖管理方式。本文将全面介绍 Java 模块系统的核心概念、优势及实际应用。 一、为什么需要…

蓝桥杯杯赛-日期模拟

知识点 处理日期 1. 按天枚举日期&#xff1a;逐天遍历起始日期到结束日期范围内的每个日期。 2. 处理闰年&#xff1a;正确判断闰年条件。闰年定义为&#xff1a;年份 满足以下任意一个条件&#xff1a;(闰年的2月只有29天) 满足下面一个条件就是闰年 1> 是 400 的倍数…

.Net中对称加密的实现

常见对称加密算法及优缺点 1. DES&#xff08;Data Encryption Standard&#xff09; 优点&#xff1a;是最早被广泛应用的加密算法&#xff0c;算法公开&#xff0c;实现简单&#xff0c;效率较高。缺点&#xff1a;密钥长度较短&#xff08;56 位&#xff09;&#xff0c;在…

SQLMesh调度系统深度解析:内置调度与Airflow集成实践

本文系统解析SQLMesh的两种核心调度方案&#xff1a;内置调度器与Apache Airflow集成。通过对比两者的适用场景、架构设计和操作流程&#xff0c;为企业构建可靠的数据分析流水线提供技术参考。重点内容包括&#xff1a; 内置调度器的轻量级部署与性能优化策略Airflow集成的端到…

centos线程数查看

查看当前最大支持的线程数 cat /proc/sys/kernel/threads-max当前用户进程可以创建的最大线程数&#xff08;包括子进程&#xff09; [rootlocalhost tmp]# ulimit -u得到当前实际的线程数 [rootlocalhost tmp]# ps -eLf | wc -l统计每个进程的总线程数前20的数据 [rootloc…

【大模型】视觉语言模型:Qwen2.5-VL的使用

官方github地址&#xff1a;https://github.com/QwenLM/Qwen2.5-VL 目录 Qwen家族的最新成员&#xff1a;Qwen2.5-VL 主要增强功能 模型架构更新 快速开始 使用Transformers聊天 Docker Qwen家族的最新成员&#xff1a;Qwen2.5-VL 主要增强功能 强大的文档解析功能&am…

HDMI接口设计

1. HDMI简介 HDMI(High Definition Multimedia Interface)高清多媒体接口,是首个支持在单线缆上传输,不经过压缩的全数字高清晰度、多声道音频和智能格式与控制命令数据的数字接口。这个接口可以同时传输视频信号、音频信号和控制信号。 从上图里面可以看到HDMI有3组数据信号…

C/C++ JSON 库综合对比及应用案例(六)

第六部分&#xff1a;C/C JSON 库综合对比及应用案例 &#x1f4e2; 快速掌握 JSON&#xff01;文章 视频双管齐下 &#x1f680; 如果你觉得阅读文章太慢&#xff0c;或者更喜欢 边看边学 的方式&#xff0c;不妨直接观看我录制的 JSON 课程视频&#xff01;&#x1f3ac; …

LXC 导入(Rockylinux,almalinux,oraclelunx,debian,ubuntu,openEuler,kail,opensuse)

前提要求 ubuntu下安装lxd 参考Rockylinux下安装lxd 参考LXC 源替换参考LXC 容器端口发布参考LXC webui 管理<

Spring MVC 页面跳转方案与区别

SpringMVC 的页面跳转方案主要分为 ‌转发&#xff08;Forward&#xff09;‌ 和 ‌重定向&#xff08;Redirect&#xff09;‌ 两类&#xff0c;具体实现方式和区别如下&#xff1a; 一、页面跳转方案 1. ‌转发&#xff08;Forward&#xff09;‌ 默认方式‌&#xff1a;直…