当前位置: 首页 > news >正文

Linux系统编程收尾(35)

文章目录

  • 前言
  • 一、读写锁
  • 二、自旋锁
  • 总结


前言

  大家好,这是我们Linux系统编程的最后一节课了!
  大家请再撑住一会儿~


一、读写锁

  提到读写锁,我们就不得不提到 读者写者模型 ,跟 生产者消费者模型 不同的是,本模型的核心思想是 读者共享,写者互斥

  这就好比博客发布了,允许很多人同时读,但如果作者想要进行修改,那么其他人自然也就无法查看了,这就是一个很典型的 读者写者 问题

读者写者模型 也遵循 321 原则

3 种关系:

  读者 <-> 读者 无关系
  写者 <-> 写者 互斥
  读者 <-> 写者 互斥、同步

2 种角色:读者、写者

1 个交易场所:阻塞队列或其他缓冲区

为什么读者与读者间甚至不存在互斥关系?
因为读者读取数据时,并不会对数据做出修改,因此不需要维持互斥关系

  pthread库里面提供了有关读写锁的一些接口

#include <pthread.h>pthread_rwlock_t; // 读写锁类型// 初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *__restrict__ __rwlock, const pthread_rwlockattr_t *__restrict__ __attr); // 销毁读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *__rwlock) // 读者,加锁
int pthread_rwlock_rdlock(pthread_rwlock_t *__rwlock); // 阻塞式
int pthread_rwlock_tryrdlock(pthread_rwlock_t *__rwlock); // 非阻塞式// 写者,加锁
int pthread_rwlock_wrlock(pthread_rwlock_t *__rwlock); // 阻塞式 
int pthread_rwlock_trywrlock(pthread_rwlock_t *__rwlock); // 非阻塞式// 解锁(读者锁、写者锁都可以解)
int pthread_rwlock_unlock(pthread_rwlock_t *__rwlock); 

  注意: 读者和写者使用的加锁接口并不是同一个

关于 读者写者模型 的实现:

  • 读者读数据时,允许其他读者一起读取数据,但不允许写者修改数据
  • 写者写数据时,不允许读者进入
  • 读者读取完数据后,通知写者进行写入
  • 写者写完数据后,通知读者进行读取

  因为现实中,读者数量大多数情况下都是多于写者的,所以势必会存在很多很多读者不断读取,导致写者根本申请不到信号量,写者陷入 死锁 状态

  这是读者写者模型的特性,也是 读者优先 策略的体现,如果想要避免死锁,可以选择 写者优先 策略,优先让写者先写,读者先等一等

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include <cstdlib>
#include <ctime>// 共享资源
int shared_data = 0;// 读写锁
pthread_rwlock_t rwlock;// 读者线程函数
void* Reader(void* arg)
{//sleep(1); //读者优先,一旦读者进入 && 读者很多,写者基本就很难进入了int number = *(int *)arg;while (true){pthread_rwlock_rdlock(&rwlock); // 读者加锁std::cout << "读者-" << number << " 正在读取数据, 数据是: " << shared_data << std::endl;sleep(1);                       // 模拟读取操作pthread_rwlock_unlock(&rwlock); // 解锁}delete (int*)arg;return NULL;
}// 写者线程函数
void* Writer(void* arg)
{int number = *(int *)arg;while (true){pthread_rwlock_wrlock(&rwlock); // 写者加锁shared_data = rand() % 100;     // 修改共享数据std::cout << "写者- " << number << " 正在写入. 新的数据是: " << shared_data << std::endl;sleep(2);                       // 模拟写入操作pthread_rwlock_unlock(&rwlock); // 解锁}delete (int*)arg;return NULL;
}int main()
{srand(time(nullptr) ^ getpid());pthread_rwlock_init(&rwlock, NULL); // 初始化读写锁// 可以更高读写数量配比,观察现象const int reader_num = 2;const int writer_num = 2;const int total = reader_num + writer_num;pthread_t threads[total]; // 假设读者和写者数量相等// 创建读者线程for (int i = 0; i < reader_num; ++i){int *id = new int(i);pthread_create(&threads[i], NULL, Reader, id);}// 创建写者线程for (int i = reader_num; i < total; ++i){int *id = new int(i - reader_num);pthread_create(&threads[i], NULL, Writer, id);}// 等待所有线程完成for (int i = 0; i < total; ++i){pthread_join(threads[i], NULL);}pthread_rwlock_destroy(&rwlock); // 销毁读写锁return 0;
}

  现在我将以 生活化 的例子来帮大家理解这段代码

  首先,先用图书馆来比喻什么是读者,什么是写者
在这里插入图片描述

共享数据与锁初始化

在这里插入图片描述

读者线程

在这里插入图片描述

写者线程

在这里插入图片描述

主线程

在这里插入图片描述

锁的三种状态

在这里插入图片描述

二、自旋锁

  接口大致浏览~

  自旋锁:申请锁失败时,线程不会被挂起,而且不断尝试申请锁

  自旋 本质上就是一个不断 轮询 的过程,即不断尝试申请锁,这种操作是十分消耗 CPU 时间的,因此推荐临界区中的操作时间较短时,使用 自旋锁 以提高效率;操作时间较长时,自旋锁 会严重占用 CPU 时间

  自旋锁 的优点:可以减少线程切换的消耗

#include <pthread.h>pthread_spinlock_t lock; // 自旋锁类型int pthread_spin_init(pthread_spinlock_t *lock, int pshared); // 初始化自旋锁int pthread_spin_destroy(pthread_spinlock_t *lock); // 销毁自旋锁// 自旋锁加锁
int pthread_spin_lock(pthread_spinlock_t *lock); // 失败就不断重试(阻塞式)
int pthread_spin_trylock(pthread_spinlock_t *lock); // 失败就继续向后运行(非阻塞式)// 自旋锁解锁
int pthread_spin_unlock(pthread_spinlock_t *lock);

  现在我们再次借用 DS 来用比喻助于你的理解

在这里插入图片描述

// 操作共享变量会有问题的售票系统代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>int ticket = 1000;
pthread_spinlock_t lock;void* route(void* arg)
{char* id = (char*)arg;while (1){pthread_spin_lock(&lock);if (ticket > 0){usleep(1000);printf("%s sells ticket:%d\n", id, ticket);ticket--;pthread_spin_unlock(&lock);}else{pthread_spin_unlock(&lock);break;}}return nullptr;
}int main(void)
{pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE);pthread_t t1, t2, t3, t4;pthread_create(&t1, NULL, route, (void *)"thread 1");pthread_create(&t2, NULL, route, (void *)"thread 2");pthread_create(&t3, NULL, route, (void *)"thread 3");pthread_create(&t4, NULL, route, (void *)"thread 4");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);pthread_spin_destroy(&lock);return 0;
}

