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

目录

概述

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系统编程手册》

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

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

相关文章

UnicodeDecodeError: ‘gbk‘和Error: Command ‘pip install ‘pycocotools>=2.0

今天重新弄YOLOv5的时候发现不能用了&#xff0c;刚开始给我报这个错误 subprocess.CalledProcessError: Command ‘pip install ‘pycocotools&#xff1e;2.0‘‘ returned non-zero exit statu 说这个包安装不了 根据他的指令pip install ‘pycocotools&#xff1e;2.0这个根…

FPGA的时钟资源

目录 简介 Clock Region详解 MRCC和SRCC的区别 BUFGs 时钟资源总结 简介 7系列FPGA的时钟结构图&#xff1a; Clock Region&#xff1a;时钟区域&#xff0c;下图中有6个时钟区域&#xff0c;用不同的颜色加以区分出来 Clock Backbone&#xff1a;从名字也能看出来&#x…

【数据结构】万字长文图解+代码实现AVL树

目录 一、概念 二、图解 1.图解插入 2.图解右单旋 3.图解左单旋 4.图解右左双旋 5.图解左右双旋 6.验证是否是AVL树 三、代码实现 一、概念 AVL树是一种高度平衡的二叉搜索树&#xff0c;得名于其发明者的名字&#xff08;G. M. Adelson-Velskii和E. M. Landis&#xff0…

ELK-介绍及Elasticsearch集群搭建

ELK是三个开源软件的缩写&#xff0c;分别为Elasticsearch、Logstash、kibana它们都是开源软件。后来新增了一个FileBeat&#xff0c;它是一个轻量及的日志收集处理工具&#xff0c;因为Logstash由java程序开发&#xff0c;比较消耗内存资源&#xff0c;后来将Logstash使用go语…

微信小程序如何实现下拉刷新

