Linux线程同步(6)——更高并行性的读写锁

news2024/11/29 10:54:45

        互斥锁或自旋锁要么是加锁状态、要么是不加锁状态,而且一次只有一个线程可以对其加锁。读写锁有 3 种状态:读模式下的加锁状态(以下简称读加锁状态)、写模式下的加锁状态(以下简称写加锁状态)和不加锁状态(见),一次只有一个线程可以占有写模式的读写锁,但是可以有多个线程同时占有读模式的读写锁。因此可知,读写锁比互斥锁具有更高的并行性!

        读写锁有如下两个规则:

  • 当读写锁处于写加锁状态时,在这个锁被解锁之前,所有试图对这个锁进行加锁操作(不管是以读模式加锁还是以写模式加锁)的线程都会被阻塞。
  • 当读写锁处于读加锁状态时,所有试图以读模式对它进行加锁的线程都可以加锁成功;但是任何以写模式对它进行加锁的线程都会被阻塞,直到所有持有读模式锁的线程释放锁为止。

        虽然各操作系统对读写锁的实现各不相同,但当读写锁处于读模式加锁状态,而这时有一个线程试图以写模式获取锁时,该线程会被阻塞;而如果另一线程以读模式获取锁,则会成功获取到锁,对共享资源进行读操作。

        所以,读写锁非常适合于对共享数据读的次数远大于写的次数的情况。当读写锁处于写模式加锁状态时,它所保护的数据可以被安全的修改,因为一次只有一个线程可以在写模式下拥有这个锁;当读写锁处于读模式加锁状态时,它所保护的数据就可以被多个获取读模式锁的线程读取。所以在应用程序当中,使用读写锁实现线程同步,当线程需要对共享数据进行读操作时,需要先获取读模式锁(对读模式锁进行加锁), 当读取操作完成之后再释放读模式锁(对读模式锁进行解锁);当线程需要对共享数据进行写操作时,需要先获取到写模式锁,当写操作完成之后再释放写模式锁。

        读写锁也叫做共享互斥锁。当读写锁是读模式锁住时,就可以说成是共享模式锁住。当它是写模式锁住时,就可以说成是互斥模式锁住。

读写锁初始化

        与互斥锁、自旋锁类似,在使用读写锁之前也必须对读写锁进行初始化操作,读写锁使用 pthread_rwlock_t 数据类型表示,读写锁的初始化可以使用宏 PTHREAD_RWLOCK_INITIALIZER 或者函数 pthread_rwlock_init(),其初始化方式与互斥锁相同,譬如使用宏 PTHREAD_RWLOCK _INITIALIZER 进行初始化必须在定义读写锁时就对其进行初始化:

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

        对于其它方式可以使用 pthread_rwlock_init()函数对其进行初始化,当读写锁不再使用时,需要调用 pthread_rwlock_destroy()函数将其销毁,其函数原型如下所示:

#include <pthread.h>

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);

        使用这两个函数同样需要包含头文件,调用成功返回 0,失败将返回一个非 0 值的错误码。

        参数 rwlock 指向需要进行初始化或销毁的读写锁对象。对于 pthread_rwlock_init()函数,参数 attr 是一 个 pthread_rwlockattr_t *类型指针,指向 pthread_rwlockattr_t 对象。pthread_ rwlockattr_t 数据类型定义了读写锁的属性,若将参数 attr 设置为 NULL,则表示将读写锁的属性设置为默认值,在这种情况下其实就等价于 PTHREAD_RWLOCK_INITIALIZER 这种方式初始化,而不同之处在于,使用宏不进行错误检查。

        当读写锁不再使用时,需要调用 pthread_rwlock_destroy()函数将其销毁。

        读写锁初始化使用示例:

pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock, NULL);

......

pthread_rwlock_destroy(&rwlock);

读写锁上锁和解锁

        以读模式对读写锁进行上锁,需要调用 pthread_rwlock_rdlock()函数;以写模式对读写锁进行上锁,需 要调用 pthread_rwlock_wrlock()函数。不管是以何种方式锁住读写锁,均可以调用 pthread_rwlock_unlock()函 数解锁,其函数原型如下所示:

#include <pthread.h>

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

        参数 rwlock 指向读写锁对象。调用成功返回 0,失败返回一 个非 0 值的错误码。 当读写锁处于写模式加锁状态时,其它线程调用 pthread_rwlock_rdlock()或 pthread_rwlock_wrlock()函数均会获取锁失败,从而陷入阻塞等待状态;

        当读写锁处于读模式加锁状态时,其它线程调用 pthread_rwlock_rdlock()函数可以成功获取到锁,如果调用 pthread_rwlock_wrlock()函数则不能获取到锁,从而陷入阻塞等待状态。 如果线程不希望被阻塞,可以调用 pthread_rwlock_tryrdlock()和 pthread_rwlock_trywrlock()来尝试加锁, 如果不可以获取锁时,两者均会立马返回错误,错误码为 EBUSY。其函数原型如下所示:

