linux多线程编程使用互斥量的原理分析和应用实例

news2025/2/27 13:04:43

目录

概述

1 保护对共享变量的访问:互斥量

1.1 认识互斥量

1.2 互斥锁API

1.2.1 互斥锁初始化函数

1.2.2 互斥锁函数

1.2.3 互斥锁变体函数

1.3 互斥锁使用方法

1.4 互斥锁死锁

2 互斥量的应用介绍

2.1 创建与销毁

2.1.1 创建互斥量

2.1.2 销毁互斥量

2.2 加锁和解锁

2.2.1 加锁

2.2.2 解锁

4 使用互斥量的案例

4.1 程序功能介绍

4.2 代码实现

4.3 验证

5 参考文献


概述

本文详细介绍互斥量的相关知识,并介绍和互斥量相关的函数接口,并使用具体的实例来介绍这些接口的使用方法。还编写一个案例,以说明互斥量在实际工程应用中的实用技巧。

1 保护对共享变量的访问:互斥量

1.1 认识互斥量

互斥量(Mutex), 又称为互斥锁, 是一种用来保护临界区的特殊变量, 它可以处于锁定(locked) 状态, 也可以处于解锁(unlocked) 状态:如果互斥锁是锁定的, 就是某个特定的线程正持有这个互斥锁;如果没有线程持有这个互斥锁,那么这个互斥锁就处于解锁状态。每个互斥锁内部有一个线程等待队列,用来保存等待该互斥锁的线程。当互斥锁处于解锁状态时, 如果某个线程试图获取这个互斥锁, 那么这个线程就可以得到这个互斥锁而不会阻塞;当互斥锁处于锁定状态时, 如果某个线程试图获取这个互斥锁, 那么这个线程将阻塞在互斥锁的等待队列内。互斥量是最简单也是最有效的线程同步机制。程序可以用它来保护临界区,以获得对排它性资源的访问权。另外,互斥量只能被短时间地持有,使用完临界资源后应立即释放锁。互斥量的总结如下:

1)互斥量的作用:确保同一时刻仅有一个线程可以访问共享资源

2)互斥量的状态:已锁定(lock)和未锁定(unlock)

3)任何一段时间,只有一个线程可以锁定该互斥量

4)一旦线程锁定互斥量,该线程称为该互斥量的所有者,只有所有者才能解锁

1.2 互斥锁API

1.2.1 互斥锁初始化函数

互斥锁初始化函数声明:

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

参数介绍

参数描述
mutexmutex 是一个 pthread_mutex_t 类型指针, 指向需要进行初始化操作的互斥锁对象
attr参数 attr 是一个 pthread_mutexattr_t 类型指针,指向一个 pthread_mutexattr_t 类型对象,该对象用<于定义互斥锁的属性,若将参数 attr 设置为 NULL,则表示将互斥锁的属性设置为默认值,在这种情况下其实就等价于 PTHREAD_MUTEX_INITIALIZER 这种方式初始化,而不同之处在于使用宏不进行错误检查。

1.2.2 互斥锁函数

互斥锁初始化之后,处于一个未锁定状态,调用函数 pthread_mutex_lock()可以对互斥锁加锁、获取互斥锁,而调用函数 pthread_mutex_unlock()可以对互斥锁解锁、释放互斥锁。

互斥锁函数声明:

#include <pthread.h>
​
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

1.2.3 互斥锁变体函数

当互斥锁已经被其它线程锁住时,调用 pthread_mutex_lock()函数会被阻塞,直到互斥锁解锁;如果线程不希望被阻塞,可以使用 pthread_mutex_trylock()函数;

调用 pthread_mutex_trylock()函数尝试对互斥锁进行加锁,如果互斥锁处于未锁住状态,那么调用 pthread_mutex_trylock()将会锁住互斥锁并立马返回,如果互斥锁已经被其它线程锁住,调用 pthread_mutex_trylock()加锁失败,但不会阻塞,而是返回错误码 EBUSY。

互斥锁变体函数声明:

#include <pthread.h>
​
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_timedlock(pthread_mutex_t *mutex);

1.3 互斥锁使用方法

1)调用 pthread_mutex_lock()函数对互斥锁进行上锁,如果互斥锁处于未锁定状态,则此次调用会上锁成 功,函数调用将立马返回;

2)如果互斥锁此时已经被其它线程锁定了,那么调用 pthread_mutex_lock()会一直阻塞,直到该互斥锁被解锁,到那时,调用将锁定互斥锁并返回。

3)调用 pthread_mutex_unlock()函数将已经处于锁定状态的互斥锁进行解锁。