1.首先在你需要实现下拉刷新页面的json文件中写入"enablePullDownRefresh": true。 2.在js文件的onPullDownRefresh() 事件中实现下拉刷新。 实现代码 onPullDownRefresh() {console.log(开始下拉刷新)wx.showNavigationBarLoading()//在标题栏中显示加载图标this.d…

一个将图片转3D的开源项目TripoSR

TripoSR AI是StabilityAI联合发布的图生3D模型&#xff0c;TripoSR是一个快速的3D物体重建模型。TripoSR能够在不到一秒钟的时间内从单张图片生成高质量的3D模型。TripoSR模型的特点是能够快速处理输入&#xff0c;在 NVIDIA A100 GPU 上不到 0.5 秒的时间内生成高质量的 3D 模…

用于无人机路径规划的增强型粒子群优化

摘要&#xff1a; 近年来&#xff0c;元启发式算法被广泛用于解决多目标优化问题&#xff08;MOOPs&#xff09;。粒子群优化&#xff08;PSO&#xff09;由于其在解决MOOPs方面的有效性&#xff0c;在这些算法中得到了普及。然而&#xff0c;PSO的性能高度依赖于其探索和利用能…

解锁安卓开发利器:深度探析ADB【安卓开发】

引言 在安卓开发与维护过程中&#xff0c;我们经常会遇到一些限制&#xff0c;比如无法直接访问某些系统功能&#xff0c;或者在某些定制系统中 受到限制 。为了解决这些问题&#xff0c;我们需要一种有效的工具来管理和调试安卓设备&#xff0c;而这时候ADB&#xff08;Andro…

离线数仓(五)【数据仓库建模】

前言 今天开始正式数据仓库的内容了, 前面我们把生产数据 , 数据上传到 HDFS , Kafka 的通道都已经搭建完毕了, 数据也就正式进入数据仓库了, 解下来的数仓建模是重中之重 , 是将来吃饭的家伙 ! 以及 Hive SQL 必须熟练到像喝水一样 ! 第1章 数据仓库概述 1.1 数据仓库概念 数…

【打工日常】使用docker部署个人实时在线文档协助编辑器

一、Etherpad介绍 Etherpad是一个高度可定制的开源在线编辑器&#xff0c;提供真正实时的协作编辑。放在自己的服务器里面&#xff0c;可以更大程度的保护自己工作的隐私&#xff0c;并且Etherpad允许您实时协作编辑文档&#xff0c;就像在浏览器中运行的实时多人编辑器一样这样…

Linux网络基础2之http

这里是ky233的主页&#xff0c;欢迎光临~https://blog.csdn.net/ky233?typeblog 目录 一、认识URL 1.认识URL 2.urlencode和urldecode 二、HTTP协议格式 1.快速构建http请求和相应的报文格式 三、http demo 1.GET和POST 2.HTTP的状态码 3.http的特征 4.HTTP常见Head…

ARTS Week 20

Algorithm 本周的算法题为 1222. 可以攻击国王的皇后 在一个 下标从 0 开始 的 8 x 8 棋盘上&#xff0c;可能有多个黑皇后和一个白国王。 给你一个二维整数数组 queens&#xff0c;其中 queens[i] [xQueeni, yQueeni] 表示第 i 个黑皇后在棋盘上的位置。还给你一个长度为 2 的…

VARMA(Vector Auto Regressive Moving Average) in Time Series Modelling

what is VARMA? ARIMA是针对单一变量进行建模的方法,当我们需要进行多变量时序建模时,需要使用VAR and VMA and VARMA模型。 VAR:Vector Auto-Regressive,a generalization of the auto-regressive model for multivariate time series where the time series is station…

高吞吐SFTP连接池设计方案

背景 在现代的数据驱动环境中&#xff0c;安全文件传输协议&#xff08;SFTP&#xff09;扮演着至关重要的角色&#xff0c;它提供了一种安全、可靠的文件传输方式。我们目前项目是一个大型数据集成平台&#xff0c;跟上下游有很多文件对接是通过SFTP协议&#xff0c;当需要处…

如何修复advapi32.dll丢失无法启动程序的问题

如果你在运行Windows程序时遇到了“advapi32.dll丢失无法启动程序”的错误消息&#xff0c;那么这意味着你的计算机缺少这个DLL文件。在本文中&#xff0c;我们将提供一些解决方案&#xff0c;帮助你解决这个问题并恢复计算机的正常运行。 一.advapi32.dll丢失电脑的提示 关于…

C语言编译成库文件的要求

keil编译成库文件 在Keil中&#xff0c;将C语言源文件编译成库文件通常需要进行以下步骤&#xff1a; 创建一个新的Keil项目&#xff0c;并将所需的C语言源文件添加到该项目中。 在项目设置中配置编译选项&#xff0c;确保生成的目标文件符合库文件的标准格式。 编译项目&…

React Three Fiber快速入门

https://threejs-journey.com/lessons/what-are-react-and-react-three-fiber#学习笔记 1.基础知识 resize 填充模版 构建第一个场景 we didn’t have to create a scenewe didn’t have to create the webglrenderthe scene is being rendered on each framethe default…

C++字符串操作【超详细】

零.前言 本文将重点围绕C的字符串来展开描述。 其中&#xff0c;对于C/C中字符串的一些区别也做出了回答&#xff0c;并对于C的&#xff08;string库&#xff09;进行了讲解&#xff0c;最后我们给出字符串的不同表达形式。 开发环境&#xff1a; VS2022 一.字符串常量跟字…

leetcode 热题 100_相交链表

题解一&#xff1a; 哈希表&#xff1a;两链表出现的第一个相同的值就是相交节点&#xff0c;因此我们先用哈希记录链表A所有出现过的值&#xff0c;再遍历链表B查找哈希表&#xff0c;找出第一个相同的值即为结果。 import java.util.HashSet;public class Solution {public …

如何使用Hexo搭建个人博客

文章目录 如何使用Hexo搭建个人博客环境搭建连接 Github创建 Github Pages 仓库本地安装 Hexo 博客程序安装 HexoHexo 初始化和本地预览 部署 Hexo 到 GitHub Pages开始使用发布文章网站设置更换主题常用命令 插件安装解决成功上传github但是web不更新不想上传文章处理方式链接…