#include <pthread.h>

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

        参数 rwlock 指向需要加锁的读写锁,加锁成功返回 0,加锁失败则返回 EBUSY。

        使用示例

        示例代码演示了使用读写锁来实现线程同步,全局变量 g_count 作为线程间的共享变量,主线程中创建了 5 个读取 g_count 变量的线程,它们使用同一个函数 read_thread,这 5 个线程仅仅对 g_count 变量进行读取,并将其打印出来,连带打印线程的编号(1~5);主线程中还创建了 5 个写 g_count 变量的线程,它们使用同一个函数 write_thread,write_thread 函数中会将 g_count 变量的值进行累加,循环 10 次,每次将 g_count 变量的值在原来的基础上增加 20,并将其打印出来,连带打印线程的编号(1~5)。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>

static pthread_rwlock_t rwlock;
static int g_count = 0;

static void *read_thread(void *arg){
    int number = *((int *)arg);
    int j;
    for (j = 0; j < 10; j++) {
        pthread_rwlock_rdlock(&rwlock); //以读模式获取锁
        printf("读线程<%d>, g_count=%d\n", number+1, g_count);
        pthread_rwlock_unlock(&rwlock);//解锁
        sleep(1);
    }
    return (void *)0;
}

static void *write_thread(void *arg){
    int number = *((int *)arg);
    int j;
    for (j = 0; j < 10; j++) {
        pthread_rwlock_wrlock(&rwlock); //以写模式获取锁
        printf("写线程<%d>, g_count=%d\n", number+1, g_count+=20);
        pthread_rwlock_unlock(&rwlock);//解锁
        sleep(1);
    }
    return (void *)0;
}

static int nums[5] = {0, 1, 2, 3, 4};

int main(int argc, char *argv[]){
    pthread_t tid[10];
    int j;
    /* 对读写锁进行初始化 */
    pthread_rwlock_init(&rwlock, NULL);
   
    /* 创建 5 个读 g_count 变量的线程 */
    for (j = 0; j < 5; j++)
        pthread_create(&tid[j], NULL, read_thread, &nums[j]);
    
    /* 创建 5 个写 g_count 变量的线程 */
    for (j = 0; j < 5; j++)
        pthread_create(&tid[j+5], NULL, write_thread, &nums[j]);
    
    /* 等待线程结束 */
    for (j = 0; j < 10; j++)
        pthread_join(tid[j], NULL);//回收线程
    
    /* 销毁自旋锁 */
    pthread_rwlock_destroy(&rwlock);
    exit(0);
}

         编译测试,其打印结果如下:

         在这个例子中,我们演示了读写锁的使用,但仅作为演示使用,在实际的应用编程中,需要根据应用场景来选择是否使用读写锁。

读写锁的属性

        读写锁与互斥锁类似,也是有属性的,读写锁的属性使用 pthread_rwlockattr_t 数据类型来表示,当定义 pthread_rwlockattr_t 对象时,需要使用 pthread_rwlockattr_init()函数对其进行初始化操作,初始化会将 pthread_rwlockattr_t 对象定义的各个读写锁属性初始化为默认值;当不再使用 pthread_rwlockattr_t 对象时, 需要调用 pthread_rwlockattr_destroy()函数将其销毁,其函数原型如下所示:

#include <pthread.h>

int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);

        参数 attr 指向需要进行初始化或销毁的 pthread_rwlockattr_t 对象;函数调用成功返回 0,失败将返回一 个非 0 值的错误码。

        读写锁只有一个进程共享属性,它与互斥锁以及自旋锁的进程共享属性相同。Linux 下提供了相应的函数用于设置或获取读写锁的共享属性 。 函数pthread_rwlockattr_getpshared() 用于从 pthread_rwlockattr_t 对象中获取共享属性,函数 pthread_rwlockattr_setpshared()用于设置 pthread_rwlockattr_t 对象中的共享属性,其函数原型如下所示:

#include <pthread.h>

int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);

函数 pthread_rwlockattr_getpshared()参数和返回值:

        attr:指向 pthread_rwlockattr_t 对象;

        pshared:调用 pthread_rwlockattr_getpshared()获取共享属性,将其保存在参数 pshared 所指向的内存中;

        返回值:成功返回 0,失败将返回一个非 0 值的错误码。 函数  pthread_rwlockattr_setpshared()参数和返回值:

        attr:指向 pthread_rwlockattr_t 对象;

        pshared:调用 pthread_rwlockattr_setpshared()设置读写锁的共享属性,将其设置为参数 pshared 指定的值。参数 pshared 可取值如下:

  • PTHREAD_PROCESS_SHARED:共享读写锁。该锁可以在多个进程中的线程之间共享;
  • PTHREAD_PROCESS_PRIVATE:私有读写锁。只有本进程内的线程才能够使用该读写锁,这是读写锁共享属性的默认值。

        返回值:调用成功的情况下返回 0;失败将返回一个非 0 值的错误码。 使用方式如下:

