【Linux C | 多线程编程】线程同步 | 互斥量(互斥锁)介绍和使用

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
⏰发布时间⏰:

本文未经允许,不得转发!!!

目录

  • 🎄一、概述
  • 🎄二、为什么需要互斥量
  • 🎄三、互斥量的使用
    • ✨3.1 互斥量的初始化
    • ✨3.2 互斥量的销毁
    • ✨3.3 互斥量的加锁和解锁
  • 🎄四、互斥量的属性
  • 🎄五、总结


在这里插入图片描述

🎄一、概述

互斥量采用的是英文mutual exclusive(互相排斥之意)的缩写,即mutex,是多线程编程中,常用来进行同步访问的方式之一。根据互斥量的用法,可以形象地将互斥量比喻成一把锁,锁住关键代码(临界区),每次只允许一个线程进入。

互斥量的工作机制:互斥量从本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所以在该互斥锁上的阻塞线程都会变成可进行状态,第一个变成运行状态的线程可以对互斥量加锁,其他线程在次被阻塞,等待下次运行状态。


在这里插入图片描述

🎄二、为什么需要互斥量

大部分情况下, 线程使用的数据都是局部变量, 变量的地址在线程栈空间内, 这种情况下, 变量归属于单个线程, 其他线程无法获取到这种变量。但多数的多线程编程种,会出现一些资源是多个线程共享使用的,如:全局变量、堆空间指针变量等。

当多个线程可以同时改变某个共享资源,并且这个改变的操作不是原子操作,而又不加限制的话,那么改变的结果可能是意想不到的。这就是需要互斥量的原因。

🌰看例子:下面例子,创建4个线程,对共享资源(g_Count全局变量)执行了1000万次自加1操作。我们期待的结果应该是4000万,但运行结果有时却非4000万。

// 08_mutex_test.c
// gcc 08_mutex_test.c -lpthread
#include <stdio.h>
#include <pthread.h>
#include <sys/syscall.h>
int g_Count = 0;
void *func(void *arg)
{int i=0;for(i=0; i<10000000; i++){g_Count++;}return NULL;
}int main()
{// 创建4个线程pthread_t threadId[4];int i=0;for(i=0; i<4; i++){pthread_create(&threadId[i], NULL, func, NULL);}for(i=0; i<4; i++){pthread_join(threadId[i],NULL);printf("join threadId=%lx\n",threadId[i]);}printf("g_Count=%d\n",g_Count);return 0;
}

运行结果如下:执行三次,只出现了一次4000万,且每次结果都不一样。因为g_Count++;语句不是一个原子操作,假设4个线程同时获取到g_Count时值为1,4个线程都执行g_Count++后,每个线程都认为此时g_Count的值为2,但4个线程执行了4次了。
在这里插入图片描述
综上所述,当多个线程可以同时操作共享资源时,需要满足下面三点来使各个线程互斥:
1、当一个线程操作共享资源时,不允许其他线程同时操作该资源。
2、当线程不再操作共享资源时,不能阻碍其他线程操作该资源。
3、当多个线程同时操作一个共享资源时,只允许一个线程执行操作。


在这里插入图片描述

🎄三、互斥量的使用

正确地使用互斥量来保护共享数据,首先要定义和初始化互斥量。然后是使用互斥量的加锁、解锁来保护共享数据,最后使用完销毁互斥量。

✨3.1 互斥量的初始化

POSIX提供了两种初始化互斥量的方法。

  • 1、是将PTHREAD_MUTEX_INITIALIZER赋值给定义的互斥量,如下:

    #include <pthread.h>
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    

    但这个方法没办法设置互斥量的属性,也不适用于动态分配的互斥量,比较少用。

  • 2、使用 pthread_mutex_init 初始化互斥量。如下:

    #include <pthread.h>
    int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
    

    第二个pthread_mutexattr_t指针的入参,是用来设定互斥量的属性的。大部分情况下,并不需要设置互斥量的属性,传递NULL即可,表示使用互斥量的默认属性。
    调用pthread_mutex_init之后, 互斥量处于没有加锁的状态。


✨3.2 互斥量的销毁

使用pthread_mutex_init初始化的互斥量,在确定不再需要互斥量的时候, 就要销毁它。 在销毁之前, 有三点需要注意:
1、使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量无须销毁。
2、不要销毁一个已加锁的互斥量, 或者是真正配合条件变量使用的互斥量。
3、已经销毁的互斥量, 要确保后面不会有线程再尝试加锁。

