互斥锁/读写锁(Linux)

 一、互斥锁

临界资源概念:

不能同时访问的资源,比如写文件,只能由一个线程写,同时写会写乱。

比如外设打印机,打印的时候只能由一个程序使用。

外设基本上都是不能共享的资源。

生活中比如卫生间,同一时间只能由一个人使用。

必要性: 临界资源不可以共享

两种方法创建互斥锁,静态方式和动态方式
动态方式:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
其中mutexattr用于指定互斥锁属性,如果为NULL则使用缺省属性。
静态方式:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;锁的销毁:
int pthread_mutex_destroy(pthread_mutex_t *mutex)
在Linux中,互斥锁并不占用任何资源,
因此LinuxThreads中的 pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。互斥锁的使用:
int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)vim 设置代码全文格式化:gg=G

查看线程:

没有加互斥锁: 

#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>FILE* fp;void* testattr(void* arg)
{pthread_detach(pthread_self());printf("This is testattr pthread\n");char str[] = "You read testattr thread\n";char c;int i = 0;while(1){while(i<strlen(str)){c = str[i];int ret = 0;ret = fputc(c,fp);i++;usleep(1);}i = 0;usleep(1);}pthread_exit("testattr exit");
}void* testattr2(void* arg)
{pthread_detach(pthread_self());printf("This is testattr2 pthread\n");char str[] = "I write testattr2 line\n";char c;int i = 0;while(1){while(i<strlen(str)){c = str[i];int ret = 0;ret = fputc(c,fp);i++;usleep(1);}i = 0;usleep(1);}pthread_exit("testattr2 exit");
}int main()
{pthread_t pthread;pthread_t pthread2;int i = 0;void* retv;fp = fopen("1.txt","a+");if(fp == NULL){perror("fp");exit(-1);}pthread_create(&pthread,NULL,testattr,NULL);pthread_create(&pthread2,NULL,testattr2,NULL);while(1){sleep(1);}fclose(fp);return 0;
}

 1.txt中的值:

 加上互斥锁:

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);

mutex 参数是一个指向互斥锁对象的指针,该锁对象必须是一个已经初始化的互斥锁。

pthread_mutex_lock 函数尝试对互斥锁进行加锁。如果互斥锁当前没有被锁住,那么调用将成功,该线程将对互斥锁进行加锁并立即返回。如果互斥锁当前被其他线程锁住,那么调用将被阻塞,直到互斥锁被释放。

在加锁之后,线程负责确保在对共享资源的访问完成后调用 pthread_mutex_unlock 函数进行解锁,以允许其他线程获得对互斥锁的访问。

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 
是一种静态初始化互斥锁的方式。
在使用互斥锁之前,必须对其进行初始化。
PTHREAD_MUTEX_INITIALIZER 是一个宏,它在编译时为互斥锁对象提供了默认的初始值。
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>FILE* fp;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* testattr(void* arg)
{pthread_detach(pthread_self());printf("This is testattr pthread\n");char str[] = "You read testattr thread\n";char c;int i = 0;while(1){pthread_mutex_lock(&mutex);while(i<strlen(str)){c = str[i];int ret = 0;ret = fputc(c,fp);i++;usleep(1);}pthread_mutex_unlock(&mutex);i = 0;usleep(1);}pthread_exit("testattr exit");
}void* testattr2(void* arg)
{pthread_detach(pthread_self());printf("This is testattr2 pthread\n");char str[] = "I write testattr2 line\n";char c;int i = 0;while(1){pthread_mutex_lock(&mutex);while(i<strlen(str)){c = str[i];int ret = 0;ret = fputc(c,fp);i++;usleep(1);}pthread_mutex_unlock(&mutex);i = 0;usleep(1);}pthread_exit("testattr2 exit");
}int main()
{pthread_t pthread;pthread_t pthread2;int i = 0;void* retv;fp = fopen("1.txt","a+");if(fp == NULL){perror("fp");exit(-1);}pthread_create(&pthread,NULL,testattr,NULL);pthread_create(&pthread2,NULL,testattr2,NULL);while(1){sleep(1);}fclose(fp);return 0;
}