pthread_rwlock_t rwlock; //定义读写锁
pthread_rwlockattr_t attr; //定义读写锁属性

/* 初始化读写锁属性对象 */
pthread_rwlockattr_init(&attr);

/* 将进程共享属性设置为 PTHREAD_PROCESS_PRIVATE */
pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE);

/* 初始化读写锁 */
pthread_rwlock_init(&rwlock, &attr);

......

/* 使用完之后 */
pthread_rwlock_destroy(&rwlock); //销毁读写锁
pthread_rwlockattr_destroy(&attr); //销毁读写锁属性对象

总结

        “Linux线程同步”系列到此就告一段落,介绍了线程同步的几种不同的方法,包括互斥锁、条件变量、自旋锁以及读写锁,当然,除此之外, 线程同步的方法其实还有很多,譬如信号量、屏障等等。 在实际应用开发当中,用的最多的还是互斥锁和条件变量,当然具体使用哪一种线程同步方法还是得根据场景来进行选择。

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

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

相关文章

django视图(request请求response返回值)

一、视图函数介绍 视图就是应用中views.py中定义的函数&#xff0c;称为视图函数 def index(request):return HttpResponse("hello world&#xff01;") 1、视图的第一个参数必须为HttpRequest对象&#xff0c;还可能包含下参数如通过正则表达式组获取的位置参数、通…

VBA——01篇(入门篇——简单基础语法)

VBA——01篇&#xff08;入门篇——简单基础语法&#xff09; 1. 语法格式1.1 简单语法1.2 简单例子 2. 变量2.1 常用数据类型2.2 声明变量的常用方式2.3 简单例子 3. 单元格赋值3.1 直接赋值3.2 拷贝单元格 4. 简单的逻辑语法4.1 简单if4.2 简单for循环4.2.1 简单语法例子4.2.…

基于混合整数二阶锥(MISOCP)的配电网重构(附matlab代码)

参考资料&#xff1a;主动配电网网络分析与运行调控 (sciencereading.cn) 配电网重构是指在满足配电网运行基本约束的前提下&#xff0c;通过改变配电网中一个或多个开关的状态对配电网中一个或多个指标进行优化。通过配电网重构&#xff0c;可以在不增加设备投资的情况下&…

注解实现:判空赋值

工作中的小玩意~~ 流程&#xff1a; 注解实现反射工具类 注解定义及实现 注解定义&#xff1a; Documented Target(ElementType.FIELD) Retention(RetentionPolicy.RUNTIME) public interface CheckParam {String value() default "-1"; }简单解释上述其相关注解…

哈工大2023春计算机组成原理真题回忆

仅供同学参考&#xff0c;严禁用作商业用途 如发现将追究责任 2023-5-14 属鼠经历了计算机组成原理考试 现将本人真题回忆如下&#xff1a;欢迎大家补充&#xff0c;并期待大家一起参与这个开源的项目。 致谢:真诚感谢草履虫同学提供的图片 15个选择部分回忆如下 &#xff1a…

【历史上的今天】4 月 13 日:Damn Small Linux 首次发布;谷歌关闭短网址服务;数学先驱出生

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 4 月 13 日&#xff0c;在 2006 年的今天&#xff0c;盛大文学榕树下网站被民营企业收购&#xff1b;原创文学网站榕树下被民营传媒集团欢乐传媒收购&#xff…

hnust 湖南科技大学 2023 软件测试技术 期中考试 复习资料

前言 写的比较匆忙&#xff0c;重点也不明确&#xff0c;没什么参考价值致谢&#xff1a;ly&#xff0c;zxq重点来源&#xff1a;信安※&#xff1a;补充内容★&#xff1a;重点✦&#xff1a;个人推测考点考试范围&#xff1a;1-9章获取最新版本 题型 判断&#xff1a;10简…

AMBER分子动力学模拟之TOP准备-- HIV蛋白酶-抑制剂复合物(1)

AMBER分子动力学模拟之TOP准备-- HIV蛋白酶-抑制剂复合物(1) 我们以HIV蛋白酶-抑制剂复合物为例子&#xff0c;跑Amber动力学模拟 下载1phv 从PBD下载文件&#xff1a;https://www.rcsb.org/ PDB文件预处理 我们以 “protein(water) ligandcomplex” 为例来说一下如何处…

系统设计基本原理-耦合与内聚