4)以下行为均属错误:

  • 对处于未锁定状态的互斥锁进行解锁操作;

  • 解锁由其它线程锁定的互斥锁。

5)如 果 有 多 个 线 程 处 于 阻 塞 状 态 等 待 互 斥 锁 被 解 锁 , 当 互 斥 锁 被 当 前 锁 定 它 的 线 程 调 用 pthread_mutex_unlock()函数解锁后,这些等待着的线程都会有机会对互斥锁上锁,但无法判断究竟哪个线程会获得这个机会。

1.4 互斥锁死锁

当超过一个线程加锁同一组互斥量,就可能发生死锁。

要避免此类死锁的问题,最简单的方式就是定义互斥锁的层级关系,当多个线程对一组互斥锁操作时,总是应该按照相同的顺序对该组互斥锁进行锁定。譬如在上述场景中,如果两个线程总是先锁定 mutex1 在锁mutex2,死锁就不会出现。有时,互斥锁之间的层级关系逻辑不够清晰,即使是这样,依然可以设计出所有线程都必须遵循的强制层级顺序 。

2 互斥量的应用介绍

2.1 创建与销毁

2.1.1 创建互斥量

pthreads 使用 pthread_mutex_t 类型的变量来表示互斥量,同时在使用互斥量进行同步前 需要先对它进行初始化,可以用静态或动态的方式对互斥量进行初始化。

(1)静态初始化 对于静态分配的 pthread_mutex_t 变量来说,只要将 PTHREAD_MUTEX_INITIALIZER赋给变量就行了。

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

(2)动态初始化 对于动态分配或者不使用默认属性的互斥变量来说,需要调用 pthread_mutex_int()函数来执行初始化工作。 pthread_mutex_int()函数原型如下:

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

一个创建互斥量的实例:

int error;
pthread_mutex_t mylock;
​
if (error = pthread_mutex_init(&mylock, NULL))
    fprintf(stderr, "Failed to initialize mylock : %s\n", strerror(error));

参数 mutex : 一个指向要初始化的互斥量的指针;

参数 attr : 传递 NULL 来初始化一个带有默认属性的互斥量,否则就要用类似于线程属性对象所使用的方法,先创建互斥量属性对象,再用该属性对象来创建互斥量。

函数成功返回 0,否则返回一个非 0 的错误码.

错误码列表如下:

错误码出错描述
EAGAIN系统缺乏初始化互斥量所需的非内存资源
ENOMEM系统缺乏初始化互斥量所需的内存资源
EPERM调用程序没有适当的优先级

建议

静态初始化程序通常比调用 pthread_mutex_init 更有效,而且在任何线程开始执行之前,确保变量被初始化一次。

2.1.2 销毁互斥量

销毁互斥量使用 pthread_mutex_destroy()函数,原型如下:

int pthread_mutex_destroy(pthread_mutex_t *mutex);

一个销毁互斥量的实例:

int error;
pthread_mutex_t mylock;
​
if (error = pthread_mutex_destroy(&mylock))
    fprintf(stderr, "Failed to destroy mylock : %s\n", strerror(error));

2.2 加锁和解锁

2.2.1 加锁

加锁: 线程试图锁定互斥量的过程。

pthreads 中有两个试图锁定互斥量的函数,其分别如下:

函数名功能介绍
pthread_mutex_lock()会一直阻塞到互斥量可用为止
pthread_mutex_trylock()尝试加锁, 通常会立即返回

2.2.2 解锁

解锁是线程将互斥量由锁定状态变为解锁状态 ,一个加锁解锁的实例:

void function()
{
    pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;
​
    pthread_mutex_lock(&mylock);
    
    //临界区代码
    
    pthread_mutex_unlock(&mylock);
}

4 使用互斥量的案例

4.1 程序功能介绍

使用互斥量来保证多线程同时输出顺序的例子,互斥量能保证只有获取资源的线程打印完, 别的线程才能打印, 从而避免了打印乱序的问题。

4.2 代码实现

创建一个test_thread.c文件,然后编写如下代码:

