Linux系统编程系列之互斥锁和读写锁

news2025/1/12 15:50:20

一、什么是互斥锁和读写锁

        互斥锁是一种并发机制,用于控制多个线程对共享资源的访问。

        读写锁是一种并发机制,用于控制多个线程对共享资源的访问。

二、特性

        1、互斥锁

        当一个线程获得了互斥锁并进入临界区(对共享资源进行访问)时,其他线程将被阻塞,直到该线程释放互斥锁。这可以确保同时只有一个线程能够访问共享资源,避免多个线程同时修改共享资源导致数据不一致或其他问题。

        2、读写锁

        读写锁允许多个线程同时读取共享资源,但是只允许一个线程进行写操作。在读取共享资源时,多个线程可以同时获得读锁,不会相互阻塞,从而提高了并发性能。而在写操作时,只有一个线程可以获得写锁,其他线程将被阻塞,以避免同时修改导致数据不一致或其他问题。        

        使用读写锁可以有效地提高系统的并发性能和吞吐量,访问效率比互斥锁高。

三、使用场景

        1、互斥锁

                (1)、 线程共享同一个全局变量或者静态变量时,需要使用互斥锁来保证数据的一致性和正确性。
                (2)、 多个线程访问共享资源时,需要使用互斥锁来保证同一时间只有一个线程能够访问共享资源。
                (3)、 线程需要保证一段代码的原子性操作时,需要使用互斥锁来对这段代码进行加锁保护。
                (4)、 多线程并发执行时,需要使用互斥锁来保证线程间执行的顺序和正确性。

                总之,互斥锁主要用于控制多个线程对共享资源的访问,确保同一时刻只有一个线程能够访问共享资源。

        2、读写锁

                (1)、读多写少的情况,读写锁可以提高并发读的性能。读写锁允许多个线程同时读取共享资源,但只允许一个线程进行写操作。
                (2)、对于频繁的读取共享资源和不频繁的写入共享资源的场景,使用读写锁可以避免由于写操作的串行化导致的性能瓶颈。
                (3)、适用于需要有一定实时性的场景,读写锁中的读操作是共享的,可以在不阻塞其他线程执行的情况下快速地读取数据,提高程序的响应速度。

                总之,读写锁适用于读多写少的场景,可以提高并发读的性能,避免由于写操作的串行化导致的性能瓶颈,并且具有一定的实时性。

四、同步与互斥

        互斥可以简单理解为控制两个进度使之互相排斥,不同同时运行。

        同步可以简单理解为控制两个进度使之有先有后,次序可控。

五、相关的函数API接口

        1、互斥锁

                (1)、定义

// 互斥锁是一个特殊的变量
// 声明一个互斥锁变量m
pthread_mutext_t m;

                (2)、初始化和销毁

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


// 静态初始化
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;

// 动态初始化
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
                       const pthread_mutexattr_t *restrict attr);
// 接口说明
        返回值:一直都是0
        参数mutex:互斥锁
        参数attr:互斥锁属性(一般置为NULL)

// 销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

// 接口说明
       返回值:成功返回0,失败返回错误码
       参数mutex:互斥锁


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

                (3)、加锁

// 阻塞上锁
int pthread_mutex_lock(pthread_mutex_t *mutex);

// 接口说明
        返回值:成功返回0,失败返回错误码
        参数mutex:互斥锁


// 非阻塞上锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);

// 接口说明
        返回值:成功返回0,失败返回错误码
        参数mutex:互斥锁

                (4)、解锁

// 解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);

// 接口说明
        返回值:成功返回0,失败返回错误码
        参数mutex:互斥锁

              (5)、锁属性

                当某一个线程所执行的功能有可能发生递归时,需要注意互斥锁的属性问题,需要对有可能被同一个线程重复加锁的锁资源设置为允许递归(重复)上锁。但是需要注意的是,同一个线程对同一锁资源上了多次锁,就需要解锁多少次,否则其他线程将永远无法获得该锁资源(死锁)。

// 初始化线程互斥锁属性
int pthread_mutexattr_init(pthread_mutexattr_t *attr);

// 销毁线程互斥锁属性
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);


// 设置线程互斥锁属性
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int kind);

// 设置锁类型为递归锁
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

          2、读写锁

                (1)、定义

// 读写锁是一种特殊的变量
// 声明一个读写锁变量rw
pthread_rwlock_t rw;

                (2)、初始化和销毁

                跟互斥锁的初始化和销毁差不多

// 静态初始化
pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER;