耦合 耦合是模块之间的相互独立性(互相连接的紧密程度)的度量&#xff0c;耦合取决于各个模块之间接口的复杂程度、调用模块的方式以及通过接口的信息类型等。 耦合类型 无直接耦合&#xff1a;指两个模块之间没有直接的关系&#xff0c;它们分别从属于不同模块的控制与调用&…

k8s基础11——安全控制之RBAC用户授权、RBAC用户组授权、SA程序授权

文章目录 一、K8s安全框架1.1 鉴权1.1.1 HTTPS证书认证1.1.2 HTTP Token认证 1.2 授权1.3 准入控制1.4 集群四大角色 二、RBAC给用户授权&#xff08;TLS&#xff09;2.1 签发客户端证书2.2 生成kubeconfig授权文件2.2.1 手动生成2.2.2 脚本生成2.2.3 切换操作集群 2.3 定义RBA…

移动应用开发实验-内容提供者-ContentResolver的使用

文章目录 前言读取通讯录信息要求环境 具体实现主页面布局(activity_main.xml)关于RecyclerView库的相关问题添加RecyclerView库操作 解决报错Item布局(info.xml)添加访问权限编写实体类&#xff08;ContactInfo.java&#xff09;编写适配器&#xff08;MyAdapter.java&#xf…

20 散列表的查找

散列表的查找 简介&#xff1a;散列表&#xff08;也成哈希表&#xff09;是一种高效的数据结构&#xff0c;他可以在平均复杂度为O(1)的情况下实现查找、插入和删除操作。 哈希表的基本思想是根据关键字的值来计算其应存储的位置。这个计算过程就是通过哈希函数来实现的。 根…

计算机视觉——day 91基于双网络的鲁棒特征高光谱目标检测(偏门且很水啊)

基于双网络的鲁棒特征高光谱目标检测 I. INTRODUCTIONII. 提出的方法A. 总体框架B.训练集构建C. Dual Networks III. EXPERIMENTSIV. 结论 I. INTRODUCTION 用于高光谱目标检测的深度网络训练通常面临样本有限的问题&#xff0c;在极端情况下&#xff0c;可能只有一个目标样本…

黑盒测试方法

1 等价类划分 1.1 定义 等价类划分法是一种典型的&#xff0c;并且是最基础的黑盒测试用例设计方法。采用等价类划分法时&#xff0c;完全不用考虑程序内部结构&#xff0c;设计测试用例的唯一依据是软件需求规格说明书。 所谓等价类&#xff0c;是输入条件的一个子集合&…

kali整体版本更新方法,为啥更新?

玩过kali都知道&#xff0c;如果不更新版本&#xff0c;那么安装某个软件总是有很多依赖版本问题&#xff0c;解决起来的确麻烦&#xff0c;这篇文章彻底解决这些问题。 1&#xff0c;更新源 国外源与国内源的选择 kali默认配置的是国外源&#xff0c;但国外源的下载速度非常慢…

基于容器和Kubernetes的应用无限扩容

基于应用负载动态管理CPU、内存等资源的使用是云原生架构的最佳实践之一&#xff0c;Kubernetes通过资源请求和限制以及HPA/VPA等机制&#xff0c;为云原生应用资源管理提供了很好的支持。原文: Infinite Scaling with Containers and Kubernetes[1] 如果没有足够资源让容器完成…

matlab第八章_Simulink简介

目录 Simulink简介 基本知识 Simulink组成 模块库简介 Simulink系统仿真 Stateflow建模与应用 Stateflow的定义 状态图编辑器 Stateflow流程图 Simulink简介 基本知识 Simulink是实现动态系统建模&#xff0c;仿真和分析的软件包&#xff0c;被广泛应用于线性系统&…

土地报征简介

报征概念&#xff1a; 土地报征是指国家为了人民整体利益出发&#xff0c;根据我国相关法律和法规的要求和流程&#xff0c; 将集体土地性质转化为国有土地性质&#xff0c;并给予被征地的对象给予合理的补偿和安置工作。报征4个价段&#xff1a; 1、组卷阶段 &#xff08;1&…

Linux系统之top命令的基本使用

Linux系统之top命令的基本使用 一、top命令介绍二、检查本地环境1. 检查操作系统版本2. 检查系统内核版本 三、top命令的使用帮助1. top命令的选项2. top命令的交换命令 四、top显示信息解释1. top的第一行解释2. top的第二、三行信息解释3. top的第四、五行信息解释4. top的进…

Java 常用注解的使用

在Java中&#xff0c;注解&#xff08;Annotation&#xff09;是一种元数据&#xff0c;它提供了一种机制&#xff0c;可以将元数据与代码一起存储&#xff0c;并使其可用于编译器、解释器或其他工具的处理。注解可以在Java源代码中添加元数据&#xff0c;以提供更多的信息&…