在这里插入图片描述
  上面是DS大人给出的比喻,下面是其给出的对比表

在这里插入图片描述


总结

  结束喽,Linux系统编程环节,现在是即将进入Linux网络编程!!!

http://www.xdnf.cn/news/728209.html

相关文章:

  • 【C/C++】cmake实现Release版本禁用调试接口技巧
  • [定昌linux开发板]启用用户唯一性限制
  • Android全局网络监控最佳实践(Kotlin实现)
  • 从Java的Jvm的角度解释一下为什么String不可变?
  • Spring Boot3.4.1 集成redis
  • 自动过滤:用 AutoFilterer 实现高性能动态查询
  • 怎么从一台电脑拷贝已安装的所有python第三方库到另一台
  • 分库分表的常见策略
  • Arduino学习-跑马灯
  • day 26 函数专题
  • 基于云模型与TOPSIS评价算法的综合应用研究
  • 深度刨析树结构(从入门到入土讲解AVL树及红黑树的奥秘)
  • 深入理解Transformer架构:从原理到实践
  • python中 @注解 及内置注解 的使用方法总结以及完整示例
  • Jenkins 2.479.1安装和邮箱配置教程
  • SkyWalking如何实现跨线程Trace传递
  • 权威认证与质量保障:第三方检测在科技成果鉴定测试中的核心作用
  • 【C语言编译与链接】--翻译环境和运行环境,预处理,编译,汇编,链接
  • 怎么用外网打开内网的网址?如在异地在家连接访问公司局域网办公网站
  • DeepSeek 赋能数字人直播带货:技术革新重塑电商营销新生态
  • 处理知识库文件_编写powershell脚本文件_批量转换其他格式文件到pdf文件---人工智能工作笔记0249
  • Android 代码阅读环境搭建:VSCODE + SSH + CLANGD(详细版)
  • 生成式AI如何重塑设计思维与品牌创新?从工具到认知革命的跃迁
  • TCP通信与MQTT协议的关系
  • 使用ssh-audit扫描ssh过期加密算法配置
  • Qt实现csv文件按行读取的方式
  • ​什么是RFID电子标签​
  • 1. pytorch手写数字预测
  • 新能源集群划分+电压调节!基于分布式能源集群划分的电压调节策略!
  • 24位高精度数据采集卡NET8860音频振动信号采集监测满足自动化测试应用现场的多样化需求