1.txt中的值: 

动态方式创建互斥锁:
        int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
        其中mutexattr用于指定互斥锁属性,如果为NULL则使用缺省属性。

二、读写锁

必要性:提高线程执行效率

特性:

        写者:写者使用写锁,如果当前没有读者,也没有其他写者,写者立即获得写锁;否则写者将等待,直到没有读者和写者。

        读者:读者使用读锁,如果当前没有写者,读者立即获得读锁;否则读者等待,直到没有写者。

注意:

        同一时刻只有一个线程可以获得写锁,同一时刻可以有多个线程获得读锁。 

        读写锁处于写锁状态时,所有试图对读写锁加锁的线程,不管是读者试图加读锁,还是写者试图加写锁,都会被阻塞。

        读写锁处于读锁状态时,有写者试图加写锁时,之后的其他线程的读锁请求会被阻塞,以避免写者长时间的不写锁

初始化一个读写锁   pthread_rwlock_init读锁定读写锁       pthread_rwlock_rdlock非阻塞读锁定     pthread_rwlock_tryrdlock写锁定读写锁      pthread_rwlock_wrlock非阻塞写锁定      pthread_rwlock_trywrlock解锁读写锁         pthread_rwlock_unlock释放读写锁         pthread_rwlock_destroy
int pthread_detach(pthread_t thread);    成功:0;失败:错误号作用:从状态上实现线程分离,注意不是指该线程独自占用地址空间。线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后(不会产生僵尸线程),其退出状态不由其他线程获取,而直接自己自动释放(自己清理掉PCB的残留资源)。网络、多线程服务器常用。

 示例代码:

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>pthread_rwlock_t rwlock;FILE *fp;
void * read_func(void *arg){pthread_detach(pthread_self());printf("read thread\n");char buf[32]={0};while(1){pthread_rwlock_rdlock(&rwlock);while(fgets(buf,32,fp)!=NULL){printf("%d,rd=%s\n",(int)arg,buf);usleep(1000);}pthread_rwlock_unlock(&rwlock);sleep(1);}}void *func2(void *arg){pthread_detach(pthread_self());printf("This func2 thread\n");char str[]="I write func2 line\n";char c;int i=0;while(1){pthread_rwlock_wrlock(&rwlock);while(i<strlen(str)){c = str[i];fputc(c,fp);usleep(1);i++;}pthread_rwlock_unlock(&rwlock);i=0;usleep(1);}pthread_exit("func2 exit");}void *func(void *arg){pthread_detach(pthread_self());printf("This is func1 thread\n");char str[]="You read func1 thread\n";char c;int i=0;while(1){pthread_rwlock_wrlock(&rwlock);while(i<strlen(str)){c = str[i];fputc(c,fp);i++;usleep(1);}pthread_rwlock_unlock(&rwlock);i=0;usleep(1);}pthread_exit("func1 exit");
}int main(){pthread_t tid1,tid2,tid3,tid4;void *retv;int i;fp = fopen("1.txt","a+");if(fp==NULL){perror("fopen");return 0;}pthread_rwlock_init(&rwlock,NULL);pthread_create(&tid1,NULL,read_func,1);pthread_create(&tid2,NULL,read_func,2);
sleep(1);
//读写是抢占,不是按顺序
//有时候一直在调用读,写没排上号
//谁抢上是谁,所以加上sleep(1)pthread_create(&tid3,NULL,func,NULL);pthread_create(&tid4,NULL,func2,NULL);while(1){    sleep(1);} }
  1. 文件和锁的初始化:

    • 该程序以附加模式("a+")打开名为"1.txt"的文件,使用fopen。如果文件不存在,它尝试创建它。
    • 使用pthread_rwlock_init初始化读写锁。
  2. 线程函数:

    • read_func:该函数负责从文件中读取。它不断从文件中读取行并将它们打印到控制台。它使用读锁以确保它可以与其他读取器并发读取,但在读取正在进行时不能写入。
    • testattrtestattr1:这些函数不断将预定义的字符串写入文件。它们使用写锁以确保只有其中一个可以同时写入文件。
  3. 主函数:

    • 它创建五个线程:两个用于读取(pthread2pthread3),三个用于写入(pthreadpthread1和主线程)。
    • 主线程进入一个无限循环(while(1))以使程序无限期运行。
  4. 线程分离:

    • 使用pthread_detach(pthread_self())分离每个线程,以在线程退出时自动回收资源。
  5. 线程同步:

    • 读写操作由读写锁(pthread_rwlock_rdlockpthread_rwlock_wrlock)保护,以确保正确的同步并避免数据损坏。
  6. 文件操作:

    • 文件操作使用标准文件I/O函数(fgets用于读取,fputc用于写入)。
  7. 睡眠和延迟:

    • 使用usleepsleep引入操作之间的延迟,以更好地展示并发行为。
  8. 主函数中的无限循环:

    • 通过带有sleep(1)延迟的无限循环,保持主线程处于活动状态。

那读写的顺序是什么?

         在这个程序中,有两个读线程 (read_func) 和两个写线程 (testattrtestattr1),以及主线程。读线程和写线程是同时运行的,并且使用了读写锁 (pthread_rwlock_t rwlock) 来确保对文件的安全访问。

  • 读线程 (read_func) 在一个无限循环中,通过 pthread_rwlock_rdlock 获取读锁,然后通过 fgets 从文件中读取内容。读操作完成后,通过 pthread_rwlock_unlock 释放读锁。这表示读线程首先获取读锁,然后读取文件内容。

  • 写线程 (testattrtestattr1) 在一个无限循环中,通过 pthread_rwlock_wrlock 获取写锁,然后通过 fputc 向文件写入内容。写操作完成后,通过 pthread_rwlock_unlock 释放写锁。这表示写线程首先获取写锁,然后写入文件内容。

        总体而言,程序中的读线程和写线程是同时运行的,但是通过使用读写锁,可以确保对文件的安全访问,防止读和写操作之间的冲突。

注意:

读线程 (read_func) 在读取文件时获取读锁 (pthread_rwlock_rdlock),
这时其他读线程也可以同时获取读锁。
这允许多个读线程同时读取文件内容,因为读操作不会互斥。
在使用读写锁时,写线程获得写锁时会阻塞其他写线程和读线程,
以确保在写入文件时不会同时有其他线程读或写。在这个程序中,当一个线程(写线程)获得写锁时,
其他线程(包括读线程和其他写线程)都会被阻塞,直到写线程释放写锁。所以,写线程会独占地访问文件,直到它完成写入并释放写锁。
这种方式确保了写的原子性,防止多个写线程之间和读线程之间的竞争条件。

为什么需要读锁?

虽然读操作本身不会改变数据,但如果在读的同时有其他线程在写,就可能读到不一致或不准确的数据。
因此,在多线程环境下,为了确保数据的一致性,读操作也需要进行同步,而这就是使用读写锁的原因。
上述的程序中,pthread_rwlock_rdlock 用于获取读锁,而 pthread_rwlock_unlock 用于释放读锁。
这样做的目的是:通过获取读锁,确保在读取文件内容时不会被写线程中断,避免了读线程和写线程之间的竞态条件。释放读锁,以允许其他读线程或写线程访问文件。使用读写锁可以实现多个线程同时读取文件,但在写线程写入时阻塞读线程,以保证对文件的安全访问。
这种同步机制确保了对共享资源的正确和一致的访问。

为什么此代码只需要一个读的回调函数?

在这个特定的程序中,可能只需要一个读回调函数。
读回调函数负责获取读锁,读取文件内容,然后释放读锁,以确保在读的过程中其他线程不会写入文件。
由于读操作本身不修改数据,多个读线程可以并发地执行。写操作则需要更谨慎地处理,因为写线程在写入时需要独占访问,避免与其他写线程或读线程发生冲突。
因此,写回调函数需要获取写锁,执行写操作,然后释放写锁。

这里为什么需要usleep(1000)?

usleep(1000); 的目的是让读线程休眠一毫秒(1000微秒)。
这样的操作通常是为了减缓循环的执行速度,以避免过于频繁地执行文件读取操作。
usleep 函数用于在指定的微秒数内挂起当前线程的执行。

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

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

相关文章

Windows 上面双网卡网络,配置为优先IPV4

多数网络游戏加速器是不支持IPV6的&#xff0c;即便支持IPV6也不好用&#xff0c;原因是IPV6在大陆并不是普及的状态&#xff0c;很多资源是没有的。 所以本文会教大家怎么让双IP栈的用户&#xff0c;怎么配置优先适用IPV4&#xff0c;并且IPV6也还可以用。 跟着我的步骤来&am…

Docker 和 Kubernetes:容器化时代的崛起与演变

在过去的十年间&#xff0c;容器化技术彻底改变了软件开发和部署的面貌。 Docker 的登场无疑是这场变革的催化剂&#xff0c;它将应用和服务的打包、分发、部署流程标准化&#xff0c;让开发者的生活变得更加简单。 紧随其后&#xff0c;Kubernetes 作为容器编排的领军者&#…

虹科分享丨汽车技术的未来:Netropy如何测试和确保汽车以太网的性能

来源&#xff1a;艾特保IT 虹科分享丨汽车技术的未来&#xff1a;Netropy如何测试和确保汽车以太网的性能 原文链接&#xff1a;https://mp.weixin.qq.com/s/G8wihrzqpJJOx5i0o63fkA 欢迎关注虹科&#xff0c;为您提供最新资讯&#xff01; #汽车以太网 #车载网络 #Netropy …

STM32第一节——初识STM32

1 硬件介绍 1.1 硬件平台 配套硬件&#xff1a;以野火的STM32 F1霸道开发板为平台&#xff0c;若用的是别的开发板&#xff0c;可自己进行移植。 1.2 什么是STM32 STM32是由意法半导体&#xff08;STMicroelectronics&#xff09;公司推出的一系列32位的ARM Cortex-M微控制…

D. Gargari and Permutations

很好玩的一道类似LCS的DP 问题 定义dp(i) 为考虑最后一个字符串&#xff0c;且选择a&#xff08;i&#xff09;得到的最大LIS值 然后枚举所有小于i的位置&#xff0c;可以更新的条件是 所有的字符串中都有a[j]<a[i] 这个用map一处理就好了 #include<bits/stdc.h> usi…

腾讯云轻量应用Windows服务器如何搭建幻兽帕鲁Palworld私服?

幻兽帕鲁/Palworld是一款2024年Pocketpair开发的开放世界生存制作游戏&#xff0c;在帕鲁的世界&#xff0c;玩家可以选择与神奇的生物“帕鲁”一同享受悠闲的生活&#xff0c;也可以投身于与偷猎者进行生死搏斗的冒险。而帕鲁可以进行战斗、繁殖、协助玩家做农活&#xff0c;也…

Type-C平板接口协议芯片介绍,实现单C口充放电功能

在现代平板电脑中&#xff0c;Type-C接口已经成为了一个非常常见的接口类型。相比于传统的USB接口&#xff0c;Type-C接口具有更小的体积、更快的传输速度和更方便的插拔体验。但是&#xff0c;在使用Type-C接口的平板电脑上&#xff0c;如何实现单C口充电、放电和USB2.0数据传…

qt学习:tcp区分保存多个客户端

在前面文掌的tcp客服端服务端进行更改 qt学习&#xff1a;Network网络类tcp客户端tcp服务端-CSDN博客https://blog.csdn.net/weixin_59669309/article/details/135842933?spm1001.2014.3001.5501前面的服务端每次有新的客户端连接&#xff0c;就会覆盖掉原来的指针&#xff0…

uniCloud ---- 云存储

目录 概念 1. web界面 2. 客户端API或组件上传 API 自定义样式 选择指定后缀图片&#xff0c;且限制选择个数 手动上传 应用 3. 云函数上传文件到云存储 uni.chooseImage(OBJECT) uni.previewImage(OBJECT) uploadFile(Object object) 请求参数 响应参数 应用 概念…

Web11--Bootstrap

1、Bootstrap入门 1.1 Bootstrap简介 官网&#xff1a;Bootstrap中文网 1.2 Bootstrap引入 <!DOCTYPE html> <html><head><meta charset"utf-8"><meta name"viewport" content"widthdevice-width, initial-scale1"…

服务器是什么?(四种服务器类型)

服务器 服务器定义广义: 专门给其他机器提供服务的计算机。狭义:一台高性能的计算机&#xff0c;通过网络提供外部计算机一些业务服务 个人PC内存大概8G&#xff0c;服务器内存128G起步 服务器是什么 服务器指的是 网络中能对其他机器提供某些服务的计算机系统 &#xff0c;相对…

一款颜值与实力并存的翻页时钟(免费)

FliTik是一款颜值与实力并存的翻页时钟&#xff0c;安卓端是完全免费的&#xff0c;无任何广告&#xff0c;极简风 &#xff0c;软件默认是12小时制&#xff0c;可以在设置中启用24小时制&#xff0c;并且还支持设置显示秒钟、日期、文案&#xff0c;滴答声和语音报时。 支持横…

数据分析的理念、流程、方法、工具(上)

一、数据的价值 1、数据驱动企业运营 从电商平台的「猜你喜欢」到音乐平台的「心动模式」&#xff0c;大数据已经渗透到了我们生活的每一个场景。不论是互联网行业&#xff0c;还是零售业、制造业等&#xff0c;各行各业都在依托互联网大数据&#xff08;数据采集、数据存储、…

C++ 设计模式之责任链模式

【声明】本题目来源于卡码网&#xff08;卡码网KamaCoder&#xff09; 【提示&#xff1a;如果不想看文字介绍&#xff0c;可以直接跳转到C编码部分】 【设计模式大纲】 【简介】 --什么是责任链模式&#xff08;第21种设计模式&#xff09; 责任链模式是⼀种行为型设计模式&am…

C语言实现希尔排序算法(附带源代码)

希尔排序 希尔排序&#xff0c;也称递减增量排序算法&#xff0c;是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。 希尔排序是基于插入排序的以下两点性质而提出改进方法的&#xff1a; 插入排序在对几乎已经排好序的数据操作时&#xff0c;效率高&#xff0…

Java PDFBox 提取页数、PDF转图片

PDF 提取 使用Apache 的pdfbox组件对PDF文件解析读取和转图片。 Maven 依赖 导入下面的maven依赖&#xff1a; <dependency><groupId>org.apache.pdfbox</groupId><artifactId>pdfbox</artifactId><version>2.0.30</version> &l…

基于Docker、Minikube在PC端构建K8S试验环境

在桌面电脑上使用Docker和Minikube构建Kubernetes&#xff08;K8S&#xff09;试验环境&#xff0c;为学习和测试提供了一个理想的平台。Docker的容器化技术允许在隔离的环境中运行应用&#xff0c;而Minikube则简化了在单节点上部署和管理Kubernetes集群的过程。这种组合使得个…

Apipost-cli、Jenkins持续集成配置

安装 Apipost-cli npm install -g apipost-cli 运行脚本 安装好Apipost-cli后&#xff0c;在命令行输入生成的命令&#xff0c;即可执行测试用例&#xff0c;运行完成后会展示测试进度并生成测试报告。 Jenkins配置 Apipost cli基于Node js运行 需要在jenkins上配置NodeJs依…

eNSP 实验 两台AR配置同网段

实验1&#xff1a;eNSP 两台AR配置同网段 目的&#xff1a;创建两台AR&#xff0c;配置IP互相ping通 拓扑结构&#xff1a; 首先创建一个AR3260 然后创建一个AR2220 然后同轴电缆连接一下 先配置AR2220。 1、切管理员&#xff1a;system-view 进入千兆位以太网 0/0/0 interf…

MIT_线性代数笔记:第 29 讲 奇异值分解

目录 如何实现用矩阵数学语言描述这一过程举例 本讲介绍奇异值分解&#xff08;Singular value decomposition&#xff09;&#xff0c;简称 SVD。这是矩阵最终也是最好的分解&#xff0c;任意矩阵可分解为 A U Σ V T AUΣV^T AUΣVT&#xff0c;分解结果为正交矩阵 U&#x…