并发编程--互斥锁与读写锁

并发编程–互斥锁与读写锁

文章目录

  • 并发编程--互斥锁与读写锁
    • 1. 基本概念
    • 2. 互斥锁
      • 2.1 基本逻辑
      • 2.2 函数接口
      • 2.3示例代码1
      • 2.4示例代码2
    • 3. 读写锁
      • 3.1 基本逻辑
      • 3.2示例代码

1. 基本概念

互斥与同步是最基本的逻辑概念:

  • 互斥指的是控制两个进度使之互相排斥,不同时运行。
  • 同步指的是控制两个进度使之有先有后,次序可控。

img

2. 互斥锁

2.1 基本逻辑

使得多线程间互斥运行的最简单办法,就是增加一个互斥锁。任何一条线成要开始运行互斥区间的代码,都必须先获取互斥锁,而互斥锁的本质是一个二值信号量,因此当其中一条线程抢先获取了互斥锁之后,其余线程就无法再次获取了,效果相当于给相关的资源加了把锁,直到使用者主动解锁,其余线程方可有机会获取这把锁。

img

2.2 函数接口

定义
互斥锁是一个特殊的变量,定义如下:

#include <pthread>
pthread_mutex_t m;

一般而言,由于互斥锁需要被多条线程使用,因此一般会将互斥锁定义为全局变量。

初始化与销毁
未经初始化的互斥锁是无法使用的,初始化互斥锁有两种办法:

  • 静态初始化
  • 动态初始化

静态初始化很简单,就是在定义同时赋予其初值:

pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;

由于静态初始化互斥锁不涉及动态内存,因此无需显式释放互斥锁资源,互斥锁将会伴随程序一直存在,直到程序退出为止。而所谓动态初始化指的是使用 pthread_mutex_init() 给互斥锁分配动态内存并赋予初始值,因此这种情形下的互斥锁需要在用完之后显式地进行释放资源,接口如下:

#include <pthread.h>// 初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);// 销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

接口说明:

  • mutex:互斥锁
  • attr:互斥锁属性(一般设置为NULL)

加锁与解锁
互斥锁的基本操作就是加锁与解锁,接口如下:

#include <pthread.h>
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;// 加锁
pthread_mutex_lock( &m );// 解锁
pthread_mutex_unlock( &m );

2.3示例代码1

将此前判断偶数的代码用互斥锁加以改进如下:

// concurrency.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <pthread.h>pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;int global = 100;void *isPrime(void *arg)
{while(1){pthread_mutex_lock(&m);// 一段朴素的代码if(global%2 == 0)printf("%d是偶数\n", global);pthread_mutex_unlock(&m);}
}int main()
{pthread_t tid;pthread_create(&tid, NULL, isPrime, NULL);// 一条人畜无害的赋值语句while(1){pthread_mutex_lock(&m);global = rand() % 5000;pthread_mutex_unlock(&m);}
}

运行结果如下:

gec@ubuntu:~$ ./concurrency
492是偶数
2362是偶数
2778是偶数
3926是偶数
540是偶数
3426是偶数
4172是偶数
112是偶数
368是偶数
2576是偶数
1530是偶数
1530是偶数
2862是偶数
4706是偶数
...
gec@ubuntu:~$ 

可见,有了互斥锁之后,输出的结果正确了。

2.4示例代码2

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>pthread_mutex_t m;void *routine(void *arg)
{char *msg = (char *)arg;#ifdef MUTEXpthread_mutex_lock(&m);#endifwhile(*msg != '\0'){fprintf(stderr,"%c",*msg);usleep(100);msg++;}#ifdef MUTEXpthread_mutex_unlock(&m);#endifpthread_exit(NULL);
}int main(void)
{pthread_mutex_init(&m,NULL);pthread_t t1,t2;pthread_create(&t1,NULL,routine,"AAAAAAAAAAA");pthread_create(&t2,NULL,routine,"BBBBBBBBBBB");pthread_exit(NULL);
}

通过宏定义实现代码的不同运行,输出不同的结果。若不使用互斥锁的话,则直接运行,结果将会是AB交互是输出,两个线程t1,t2会同时运行,交互式输出;若使用互斥锁的话,会输出单独输出一个线程的结果,然后再输出另一个线程的结果。

若要使用互斥锁则如下:

gcc pthread_mutex.c -o pthread_mutex -lpthread -DMUTEX

3. 读写锁

3.1 基本逻辑

对于互斥锁而言,凡是涉及临界资源的访问一律加锁,这在并发读操作的场景下会大量浪费时间。要想提高访问效率,就必须要将对资源的读写操作加以区分:读操作可以多任务并发执行,只有写操作才进行恰当的互斥。这就是读写锁的设计来源。

img
读写锁提高了资源访问的效率

定义
与互斥锁类似,读写锁也是一种特殊的变量:

pthread_rwlock_t rw;

初始化
与互斥锁类似,读写锁也分成静态初始化和动态初始化:

#include <pthread.h>// 静态初始化:
pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER;// 动态初始化与销毁:
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

加锁
读写锁最大的特点是对即将要做的读写操作做了区分:

  • 读操作可以共享,因此多条线程可以对同一个读写锁加多重读锁
  • 写操作天然互斥,因此多条线程只能有一个拥有写锁。(注意写锁与读锁也是互斥的)
#include <pthread.h>// 读锁
// 1,阻塞版本
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
// 2,非阻塞版本
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);// 写锁
// 1,阻塞版本
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
// 2,非阻塞版本
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