// 动态初始化
 int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
                        const pthread_rwlockattr_t *restrict attr);

// 接口说明
        返回值:成功返回0,失败返回错误码
        参数rwlock:读写锁
        参数attr:读写锁属性

// 销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

// 接口说明
        返回值:成功返回0,失败返回错误码
        参数rwlock:读写锁

                (3)、加锁

// 阻塞加读锁
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);

                (4)、解锁

// 解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

                (5)、锁属性

// 初始化读写锁属性
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);


// 销毁读写锁属性
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);


// 设置读写锁属性
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr,
                                  int pref);


// 获取读写锁属性
int pthread_rwlockattr_getkind_np(const pthread_rwlockattr_t *attr,
                                  int *pref);

// 接口说明
        参数pref有以下几种:
        (1)、PTHREAD_RWLOCK_PREFER_READER_NP,偏向读取(读锁优先)
        (2)、PTHREAD_RWLOCK_PREFER_WRITER_NP,偏向写入(写锁优先)
        (3)、PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP,偏向写入非递归
        (4)、PTHREAD_RWLOCK_PREFER_DEFAULT_NP,默认


操作步骤:
(1)、初始化锁属性
(2)、设置锁属性
(3)、使用动态初始化的方式初始化锁资源
(4)、销毁锁属性

                偏向写入:是指读写锁的属性设置为优先考虑写入操作,即在有写锁请求时,读锁请求将被阻塞,直到写锁释放。

                偏向读取:是指读锁操作优先处理,如果有读锁请求时,则写锁请求将被阻塞。

                写入非递归:写入操作不支持递归调用,即同一个线程不能在持有写入锁的情况下再次请求写入锁。如果在持有写入锁的情况下再次请求写入锁,则会导致死锁。

                写入递归:写入操作支持递归调用,即同一个线程可以在持有写入锁的情况下再次请求写入锁。如果使用写入递归模式,需要注意避免出现死锁的情况。

六、案例

        用互斥锁来实现两个线程的数据同步,一个负责发送,一个负责接收

// 互斥锁的案例

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

char data[100];

pthread_mutex_t data_mutex; // 定义互斥锁变量
pthread_once_t data_mutex_once_init;    // 函数单例初始化变量
pthread_once_t data_mutex_once_destroy;    // 函数单例销毁变量

// 初始化互斥锁data_mutex
void data_mutex_init(void)
{
    pthread_mutex_init(&data_mutex, NULL);
}

// 销毁互斥锁data_mutex
void data_mutex_destroy(void)
{
    pthread_mutex_destroy(&data_mutex);
}

// 线程1的例程函数,用来接收数据
void *recv_routine(void *arg)
{
    printf("I am recv_routine, my tid = %ld\n", pthread_self());

    // 设置线程分离 
    pthread_detach(pthread_self()); 

    // 函数单例,本程序只会执行data_mutex_init()一次
    pthread_once(&data_mutex_once_init, data_mutex_init);

    sleep(1);   // 先睡眠1s,保证让线程2先执行
    while(1)
    {
        pthread_mutex_lock(&data_mutex);    // 阻塞等待有数据才可以申请成功,用来同步
        printf("pthread1 read data: %s\n", data);
        memset(data, 0, sizeof(data));
        
    }

    // 函数单例,本程序只会执行data_mutex_init()一次
    pthread_once(&data_mutex_once_destroy, data_mutex_destroy);
}

// 线程2的例程函数,用来发送数据
void *send_routine(void *arg)
{
    printf("I am send_routine, my tid = %ld\n", pthread_self());

    // 函数单例,本程序只会执行data_mutex_init()一次
    pthread_once(&data_mutex_once_init, data_mutex_init);

    // 先申请锁,防止线程1读取空数据,为同步做准备
    pthread_mutex_lock(&data_mutex);

    while(1)
    {
        printf("please input data:\n");
        fgets(data, 100, stdin);
        printf("pthread2 send data\n");
        pthread_mutex_unlock(&data_mutex);  // 解锁,相当于给线程1发送信号
    }

    // 函数单例,本程序只会执行data_mutex_init()一次
    pthread_once(&data_mutex_once_destroy, data_mutex_destroy);
}