/***************************************************************
Copyright  2024-2029. All rights reserved.
文件名     :  test_thread.c
作者       : tangmingfei2013@126.com
版本       : V1.0
描述       : pthread API test
其他       : 无
日志       : 初版V1.0 2024/03/04

***************************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <linux/fs.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <time.h>
#include <pthread.h>

pthread_t tid[2];

pthread_mutex_t lock;
 
 
 void* thread_forMutex(void *arg)
 {
     int id = (long)arg;
     int i = 0;
     
     pthread_mutex_lock(&lock);           // 使用互斥量保护临界区 
     printf("hread %d started\n", id);
     
     for (i = 0; i < 5; i++) {
         printf("hread %d printing\n", id);
         usleep(10);
     }
     
     printf("hread %d finished\n", id);
     pthread_mutex_unlock(&lock);
     
     return NULL;
 }

int main(void)
{
    long i = 0;
    int err;
    
    if (pthread_mutex_init(&lock, NULL) != 0) // 动态初始化互斥量 
    {
        printf("\n Mutex init failed\n");
        return 1;
    }
    
    while(i < 2){
        err = pthread_create(&(tid[i]), NULL, &thread_forMutex, (void*)i);
        if (err != 0){
            printf("Can't create thread :[%s]", strerror(err));
        }
        i++;
    }
    
    pthread_join(tid[0], NULL);
    pthread_join(tid[1], NULL);
    
    pthread_mutex_destroy(&lock);
    
    return 0;
}

4.3 验证

编译代码,然后在板卡中运行。可以看到 thread -1 先获取互斥锁并进行打印, thread -1 打印完成后, thread -0才开始打印。

5 参考文献

  1. 《现代操作系统》

  2. 《linux/unix系统编程手册》

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

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

相关文章

Python图像处理:1.插值、频域变换与对比度增强

一、几何变换 7.图像的插值 (1)原理介绍 下面对比三种插值方法&#xff0c;分别是最近邻插值法、双线性插值法、卷积插值法&#xff0c;三种方法的前提和特点、优缺点、适用场景如下&#xff1a; 最近邻插值&#xff08;Nearest Neighbor Interpolation&#xff09;&#xf…

简单认识Linux

今天带大家简单认识一下Linux&#xff0c;它和我们日常用的Windows有什么不同呢&#xff1f; Linux介绍 Linux内核&发行版 Linux内核版本 内核(kernel)是系统的心脏&#xff0c;是运行程序和管理像磁盘和打印机等硬件设备的核心程序&#xff0c;它提供了一个在裸设备与…

线上应用部署了两台load为1四核服务器

线上应用部署了两台服务器。 项目发布后&#xff0c;我对线上服务器的性能进行了跟踪&#xff0c;发现一台负载为3&#xff0c;另一台负载为1&#xff0c;其中一台四核服务器已经快到瓶颈了&#xff0c;所以我们紧急排查原因。 1、使用TOP命令查看占用CPU较大的负载和进程&…

鸿蒙OpenHarmony HDF 驱动开发

目录 序一、概述二、HDF驱动框架三、驱动程序四、驱动配置坚持就有收获 序 最近忙于适配OpenHarmonyOS LiteOS-M 平台&#xff0c;已经成功实践适配平台GD32F407、STM32F407、STM32G474板卡&#xff0c;LiteOS适配已经算是有实际经验了。 但是&#xff0c;鸿蒙代码学习进度慢下…

Kotlin dist downloading failed

现象&#xff1a; 在使用AndroidStudio编写Flutter项目时总是在工具的右下角提示错误信息 该问题通常在刚刚打开AndroidStudio时报出&#xff0c;但可以正常编译和运行flutter项目即Android项目 分析&#xff1a;Flutter项目组认为这是AndroidStudio工具平台本身的问题非Flut…

AI智能应用百科立即落地实操课

该课程旨在教授学员如何将AI智能应用于实际场景。通过深入的案例研究和实操练习&#xff0c;学员将学会应用机器学习、自然语言处理等技术&#xff0c;快速解决现实问题。课程强调实际操作&#xff0c;帮助学员快速运用AI技术解决工作中的挑战。 课程大小&#xff1a;3.3G 课…

pytorch安装记录

pytorch安装记录 1 安装anconda2 安装pycharm3 安装显卡驱动4 根据显卡驱动版本下载CUDA5 cudnn安装6 根据CUDA版本安装pytorch7 pytorch卸载 1 安装anconda 下载地址: https://www.anaconda.com/download#downloads 验证是否安装成功&#xff1a;打开cmd, 输入 conda 验证环…

复盘-excel

excel-选列没有用&#xff0c;选小标题才可以 将簇状柱形图放置在一个新表上##### excel: 添加数据模型时&#xff0c;要通过套用表格格式与外部断开连接 透视分析2010年人数未解决(第四套&#xff09; 通过日期显示星期几 判断星期几 因为前面已经通过星期六&#xff0c…

贪心算法(蓝桥杯 C++ 题目 代表 注解)

介绍&#xff1a; 贪心算法&#xff08;Greedy Algorithm&#xff09;是一种在每一步选择中都采取当前状态下最好或最优&#xff08;即最有利&#xff09;的选择&#xff0c;从而希望最终能够得到全局最好或最优的结果的算法。它通常用来解决一些最优化问题&#xff0c;如最小生…

sentinel prometheus指标收集及资源规则正则表达式实现

sentinel 支持 prometheus 收集指标 实现原理 在 sentinel-extension 模块下&#xff0c;新增 sentinel-prometheus-metric-exporter 模块。依赖Prometheus 提供的 simpleclient 和 simpleclient_httpserver 来实现 exporter。 依赖 simpleclient 主要是为了实现自定义Collect…

Chrome中如何导出和导入书签

导出书签 如下图所示&#xff1a; 右上角三点->书签和清单->书签管理器->右上角三点->导出书签 然后你选择保存地址即可。打开后如下&#xff1a; 导入书签 如下图所示&#xff1a; 右上角三点->书签和清单->导入书签和设置->选择以前导出的书签&…

贪吃蛇(C语言实现)

贪食蛇&#xff08;也叫贪吃蛇&#xff09;是一款经典的小游戏。 —————————————————————— 本博客实现使用C语言在Windows环境的控制台中模拟实现贪吃蛇小游戏。 实行的基本功能&#xff1a; • 贪吃蛇地图的绘制 • 蛇吃食物的功能&#xff08;上、…

Pytorch学习 day08(最大池化层、非线性激活层、正则化层、循环层、Transformer层、线性层、Dropout层)

最大池化层 最大池化&#xff0c;也叫上采样&#xff0c;是池化核在输入图像上不断移动&#xff0c;并取对应区域中的最大值&#xff0c;目的是&#xff1a;在保留输入特征的同时&#xff0c;减小输入数据量&#xff0c;加快训练。参数设置如下&#xff1a; kernel_size&#…

四、DMSP/OLS等夜间灯光数据贫困地区识别——相对误差相关折线图制作

一、前言 前文对于MPI和灯光指数拟合、误差分析&#xff0c;本文重点介绍地理加权分析&#xff0c;但是在此之前给大家介绍一下专业表格制作&#xff0c;其实专业的软件有很多像Orgin、棱镜等&#xff0c;到我们熟知的Excel&#xff0c;其实各有千秋&#xff0c;Excel入手容易…

【漏洞复现】锐捷网络NBR700G 信息泄露

0x01 产品简介 锐捷网络NBR700G路由器是锐捷网络股份有限公司的一款无线路由设备。 0x02 漏洞概述 锐捷网络NBR700G路由器存在信息漏洞。未授权的攻击者可以通过该漏洞获取敏感信息。 0x03 测绘语句 fofa&#xff1a;body"系统负荷过高&#xff0c;导致网络拥塞&…

H12-821_146

146.某IS-IS的组网图如图所示&#xff0c;根据图中内容分析&#xff0c;以下描述中错误的是哪一项? A.R1设备Level-1的LSDB只包含本区域的路由信息 B.R2和R1形成Leve1-1邻接关系&#xff0c;R2和R4形成Leve1-2邻接关系 C.R1可以通过R2或者R3访问R4 D.R6设备Level-2的LSDB只包含…

会声会影是什么软件?视频剪辑软件对比 会声会影和premiere哪个好 会声会影2024 会声会影下载安装

视频编辑软件已经成为了现代社会中不可或缺的一部分&#xff0c;特别是在新媒体时代&#xff0c;视频制作越来越受到人们的欢迎。而在众多的视频编辑软件中&#xff0c;会声会影和PR是两个非常受欢迎的软件。那么&#xff0c;会声会影和PR哪个更好呢&#xff1f;本文将从多个角…

网络编程套接字(3)——Java数据报套接字(UDP协议)

目录 一、Java数据报套接字通信模型 二、UDP数据报套接字编程 1、DatagramSocket &#xff08;1&#xff09;DatagramSocket构造方法 &#xff08;2&#xff09;DatagramSocket方法 2、DatagramPacket &#xff08;1&#xff09;DatagramPacket构造方法 &#xff08;2&…

探索React中的类组件和函数组件

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

Netty Review - 探究Netty服务端主程序无异常退出的背后机制

文章目录 概述故障场景尝试改进问题分析铺垫&#xff1a; Daemon线程Netty服务端启动源码分析逻辑分析 如何避免Netty服务端意外退出最佳实践 概述 在使用Netty进行服务端程序开发时&#xff0c;初学者可能会遇到各种问题&#xff0c;其中之一就是服务端意外退出的问题。这种问…