操作原则:

  • 如果只对数据进行读操作,那么就加 → 读锁。
  • 如果要对数据进行写操作,那么就加 → 写锁。

解锁

#include <pthread.h>int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

3.2示例代码

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>char global = 'X';pthread_rwlock_t rwlock;void *routine(void *arg)
{#ifdef RDLOCKpthread_rwlock_rdlock(&rwlock);#elif WRLOCKpthread_rwlock_wrlock(&rwlock);#endifint i = 1000;while(i > 0){fprintf(stderr,"[%c:%c]",*(char*)arg,global);i--;}pthread_rwlock_unlock(&rwlock);pthread_exit(NULL);
}int main(void)
{pthread_rwlock_init(&rwlock,NULL);pthread_t t1,t2,t3;pthread_create(&t1,NULL,routine,"1");pthread_create(&t2,NULL,routine,"2");pthread_create(&t3,NULL,routine,"3");pthread_exit(NULL);
}

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

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

相关文章

亲手打造可视化故事线管理工具:开发全流程、难点突破与开发过程经验总结

亲手打造可视化故事线管理工具&#xff1a;开发全流程、难点突破与开发过程经验总结 作为还没入门的业余编程爱好者&#xff0c;奋战了2天&#xff0c;借助AI开发一款FLASK小工具&#xff0c;功能还在完善中&#xff08;时间轴可以跟随关联图缩放&#xff0c;加了一个用C键控制…

网络攻防技术-虚拟机安装和nmap端口扫描

文章是博主上实验课做的实验和心得体会&#xff0c;有些高深的地方我可能也比较一知半解&#xff0c;欢迎来交流。全文参考课程所习得&#xff0c;纯粹梳理知识点和分享&#xff0c;如有不妥请联系修改。 文章侧重实验部分&#xff0c;也会讲述实验相关的理论知识。理论后期如果…

中断的硬件框架

今天呢&#xff0c;我们来讲讲中断的硬件框架&#xff0c;这里会去举3个开发板&#xff0c;去了解中断的硬件框架&#xff1a; 中断路径上的3个部件&#xff1a; 中断源 中断源多种多样&#xff0c;比如GPIO、定时器、UART、DMA等等。 它们都有自己的寄存器&#xff0c;可以…

动手学深度学习:手语视频在VGG模型中的测试

前言 其他所有部分同上一篇AlexNet一样&#xff0c;所以就不再赘诉&#xff0c;直接看VGG搭建部分。 模型 VGG是第一个采取块进行模块化搭建的模型。 def vgg_block(num_convs,in_channels,out_channels):layers[]for _ in range(num_convs):layers.append(nn.Conv2d(in_ch…

信息学奥赛一本通 1498:Roadblocks | 洛谷 P2865 [USACO06NOV] Roadblocks G

【题目链接】 ybt 1498&#xff1a;Roadblocks 洛谷 P2865 [USACO06NOV] Roadblocks G 【题目考点】 1. 图论&#xff1a;严格次短路径 严格次短路的路径长度必须大于最短路的路径长度。 非严格次短路的路径长度大于等于最短路的路径长度。 【解题思路】 每个交叉路口是一…

Arm CPU安全通告:基于TrustZone的Cortex-M系统面临多重故障注入攻击

安全之安全(security)博客目录导读 目录 一、概述 二、致谢 三、参考文献​​​​​​Black Hat USA 2022 | Briefings Schedule 四、版本历史 一、概述 Arm注意到BlackHat 2022大会官网发布的演讲摘要《糟糕..&#xff01;我又一次故障注入成功了&#xff01;——如何突…

【频域分析】包络分析

【频域分析】包络分析 算法配置页面 可以一键导出结果数据 报表自定义绘制 获取和下载【PHM学习软件PHM源码】的方式 获取方式&#xff1a;Docshttps://jcn362s9p4t8.feishu.cn/wiki/A0NXwPxY3ie1cGkOy08cru6vnvc

ElMessage