int main(int argc, char *argv[])
{
    pthread_t tid1, tid2;

    // 创建线程1,用来接收数据
    errno = pthread_create(&tid1, NULL, recv_routine, NULL);
    if(errno == 0)
    {
        printf("pthread create recv_routine success, tid = %ld\n", tid1);
    }
    else
    {
        perror("pthread create recv_routine fail\n");
    }

    // 1、定义线程属性变量
    pthread_attr_t attr2;

    // 2、初始化线程属性变量
    pthread_attr_init(&attr2);

    // 3、设置分离属性
    pthread_attr_setdetachstate(&attr2, PTHREAD_CREATE_DETACHED);

    // 4、创建线程2,用来发送数据,线程拥有分离属性
    errno = pthread_create(&tid2, &attr2, send_routine, NULL);
    if(errno == 0)
    {
        printf("pthread create send_routine success, tid = %ld\n", tid2);
    }
    else
    {
        perror("pthread create send_routine fail\n");
    }

    // 5、销毁属性变量
    pthread_attr_destroy(&attr2);

    // 一定要加这个,否则主函数直接退出,相当于进程退出,所有线程也退出
    // 或者加上while(1)等让主函数不退出
    pthread_exit(0);
    
    return 0;
}

        用读写锁来实现对一个整型数据的操作和访问,一条线程使数据自增,另外一条线程判断该数据的奇偶性,并设置写锁优先。

 

// 读写锁的案例

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

int data = 100; // 共享变量

pthread_rwlock_t data_rwlock; // 定义互斥锁变量
pthread_once_t data_rwlock_once_init;    // 函数单例初始化变量
pthread_once_t data_rwlock_once_destroy;    // 函数单例销毁变量

// 初始化互斥锁data_rwlock
void data_rwlock_init(void)
{
    pthread_rwlockattr_t data_rwlock_attr;
    // 设置锁属性为写锁优先
    // 1、初始化读写锁属性
    pthread_rwlockattr_init(&data_rwlock_attr);

    // 2、设置读写锁属性为写锁优先
    pthread_rwlockattr_setkind_np(&data_rwlock_attr, PTHREAD_RWLOCK_PREFER_WRITER_NP);

    // 3、动态初始化锁资源,此时读写锁是写锁优先的
    pthread_rwlock_init(&data_rwlock, NULL);

    // 4、销毁读写锁属性
    pthread_rwlockattr_destroy(&data_rwlock_attr);
}

// 销毁互斥锁data_rwlock
void data_rwlock_destroy(void)
{
    pthread_rwlock_destroy(&data_rwlock);
}

// 线程1的例程函数,用来接收数据
void *recv_routine(void *arg)
{
    printf("I am recv_routine, my tid = %ld\n", pthread_self());

    // 设置线程分离 
    pthread_detach(pthread_self()); 

    // 函数单例,本程序只会执行data_rwlock_init()一次
    pthread_once(&data_rwlock_once_init, data_rwlock_init);

    while(1)
    {
        // 加上读锁
        pthread_rwlock_rdlock(&data_rwlock);    
        if(data % 2)
        {
            printf("%d 是奇数\n", data);
        }
        else
        {
            printf("%d 是偶数\n", data);
        }
        // 解锁
        pthread_rwlock_unlock(&data_rwlock);
    }

    // 函数单例,本程序只会执行data_rwlock_init()一次
    pthread_once(&data_rwlock_once_destroy, data_rwlock_destroy);
}

// 线程2的例程函数,用来发送数据
void *send_routine(void *arg)
{
    printf("I am send_routine, my tid = %ld\n", pthread_self());

    // 函数单例,本程序只会执行data_rwlock_init()一次
    pthread_once(&data_rwlock_once_init, data_rwlock_init);

    while(1)
    {
        // 加上写锁
        pthread_rwlock_wrlock(&data_rwlock);

        data++;

        // 解锁
        pthread_rwlock_unlock(&data_rwlock);
    }

    // 函数单例,本程序只会执行data_rwlock_init()一次
    pthread_once(&data_rwlock_once_destroy, data_rwlock_destroy);
}

int main(int argc, char *argv[])
{
    pthread_t tid1, tid2;

    // 创建线程1,用来接收数据
    errno = pthread_create(&tid1, NULL, recv_routine, NULL);
    if(errno == 0)
    {
        printf("pthread create recv_routine success, tid = %ld\n", tid1);
    }
    else
    {
        perror("pthread create recv_routine fail\n");
    }

    // 1、定义线程属性变量
    pthread_attr_t attr2;

    // 2、初始化线程属性变量
    pthread_attr_init(&attr2);

    // 3、设置分离属性
    pthread_attr_setdetachstate(&attr2, PTHREAD_CREATE_DETACHED);

    // 4、创建线程2,用来发送数据,线程拥有分离属性
    errno = pthread_create(&tid2, &attr2, send_routine, NULL);
    if(errno == 0)
    {
        printf("pthread create send_routine success, tid = %ld\n", tid2);
    }
    else
    {
        perror("pthread create send_routine fail\n");
    }

    // 5、销毁属性变量
    pthread_attr_destroy(&attr2);

    // 一定要加这个,否则主函数直接退出,相当于进程退出,所有线程也退出
    // 或者加上while(1)等让主函数不退出
    pthread_exit(0);
    
    return 0;
}