销毁互斥量的接口如下:

#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);

当互斥量处于已加锁的状态, 或者正在和条件变量配合使用, 调用pthread_mutex_destroy函数会返回EBUSY错误码。


✨3.3 互斥量的加锁和解锁

关于互斥量的加锁和解锁,POSIX提供了如下接口:

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_timedlock (pthread_mutex_t * mutex, const struct timespec *abstime);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • pthread_mutex_lock:对互斥量加锁,如果互斥量未锁定,则将互斥量锁定, 同时返回成功。如果互斥量已经上锁,则一直阻塞等待互斥量解锁;
  • pthread_mutex_trylock:对互斥量加锁,如果互斥量未锁定,则将互斥量锁定, 同时返回成功。如果互斥量已经上锁,则不会阻塞等待,直接返回EBUSY
  • pthread_mutex_timedlock:对互斥量加锁,如果互斥量未锁定,则将互斥量锁定, 同时返回成功。如果互斥量已经上锁,则等待abstime设置的时间,如果还处于锁定状态,直接返回ETIMEOUT;注意,abstime是绝对时间,如果最多等待2分钟, 那么这个值应该是当前时间加上2分钟
  • pthread_mutex_unlock:对互斥量解锁。

在这里插入图片描述

🎄四、互斥量的属性

线程和线程的同步对象(互斥量,读写锁,条件变量)都具有属性。在修改属性前都需要对该结构进行初始化。使用后要把该结构回收。大部分情况使用的都是默认属性

互斥量的属性相关接口:

int pthread_mutexattr_init (pthread_mutexattr_t *attr); // 初始化互斥量属性为默认属性
int pthread_mutexattr_destroy (pthread_mutexattr_t *attr);// 销毁互斥量属性/* Get the process-shared flag of the mutex attribute ATTR.  */
int pthread_mutexattr_getpshared (const pthread_mutexattr_t * attr,int *pshared);/* Set the process-shared flag of the mutex attribute ATTR.  */
int pthread_mutexattr_setpshared (pthread_mutexattr_t *attr, int shared)

attrpshared 属性表示用这个属性对象创建的互斥锁的作用域,它的取值可以是 PTHREAD_PROCESS_PRIVATEPTHREAD_PROCESS_SHARED

  • PTHREAD_PROCESS_PRIVATE:默认属性,只有和创建这个互斥锁的线程在同一个进程中的线程才能访问这个互斥锁;
  • PTHREAD_PROCESS_SHARED:所创建的互斥锁将被保存在共享内存中,可以被多个进程中的线程共享。

互斥锁类型:

  • PTHREAD_MUTEX_NORMAL;
  • PTHREAD_MUTEX_ERRORCHECK;
  • PTHREAD_MUTEX_RECURSIVE;
  • PTHREAD_MUTEX_DEFAULT。

在这里插入图片描述

🎄五、总结

本文介绍了Linux系统下,多线程编程常用的互斥量,先是介绍了需要互斥量的原因,然后介绍互斥量的使用,并给出使用例子。

下面是使用互斥量对第二小节例子进行修改后的代码:

// 08_mutex_test.c
// gcc 08_mutex_test.c -lpthread
#include <stdio.h>
#include <pthread.h>
#include <sys/syscall.h>
int g_Count = 0;
pthread_mutex_t g_mutex;
void *func(void *arg)
{int i=0;for(i=0; i<10000000; i++){pthread_mutex_lock(&g_mutex);g_Count++;pthread_mutex_unlock(&g_mutex);}return NULL;
}int main()
{pthread_mutex_init(&g_mutex, NULL);// 创建4个线程pthread_t threadId[4];int i=0;for(i=0; i<4; i++){pthread_create(&threadId[i], NULL, func, NULL);}for(i=0; i<4; i++){pthread_join(threadId[i],NULL);printf("join threadId=%lx\n",threadId[i]);}printf("g_Count=%d\n",g_Count);pthread_mutex_destroy(&g_mutex);return 0;
}

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

参考:
《linux_多线程》
《Linux环境编程:从应用到内核》

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

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

相关文章

【verilog】 reg与寄存器的关系

一、前言 在Verilog中经常用reg定义具有数据寄存功能的单元&#xff0c;但在verilog的使用中&#xff0c;并不代表其一定就是寄存单元&#xff0c;reg还能进行组合逻辑描述&#xff0c;并且在一些场景下&#xff0c;只能使用reg来申明变量。 二、reg型变量生成组合逻辑 在Ve…