以下是关于 ElMessage 的详细说明和使用方法&#xff1a; 什么是 ElMessage ElMessage 是 Element Plus 提供的一个全局消息提示组件&#xff0c;用于在页面上显示短暂的消息提示。它可以用于显示成功、警告、错误等不同类型的消息。 基本用法 1. 引入 ElMessage 在使用 E…

全面解析 KaiwuDB 数据库的数据类型

在现代数据库管理系统中&#xff0c;数据类型的选择至关重要。它不仅决定了数据存储的效率&#xff0c;还影响到查询的速度和数据的一致性。KaiwuDB&#xff0c;作为一款开源的分布式数据库&#xff0c;提供了多种数据类型&#xff0c;以适应不同的业务需求和存储要求。本文将全…

【计网】网络交换技术之分组交换(复习自用,重要1)

复习自用的&#xff0c;处理得比较草率&#xff0c;复习的同学或者想看基础的同学可以看看&#xff0c;大佬的话可以不用浪费时间在我的水文上了 另外两种交换技术可以直接点击链接访问相关笔记&#xff1a; 电路交换 报文交换 一、分组交换的定义 1.定义 分组交换&#x…

C++ STL及Python中等效实现

一. STL 概述 STL 包含以下核心组件&#xff1a; 容器&#xff08;Containers&#xff09;&#xff1a;存储数据的结构&#xff0c;如数组、链表、集合等。迭代器&#xff08;Iterators&#xff09;&#xff1a;用于遍历容器的接口&#xff0c;类似指针。算法&#xff08;Alg…

python-63-前后端分离之图书管理系统的Flask后端

文章目录 1 flask后端1.1 数据库实例extension.py1.2 数据模型models.py1.3 .flaskenv1.4 app.py1.5 运行1.6 测试链接2 关键函数和文件2.1 请求视图类MethodView2.2 .flaskenv文件3 参考附录基于flask形成了图书管理系统的后端,同时对其中使用到的关键文件.flaskenv和函数类M…

蓝桥杯真题——好数、R格式

目录 蓝桥杯2024年第十五届省赛真题-好数 【模拟题】 题目描述 输入格式 输出格式 样例输入 样例输出 提示 代码1&#xff1a;有两个案例过不了&#xff0c;超时 蓝桥杯2024年第十五届省赛真题-R 格式 【vector容器的使用】 题目描述 输入格式 输出格式 样例输入…

Python中NumPy的索引和切片

在数据科学和科学计算领域&#xff0c;NumPy是一个功能强大且广泛使用的Python库。它提供了高效的多维数组对象以及丰富的数组操作函数&#xff0c;其中索引和切片是NumPy的核心功能之一。通过灵活运用索引和切片操作&#xff0c;我们可以轻松访问和操作数组中的元素&#xff0…

设计模式:策略模式 - 消除复杂条件判断的利器

一、什么是策略模式&#xff1f; 策略模式&#xff08;Strategy Pattern&#xff09;是一种行为型设计模式&#xff0c;它将一组算法或业务逻辑封装为独立的策略类&#xff0c;使这些策略可以互换使用&#xff0c;并通过上下文类动态选择合适的策略。 核心思想 • 将不同的行…

LeetCode hot 100—不同路径

题目 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&#xff1f; …

pytorch查询字典、列表维度

输出tensor变量维度 print(a.shape)输出字典维度 for key, value in output_dict.items():if isinstance(value, torch.Tensor):print(f"{key} shape:", value.shape)输出列表维度 def get_list_dimensions(lst):# 基线条件&#xff1a;如果lst不是列表&#xff0…

多坐标系变换全解析:从相机到WGS-84的空间坐标系详解

多坐标系变换全解析:从相机到WGS-84的空间坐标系详解 一、常见坐标系简介二、各坐标系的功能和使用场景1. WGS-84 大地坐标系(经纬高)2. 地心直角坐标系(ECEF)3. 本地 ENU / NED 坐标系4. 平台坐标系(Body)5. 相机坐标系三、坐标变换流程图四、如何选用合适的坐标系?五…

【NumPy科学计算:高性能数组操作核心指南】

目录 前言&#xff1a;技术背景与价值当前技术痛点解决方案概述目标读者说明 一、技术原理剖析核心概念图解关键技术模块技术选型对比 二、实战演示环境配置要求核心代码实现运行结果验证 三、性能对比测试方法论量化数据对比结果分析 四、最佳实践推荐方案 ✅常见错误 ❌调试技…

【特权FPGA】之PS/2键盘解码

0 故事背景 见过这种接口的朋友们&#xff0c;大概都已经成家立业了吧。不过今天我们不讨论这种接口的历史&#xff0c;只讲讲这种接口的设计。&#xff08;如果还没有成家的朋友也别生气&#xff0c;做自己想做的事情就对了&#xff01;&#xff09; 1 时序分析 数据帧格式如图…