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

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

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

相关文章

React组件(函数式组件,类式组件)

函数式组件 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>React Demo</title> <!-- 引…

【牛客】VL69 脉冲同步器(快到慢)

描述 sig_a 是 clka&#xff08;300M&#xff09;时钟域的一个单时钟脉冲信号&#xff08;高电平持续一个时钟clka周期&#xff09;&#xff0c;请设计脉冲同步电路&#xff0c;将sig_a信号同步到时钟域 clkb&#xff08;100M&#xff09;中&#xff0c;产生sig_b单时钟脉冲信号…

基于php的用户登录实现(v2版)(持续迭代)

目录 版本说明 数据库连接 登录页面&#xff1a;login.html 登录处理实现&#xff1a;login.php 用户欢迎页面&#xff1a;welcome.php 密码修改页面&#xff1a;change_password.html 修改执行&#xff1a;change_password.php 用户注册页面&#xff1a;register.html …

推荐算法中经典排序算法GBDT+LR

文章目录 逻辑回归模型逻辑回归对于特征处理的优势逻辑回归处理特征的步骤 GBDT算法GBDTLR算法GBDT LR简单代码实现 逻辑回归模型 逻辑回归&#xff08;LR,Logistic Regression&#xff09;是一种传统机器学习分类模型&#xff0c;也是一种比较重要的非线性回归模型&#xff0…

FPGA的时钟资源

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

【C语言】linux内核ip_local_out函数

一、讲解 这个函数 __ip_local_out 是 Linux 内核网络子系统中的函数&#xff0c;部分与本地出口的 IPv4 数据包发送相关。下面讲解这段代码的每一部分&#xff1a; 1. 函数声明 int __ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb)&#xff1a; -…

Hive安装教程-Hadoop集成Hive

文章目录 前言一、安装准备1. 安装条件2. 安装jdk3. 安装MySQL4. 安装Hadoop 二、安装Hive1. 下载并解压Hive2. 设置环境变量3. 修改配置文件3. 创建hive数据库4. 下载MySQL驱动5. 初始化hive数据库6. 进入Hive命令行界面7. 设置允许远程访问 总结 前言 本文将介绍安装和配置H…

Spring web开发(入门)

1、我们在执行程序时&#xff0c;运行的需要是这个界面 2、简单的web接口&#xff08;127.0.0.1表示本机IP&#xff09; package com.example.demo;import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestCont…

【sgExcelGrid】自定义组件:简单模拟Excel表格拖拽、选中单元格、横行、纵列、拖拽圈选等操作

特性&#xff1a; 可以自定义拖拽过表格可以点击某个表格&#xff0c;拖拽右下角小正方形进行任意方向选取单元格支持选中某一行、列支持监听selectedGrids、selectedDatas事件获取选中项的DOM对象和数据数组支持props自定义显示label字段别名 sgExcelGrid源码 <template&g…

SQLite3中的callback回调函数注意的细节

调用 sqlite3_exec(sqlite3*, const char *sql, sqlite_callback, void *data, char **errmsg)该例程提供了一个执行 SQL 命令的快捷方式&#xff0c; SQL 命令由 sql 参数提供&#xff0c;可以由多个 SQL 命令组成。 在这里&#xff0c; 第一个参数 sqlite3 是打开的数据库对…

HarmonyOS NEXT应用开发案例——列表编辑实现

介绍 本示例介绍用过使用ListItem组件属性swipeAction实现列表左滑编辑效果的功能。 该场景多用于待办事项管理、文件管理、备忘录的记录管理等。 效果图预览 使用说明&#xff1a; 点击添加按钮&#xff0c;选择需要添加的待办事项。长按待办事项&#xff0c;点击删除后&am…

C++——string模拟实现

前言&#xff1a;上篇文章我们对string类及其常用的接口方法的使用进行了分享&#xff0c;这篇文章将着重进行对这些常用的接口方法的内部细节进行分享和模拟实现。 目录 一.基础框架 二.遍历字符串 1.[]运算符重载 2.迭代器 3.范围for 三.常用方法 1.增加 2.删除 3.调…