互斥锁/读写锁(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);
    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 函数用于在指定的微秒数内挂起当前线程的执行。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微软人工智能办公AI工具 Copilot Pro 11项 Copilot 功能

Copilot&#xff08;曾用名 Bing Chat 和 Bing Chat Enterprise&#xff09;在此期间成为了许多用户的日常AI伴侣&#xff0c;并在正式发布后将继续为用户提供AI驱动的网络聊天体验。 微软Copilot官方网址链接&#xff1a;Microsoft Copilot: 你的日常 AI 助手 Copilot详情&am…

香港web3盛会:Unisat确认参加Big Demo Day项目路演

本次“Big Demo Day”将于1月31日举办第十期&#xff0c;是由Zeepr 总冠名&#xff0c;Central Research、Techub News联合主办、数码港、852web3支持举行的大型线下活动。Big Demo Day集结了Web2和Web3行业精英聚焦香港市场。 Unisat确认参加 Big Demo Day 线下活动&#xff0…

14.java集合

文章目录 概念Collection 接口概念示例 Iterator 迭代器基本操作&#xff1a;并发修改异常增强循环遍历数组&#xff1a;遍历集合&#xff1a;遍历字符串&#xff1a;限制 list接口ListIteratorArrayList创建 ArrayList&#xff1a;添加元素&#xff1a;获取元素&#xff1a;修…

Java JVM类加载阶段 双亲委派模式

类加载阶段 加载 将类的字节码载入方法区中&#xff0c;内部采用 C 的 instanceKlass 描述 java 类&#xff0c;它的重要 field 有&#xff1a; _java_mirror 即 java 的类镜像&#xff0c;例如对 String 来说&#xff0c;就是 String.class&#xff0c;作用是把 klass 暴露…

WWDG喂狗

3F 是0111111 40 是1000000 0X7F 127 0X5F 95 127-9532 注意:中断是在0x40,在0x40喂狗则程序不会复位 在0x5F之前喂狗会复位,减小到63以下也会复位 在0x5F与0x3F之间喂狗会继续执行,不会复位 WWDG_HandleTypeDef WWDG_Handler; //窗口看门狗句柄//初始化窗口看门狗…

2024 高级前端面试题之 HTML 「精选篇」

该内容主要整理关于 HTML 的相关面试题&#xff0c;其他内容面试题请移步至 「最新最全的前端面试题集锦」 查看。 HTML模块精选篇 1. 如何理解HTML语义化2. H5的新特性有哪些3. 说一下 HTML5 Drag API4. iframe有那些缺点5. 如何实现浏览器内多个标签页之间的通信6. 简述一下s…

EF core连接数据库的前期完整配置流程-开发环境搭建

EF core连接数据库完整流程-开发环境搭建 前置&#xff1a;.net6 core webapi不勾选任何配置 声明&#xff1a;这里是以两个配置类来做的&#xff0c;一个T_Books表&#xff0c;一个T_Person表 Book&#xff1a;创建属性及类型 BookConfig&#xff1a;对创建的进行属性数据表…

坚持刷题 | 平衡二叉树

文章目录 题目考察点代码实现实现总结对实现进一步改进扩展提问 坚持刷题&#xff0c;老年痴呆追不上我&#xff0c;今天继续二叉树&#xff1a;平衡二叉树 题目 110.平衡二叉树 考察点 递归能力&#xff1a; 能否使用递归来解决问题。树的基本操作&#xff1a;能否正确地访…

【算法练习】leetcode算法题合集之动态规划篇

普通动规系列 LeetCode343. 整数拆分 LeetCode343. 整数拆分 将10的结果存在索引为10的位置上&#xff0c;需要保证数组长度是n1&#xff0c;索引的最大值是n&#xff0c;索引是从0开始的。 n的拆分&#xff0c;可以拆分为i和n-i&#xff0c;当然i可以继续拆分。而且拆分为n-…

Vue基础知识