七、总结

        互斥锁和读写锁都是一种并发机制,用于控制多个线程对共享资源的访问。互斥锁主要用于控制多个线程对共享资源的访问,确保同一时刻只有一个线程能够访问共享资源,而读写锁适用于读多写少的场景,可以提高并发读的性能。 读写锁的属性设置需要遵循一定的步骤。

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

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

相关文章

鱼眼相机去畸变(图像拉直/展开/矫正)算法及实战总结

本文介绍两种方法 1、经纬度矫正法 2、棋盘格矫正法 一、经纬度矫正法 1、算法说明 经纬度矫正法&#xff0c; 可以把鱼眼图想象成半个地球&#xff0c; 然后将地球展开成地图&#xff0c;经纬度矫正法主要是利用几何原理&#xff0c; 对图像进行展开矫正。 经过P点的入射光线…

操作系统-《王道 操作系统》

概念、功能和目标 概念 什么是操作系统 功能和目标 作为系统的管理者向上提供方便简易的服务作为最接近底层硬件的层次 特征 并发 共享 并发与共享的关系—互为存在条件 虚拟 异步 发展与分类 单道批处理系统多道批处理系统 优点&#xff1a;多道程序并发执行&#xff0c…

Java笔记六(面向对象:类与对象)

面向对象编程的本质&#xff1a;以类的方式组织代码&#xff0c;以对象的组织&#xff08;封装&#xff09;数据 抽象 三大特征&#xff1a;封装 继承 多态 从认识角度考虑是先有对象后有类。对象&#xff0c;是具体的事物。类&#xff0c;是抽象的&#xff0c;是对对象的抽…

【JVM】 类加载机制、类加载器、双亲委派模型详解

文章目录 前言一、类加载机制二、类加载器三、双亲委派模型总结 前言 &#x1f4d5;各位读者好, 我是小陈, 这是我的个人主页 &#x1f4d7;小陈还在持续努力学习编程, 努力通过博客输出所学知识 &#x1f4d8;如果本篇对你有帮助, 烦请点赞关注支持一波, 感激不尽 &#x1f4d…

Foxit PDF

Foxit PDF 福昕PDF 软件&#xff0c;可以很好的编辑PDF文档。 调整&#xff30;&#xff24;&#xff26;页面大小 PDF文档中&#xff0c;一个页面大&#xff0c;一个页面小 面对这种情况,打开Foxit PDF 右键单击需要调整的页面,然后选择"调整页面大小". 可以选择…

【网络安全-sql注入(5)】sqlmap以及几款自动化sql注入工具的详细使用过程

一&#xff0c;sqlmap 工具的详细使用 kali系统自带这个工具&#xff0c;无需安装直接sqlmap 后面接参数使用 Windows上参照以下方法安装即可 1-1 工具下载 1-1-1 sqlmap下载 sqlmap 工具下载地址&#xff1a; GitHub - sqlmapproject/sqlmap: Automatic SQL injection a…

【C语言】【动态内存管理】malloc,free,calloc,realloc

1.malloc函数 void* malloc(size_t size)功能&#xff1a;向内存申请字节为 size大小的空间 使用时要包含头文件&#xff1a;<stdlib.h> 开辟成功&#xff1a;返回开辟好的空间初始地址的指针 开辟失败&#xff1a;返回空指针 NULL 使用举例&#xff1a; (malloc和free…

Win10系统中GPU深度学习环境配置记录

运行环境 系统&#xff1a;Win10 处理器 Intel(R) Core(TM) i7-9700K CPU 3.60GHz 3.60 GHz 机带 RAM 16.0 GB 设备 ID A18D4ED3-8CA1-4DC6-A6EF-04A33043A5EF 产品 ID 00342-35285-64508-AAOEM 系统类型 64 位操作系统, 基于 x64 的处理器 显卡&#xff1a;NVIDIA GeF…

快速上手Apache POI