在网上打印资料多少钱一张

随着互联网的普及和线上服务的完善&#xff0c;越来越多的人选择在网上打印资料。这种方式不仅方便快捷&#xff0c;而且通常价格更为透明和实惠。那么&#xff0c;在网上打印资料到底多少钱一张呢&#xff1f;这主要取决于您选择的打印平台、纸张规格、打印质量以及打印数量等…

书生·浦语大模型全链路开源体系-第3课

书生浦语大模型全链路开源体系-第3课 书生浦语大模型全链路开源体系-第3课相关资源RAG 概述在 InternLM Studio 上部署茴香豆技术助手环境配置配置基础环境下载基础文件下载安装茴香豆 使用茴香豆搭建 RAG 助手修改配置文件 创建知识库运行茴香豆知识助手 在茴香豆 Web 版中创建…

Zookeeper集群+消息队列Kafka

一. Zookeeper 集群的相关知识 1. zookeeper的概念 ZooKeeper 是一个分布式的&#xff0c;开放源码的分布式应用程序协调服务&#xff0c;是Google的Chubby一个开源的实现&#xff0c;是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件&#xff0c;提供的…

【七 (1)指标体系建设-构建高效的故障管理指标体系】

目录 文章导航一、故障概述1、故障&#xff1a;2、故障管理&#xff1a; 二、指标体系概述1、指标2、指标体系 三、指标体系构建难点1、管理视角2、业务视角3、技术视角 四、指标体系构建原则1、与战略目标对齐2、综合和平衡3、数据可获得性4、可操作性5、具体和可衡量6、参与和…

【数据库】数据库应用系统生命周期

目录 1.为什么提出”软件工程“的思想&#xff1f; 2.为什么提出”瀑布模型“&#xff1f;缺点是什么&#xff1f; 3.为什么提出”快速原型模型“&#xff1f; 4.为什么提出”螺旋模型“&#xff1f; 5.关于数据库的英文缩写。 6.模型设计中的3条设计主线&#xff1a;数…

基于Springboot+Vue+mysql仓库管理系统仓库进销存管理系统

博主介绍&#xff1a; 大家好&#xff0c;本人精通Java、Python、C#、C、C编程语言&#xff0c;同时也熟练掌握微信小程序、Php和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我有丰富的成品Java、Python、C#毕设项目经验&#xff0c;能够为学生提供各类…

mybash---打造自己的命令解释器

目前我们Linux的系统默认的命令解释器是bash; 命令解释器&#xff08;也称为命令行解释器或shell&#xff09;是计算机操作系统中的一个重要组件&#xff0c;它负责接收用户输入的命令&#xff0c;并解释和执行这些命令。其实命令解释器就是解析命令,执行命令,输出反馈; 1.命令…

linux 云计算平台基本环境(知识准备篇)

为了更多的了解云计算平台&#xff0c;结合云计算和linux的知识写了一篇云计算的介绍和汇总。 文章目录 前言1. centos的软件管理1.1 yum软件包管理1.1.1 yum命令语法&#xff1a;1.1.2 安装软件包的步骤1.1.3 yum源 2. 主机名管理与域名解析3. centos的防火墙管理4. openstack…

docker-compose yaml指定具体容器网桥ip网段subnet;docker创建即指定subnet;docker取消自启动

1、docker-compose yaml指定具体容器网桥ip网段subnet docker-compose 启动yaml有时可能的容器网段与宿主机的ip冲突导致宿主机上不了网&#xff0c;这时候可以更改yaml指定subnet 宿主机内网一般是192**&#xff0c;这时候容器可以指定172* version: 3.9 services:coredns:…

CentOS如何使用Docker部署Plik服务并实现公网访问本地设备上传下载文件

文章目录 1. Docker部署Plik2. 本地访问Plik3. Linux安装Cpolar4. 配置Plik公网地址5. 远程访问Plik6. 固定Plik公网地址7. 固定地址访问Plik 本文介绍如何使用Linux docker方式快速安装Plik并且结合Cpolar内网穿透工具实现远程访问&#xff0c;实现随时随地在任意设备上传或者…

IP代理池:隐私保护的得力助手与强化策略

目录 前言 1. IP代理池的原理 2. IP代理池的实现 3. 强化策略 总结 代码示例&#xff08;Python&#xff09; 前言 随着互联网的快速发展&#xff0c;越来越多的网络应用和服务涌现出来&#xff0c;我们的个人隐私也面临着越来越大的威胁。在这个数字化时代&#xff0c;…