Vue Vue基础知识 v-bind:动态绑定属性值 Vue 修改&#xff0c;标签内也修改 在methods 中可以定义很多函数 在 data 中可以定义很多变量 v-if / v-show&#xff1a;对符合条件的元素进行展示 v-for:把数据遍历出现在网页中 案例 <!DOCTYPE html><html lang"e…

【论文阅读笔记】Towards Universal Unsupervised Anomaly Detection in Medical Imaging

Towards Universal Unsupervised Anomaly Detection in Medical Imaging arxiv&#xff0c;19 Jan 2024 【开源】 【核心思想】 本文介绍了一种新的无监督异常检测方法—Reversed Auto-Encoders (RA)&#xff0c;旨在提高医学影像中病理检测的准确性和范围。RA通过生成类似健…

C语言关键字

关键字的分类 C语言一共多少个关键字呢&#xff1f;一般的书上&#xff0c;都是32个,但是这个都C90(C89) 的标准。其实 C99 后又新增了5个关键字。不过&#xff0c;目前主流的编译器&#xff0c;对 C99 支持的并不好&#xff0c;默认使用 C90 &#xff0c;即&#xff0c;认为3…

【大数据】Flink 中的数据传输

Flink 中的数据传输 1.基于信用值的流量控制2.任务链接 在运行过程中&#xff0c;应用的任务会持续进行数据交换。TaskManager 负责将数据从发送任务传输至接收任务。它的网络模块在记录传输前会先将它们收集到 缓冲区 中。换言之&#xff0c;记录并非逐个发送的&#xff0c;而…

活字格V9获取图片失败bug,报错404,了解存储路径,已改为批量上传和批量获取

项目场景&#xff1a; 问题描述 原因分析&#xff1a; 解决方案&#xff1a; 完成了批量上传功能&#xff0c;这插件真的很方便 于是写了个批量获取附件的js代码&#xff0c;我真厉害 项目场景&#xff1a; 活字格V9版本获取图片链接Upload 【9.0.103.0】图片上传的存储路…

java web 研究生信息管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java Web研究生信息管理系统是一套完善的java web信息管理系统 &#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境 为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为My…

Keycloak - docker 运行 前端集成

Keycloak - docker 运行 & 前端集成 这里的记录主要是跟我们的项目相关的一些本地运行/测试&#xff0c;云端用的 keycloak 版本不一样&#xff0c;不过本地我能找到的最简单的配置是这样的 docker 配置 & 运行 keycloak keycloak 有官方(Red Hat Inc.)的镜像&#…

助力工业产品质检,基于YOLOv7【tiny/l/x】不同系列参数模型开发构建智能PCB电路板质检分析系统

AI助力工业质检智能生产制造已经有很多成功的实践应用了&#xff0c;在我们前面的系列博文中也有很多对应的实践&#xff0c;感兴趣的话可以自行移步阅读前面的博文即可&#xff1a; 《助力质量生产&#xff0c;基于目标检测模型MobileNetV2-YOLOv3-Lite实现PCB电路板缺陷检测…

python:socket基础操作(4)-《tcp客户端基础》

tcp就和udp不一样了&#xff0c;tcp是客户端和服务器端&#xff0c;如果想通过tcp发送数据&#xff0c;要先让tcp进行连接服务器端 tcp客户端 先让服务器端进行启动 import socketdef main():# 创建套接字tcp_client_socket socket.socket(socket.AF_INET,socket.SOCK_STREAM…

【原理图PCB专题】OrCAD Capture CIS关闭开始界面

17.4版本 在打开OrCAD Capture CIS时会发现打开Start Page页面&#xff0c;那么如何将他关闭再也不看这个界面呢&#xff1f; 在窗口中输入SetOptionBool EnableStartPage 0 回车 重启软件后就再也不会弹出Start Page页面 如果没有发现Command Window那么将菜单栏view->C…

Cesium绘制流动管线

目录 一、第一种方式 二、第二种方式 1.安装gsap 2.引入 一、第一种方式 使用viewer.clock Clock文档 viewer.clock文档 vec4 colorImage texture2D(image, vec2(fract(st.s - time), st.t));这里&#xff0c;因为使用的是高版本的cesium1.108,直接写texture2D会报错&am…