哈喽~大家好&#xff0c;这篇我们来看看快速上手Apache POI。 &#x1f947;个人主页&#xff1a;个人主页​​​​​ &#x1f948; 系列专栏&#xff1a;【日常学习上的分享】 &#x1f949;与这篇相关的文章&#xff1a; Red…

你写过的最蠢的代码是?——一起探讨编程的奇葩时刻

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页: &#x1f405;&#x1f43e;猫头虎的博客&#x1f390;《面试题大全专栏》 &#x1f995; 文章图文并茂&#x1f996…

常见web信息泄露

一、源码(备份文件)泄露 1、git泄露 Git是一个开源的分布式版本控制系统&#xff0c;在执行git init初始化目录的时候&#xff0c;会在当前目录下自动创建一个.git目录&#xff0c;用来记录代码的变更记录等。发布代码的时候&#xff0c;如果没有把.git这个目录删除&#xff…

CCF CSP认证 历年题目自练 Day20

题目一 试题编号&#xff1a; 201903-1 试题名称&#xff1a; 小中大 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 512.0MB 问题描述&#xff1a; 题目分析&#xff08;个人理解&#xff09; 常规题目&#xff0c;先看输入&#xff0c;第一行输入n表示有多少数字&am…

Rabbitmq安装-docker版

1.简介 2.安装消息队列 下载地址https://www.rabbitmq.com/download.html 使用docker方式安装 需要先下载docker&#xff0c;参考文章https://blog.csdn.net/weixin_43917045/article/details/104747341?csdn_share_tail%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22arti…

基于SSM在线医疗服务系统+jsp【附开题|万字文档(LW)和搭建文档】

主要功能 前台登录&#xff1a; 注册用户&#xff1a;用户名、密码、姓名、联系电话 注册医生&#xff1a;医生工号、密码、医生姓名、职称、联系电话、邮箱 用户&#xff1a; ①首页、药品推荐、新闻资讯、健康资讯展示 ②医生坐诊、个人介绍、评价、预约挂号、点我收藏、药品…

实现map和set

map和set的底层就是红黑树&#xff0c;所以只要实现了红黑树&#xff0c;就可以考虑封装出map和set。所以本文会重点介绍我是如何一步步封装map和set&#xff0c;就不会再介绍红黑树的实现&#xff0c;实现可看我上篇博客&#xff0c;个人觉得封装中最麻烦的就是模板参数和迭代…

linux系统的启动流程

目录 简述linux的启动流程 git简介 Linux文件 Ubuntu文件汇总 linux文件属性 Linux命令行 更换软件源 简述linux的启动流程 韦东山课程学习路线&#xff1a;APP应用--DEV驱动--项目。 百问网官网 git资料&#xff1a;https://e.coding.net/weiongshan/01_all_series_qu…

来看看这个JS题输出什么?教你通过断电调试一步步看原因

&#x1f3b6;让我们调试看看这段代码 var foo { n: 1 };(function (foo) {console.log(foo.n) foo.n 3var foo { n: 2 }foo.n 4console.log(foo.n)})(foo)console.log(foo.n);&#x1f367;输出结果 &#x1f3a1;调试解析 &#x1f389;第一步 &#x1f38f;第二步 ✨第…

新一代网络框架UringNet,基于最新的异步I/O

介绍 在去年的一篇文章中&#xff0c;笔者曾经提到了最新一代的网络I/O框架UringNet。具体内容可以参考Rings’ Power,性能“世界第一”的Web I/O框架。这是基于最新Linux内核的异步I/O组件io_uring开发的网络框架。由于采用了最新的异步框架&#xff0c;因此在同等硬件配置条件…

五款可替代163邮箱的电子邮件服务

在众多邮箱品牌中&#xff0c;163邮箱作为中国最早的邮箱服务提供商之一&#xff0c;其出海之路并不顺利。本文将探讨163邮箱出海的劣势&#xff0c;并介绍一些替代品&#xff0c;以帮助用户更好地选择适合自己的邮箱服务。 “163邮箱的替代品有哪些&#xff1f;外贸行业适合选…

指针详解第二部分

目录 1. 数组名的理解 2. 使⽤指针访问数组 3. ⼀维数组传参的本质 4. 冒泡排序 5. ⼆级指针 6. 指针数组 7. 指针数组模拟⼆维数组 1. 数组名的理解 首先先看一个代码 #include <stdio.h> int main() {int arr[10] { 1,2,3,4,5,6,7,8,9,10 };printf("&a…