信号量与互斥锁的一些理解

2023-05-16

一直对信号量和互斥锁只有一个模糊的认识,今天特别学习了,总结一下


一、从作用上来讲

互斥锁是用在多线程多任务互斥的

信号量用于线程的同步


二、从原理上讲

线程互斥锁pthread_mutex_t的实现原理:

Linux下,信号量和线程互斥锁的实现都是通过futex系统调用。

Futex 由一块能够被多个进程共享的内存空间(一个对齐后的整型变量)组成;这个整型变量的值能够通过汇编语言调用CPU提供的原子操作指令来增加或减少,并且一个进程可以等待直到那个值变成正数。Futex的操作几乎全部在应用程序空间完成;只有当操作结果不一致从而需要仲裁时,才需要进入操作系统内核空间执行。这种机制允许使用 futex的锁定原语有非常高的执行效率:由于绝大多数的操作并不需要在多个进程之间进行仲裁,所以绝大多数操作都可以在应用程序空间执行,而不需要使用(相对高代价的)内核系统调用。

源码实现

pthread_mutex_lock: 

atomic_dec(pthread_mutex_t.value); 

if(pthread_mutex_t.value!=0) 

   futex(WAIT) 

else

   success 

 

pthread_mutex_unlock: 

atomic_inc(pthread_mutex_t.value); 

if(pthread_mutex_t.value!=1) 

   futex(WAKEUP) 

else

   success


信号量sem_t的实现原理(直接从glibc/nptl/DESIGN-sem.txt中摘的):

显示代码打印

sem_wait(sem_t*sem) 

  for (;;) { 

 

    if(atomic_decrement_if_positive(sem->count)) 

      break; 

 

    futex_wait(&sem->count, 0) 

  } 

 

sem_post(sem_t*sem) 

  n = atomic_increment(sem->count); 

  // Pass the new value of sem->count 

  futex_wake(&sem->count, n + 1); 

}

从源码看出

1.pthread_mutex_lock 操作一定会将互斥锁减一,因此互斥锁可以为负

sem_wait 操作会判断sem if positive,也就是是否大于0,不大于0futex_wait(&sem->count, 0) 等待,因此信号量一定>=0

2.互斥锁只有在1->0的过程是不会锁住的,其他情况均futex(WAIT),也就是在临界资源处锁住

信号量只有在大于0的时候是不会锁住,其他情况都会被锁

3.pthread_mutex_unlock会将互斥锁加1sem_post也一样,会将信号量加1

因此在错误的使用信号量,多次调用mutex_unlocksem_post都会导致锁或信号量值多加1,就起不到锁的效果了

 

三、使用

1.互斥锁的使用

pthread_mutex_t mutex;                  //定义锁
pthread_mutex_init(&mutex, NULL);   //
默认属性初始化锁
pthread_mutex_lock(&mutex);           //
申请锁

pthread_mutex_unlock(&mutex);       //释放锁

设置锁的属性

函数pthread_mutexattr_setpshared和函数pthread_mutexattr_settype用来设置互斥锁属性。
前一个函数设置属性pshared,它有两个取值,PTHREAD_PROCESS_PRIVATEPTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步,后者用于同步本进程的不同线程。在上面的例子中,我们使用的是默认属性PTHREAD_PROCESS_ PRIVATE。后者用来设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMALPTHREAD_MUTEX_ERRORCHECKPTHREAD_MUTEX_RECURSIVEPTHREAD _MUTEX_DEFAULT。它们分别定义了不同的上所、解锁机制,一般情况下,选用最后一个默认属性。用法如下:

pthread_mutexattr_tmutexAttr;

pthread_mutexattr_init(&mutexAttr);

pthread_mutexattr_setpshared(&mutexAttr,PTHREAD_PROCESS_PRIVATE);

pthread_mutexattr_settype(&mutexAttr,PTHREAD_MUTEX_DEFAULT);

pthread_mutex_init(&mutex,&mutexAttr);

pthread_mutex_lock阻塞线程直到pthread_mutex_unlock被调用。
还有另外两个函数可以用:intpthread_mutex_trylock (pthread_mutex_t *__mutex)该函数立即返回,根据返回状态判断加锁是否成功。intpthread_mutex_timedlock (pthread_mutex_t *mutex, struct timespec*__restrict),该函数超时返回。
互斥锁主要用来互斥访问临界区。用于线程的互斥。

 

2.信号量的使用

信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当公共资源增加时,调用函数sem_post()增加信号量。

只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和函数

pthread_mutex_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本。

 

下面我们逐个介绍和信号量有关的一些函数,它们都在头文件/usr/include/semaphore.h中定义。

  信号量的数据类型为结构sem_t,它本质上是一个长整型的数。函数sem_init()用来初始化一个信号量。它的原型为:

  extern int sem_init __P ((sem_t *__sem, int __pshared,unsigned int __value));

  sem为指向信号量结构的一个指针;pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;value给出了信号量的初始值。

  函数sem_post( sem_t *sem )用来增加信号量的值。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的。

  函数sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少。函数sem_trywait ( sem_t *sem )是函数sem_wait()的非阻塞版本,它直接将信号量sem的值减一。

  函数sem_destroy(sem_t *sem)用来释放信号量sem。


3.补充, 条件变量

简单的说,条件变量能使线程wait,另一个线程在满足条件的时候发signal,然后所有处于wait的线程按照wait的先后得到唤醒(或者一起唤醒)

条件变量用来阻塞线程等待某个事件的发生,并且当等待的事件发生时,阻塞线程会被通知。
互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线承间的同步。
条件变量的结构为pthread_cond_t
最简单的使用例子:

pthread_cond_tcond;

pthread_cond_init(&cond,NULL);

pthread_cond_wait(&cond,&mutex);

    ...

pthread_cond_signal(&cond);

调用pthread_cond_wait的线程会按调用顺序进入等待队列,当有一个信号产生时,先进入阻塞队列的线程先得到唤醒。条件变量在某些方面看起来差不多。
正如上面所说的,条件变量弥补了互斥锁的不足。
接着上面列举的生产者、消费者例子中,我们这里增加消费者(并假设缓冲区可以放任意多条信息),比如有3个消费者线程,如果都使用互斥锁,那么三个线程都要不断的去查看缓冲区是否有消息,有就取走。这无疑是资源的极大浪费。如果我们用条件变量,三个消费者线程都可以放心的“睡觉”,缓冲区有消息,消费者会收到通知,那时起来收消息就好了。

#include<stdio.h>

#include<stdlib.h>

#include<pthread.h>

#include<sys/time.h>

 

staticchar buff[50];

pthread_mutex_tmutex;

pthread_mutex_tcond_mutex;

pthread_cond_tcond;

 

void consumeItem(char*buff)

{

    printf("consumer item\n");

}

void produceItem(char*buff)

{

    printf("produce item\n");

}

void *consumer(void*param)

{

    int t = *(int*)param;

    while (1)

    {

        pthread_cond_wait(&cond,&cond_mutex);

        pthread_mutex_lock(&mutex);

        printf("%d: ", t);

        consumeItem(buff);

        pthread_mutex_unlock(&mutex);

    }

    return NULL;

}

 

void *producer(void*param)

{

    while (1)

    {

        pthread_mutex_lock(&mutex);

        produceItem(buff);       

        pthread_mutex_unlock(&mutex);

        pthread_cond_signal(&cond);

        sleep(1);

    }

    return NULL;

}

 

int main()

{

    pthread_t tid_c, tid_p, tid_c2, tid_c3;

    void *retval;

    pthread_mutex_init(&mutex, NULL);

    pthread_mutex_init(&cond_mutex, NULL);

   

    pthread_cond_t cond;

    pthread_cond_init(&cond, NULL);

   

   

    int p[3] = {1,2, 3}; //用来区分是哪个线程

    pthread_create(&tid_p, NULL, producer,NULL);

    pthread_create(&tid_c, NULL, consumer,&p[0]);

    pthread_create(&tid_c2, NULL, consumer,&p[1]);

    pthread_create(&tid_c3, NULL, consumer,&p[2]);

 

   

    pthread_join(tid_p, &retval);

    pthread_join(tid_c, &retval);

    pthread_join(tid_c2, &retval);

    pthread_join(tid_c3, &retval);

       

    return 0;

}

要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出,例如缓冲区到底有多少条信息等。
与条件变量有关的其它函数有:
/* 销毁条件变量cond */
int pthread_cond_destroy (pthread_cond_t *cond);  
/* 唤醒所有等待条件变量cond的线程 */
int pthread_cond_broadcast (pthread_cond_t *cond);

/* 等待条件变量直到超时 */
int pthread_cond_timedwait (pthread_cond_t *cond,
pthread_mutex_t *mutex,
const struct timespec *abstime);
/* 初始化条件变量属性 attr*/
int pthread_condattr_init (pthread_condattr_t *attr);
/* 销毁条件变量属性 */
int pthread_condattr_destroy (pthread_condattr_t *attr)

/* 读取条件变量属性attr的进程共享标志 */
int pthread_condattr_getpshared (const pthread_condattr_t *
attr,
int *pshared);

/* 更新条件变量属性attr的进程共享标志 */
int pthread_condattr_setpshared (pthread_condattr_t *attr,
int pshared);

一般来说,条件变量被用来进行线程间的同步。

 

 

 

 


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

信号量与互斥锁的一些理解 的相关文章

  • echarts基本使用(入门)

    echarts基本使用 1 在项目中安装echarts npm install echarts span class token operator span save 2 在项目中引入echarts span class token key
  • AdGuard Home 安装使用教程

    原文链接 xff1a 使用 Envoy 和 AdGuard Home 阻挡烦人的广告 通常我们使用网络时 xff0c 宽带运营商会为我们分配一个 DNS 服务器 这个 DNS 通常是最快的 xff0c 距离最近的服务器 xff0c 但会有很
  • 信号量和互斥量(锁)的区别

    信号量 xff1a 那是多线程同步用的 xff0c 一个线程完成了某一个动作就通过信号告诉别的线程 xff0c 别的线程再进行某些动作 互斥量 xff1a 这是多线程互斥用的 xff0c 比如说 xff0c 一个线程占用了某一个资源 xff
  • C++的struct和class的区别

    目录 1 C 43 43 的struct和class的区别1 1 成员访问范围的差异1 struct2 class 1 1 继承关系访问范围的差异1 struct struct2 struct class3 struct private c
  • Android 8.0 以后前台服务的启动方式

    前言 在官方文档 Android 8 0之后的行为变更 中有这样一段话 xff1a Android 8 0 有一项复杂功能 xff1b 系统不允许后台应用创建后台服务 因此 xff0c Android 8 0 引入了一种全新的方法 xff0
  • PID控制算法的c语言实现 附录2 直流电机PWM调速系统中控制电压非线性研究

    附录2 直流电机PWM调速系统中控制电压非线性研究 引言 由于线性放大驱动方式效率和散热问题严重 xff0c 目前绝大多数直流电动机采用开关驱动方式 开关驱动方式是半导体功率器件工作在开关状态 xff0c 通过脉宽调制PWM控制电动机电枢电
  • Android中dispatchDraw分析

    Android中dispatchDraw分析 View中 xff1a public void draw Canvas canvas 1 Draw the background 绘制背景 2 If necessary save the can
  • CMake命令之execute_process

    用途 xff1a 执行一个或多个子进程 execute process COMMAND lt cmd1 gt args1 COMMAND lt cmd2 gt args2 WORKING DIRECTORY lt directory gt
  • 利用反射 Mirror 快速的获取/打印枚举值

    在 Swift 中 xff0c 枚举是一等类型 xff0c 可以给其添加计算属性 xff0c 实例方法 xff0c 构造函数 xff0c 遵循协议 xff0c 另外 xff0c 还可以定义枚举来存储任意类型的关联值 这些特性可以让枚举适用于
  • linux 应用层编程之内核链表list的使用

    linux内核提供了一个经典通用的双向循环链表list的实现 xff0c 任何模块都可以借助该接口实现自己的内部循环链表 因为是通用的 xff0c 可以直接移植到用户态中使用 xff0c 下面介绍相关的接口与一个简单操作例子 xff0c 包
  • EFR32修改开发板VCOM串口波特率的方法

    Silabs官方开发板上的Jlink CDC串口 xff08 VCOM xff09 默认的波特率是115200 xff0c 在普通的终端软件 xff08 如 串口调试助手 xff09 里设置别的波特率是不起作用的 要让它支持其他的波特率 x
  • 用 Latex 生成英文论文中的 算法步骤/伪代码 并插入 word 中

    准备工作 xff1a 1 先安装 CTEX https mirrors tuna tsinghua edu cn ctex legacy 2 9 2 下载 algorithm2e 包 xff08 里面有详细的使用说明文档 xff09 htt
  • Docker 大势已去,Podman 万岁

    前言 郑重声明 xff1a 本文不是 Podman 的入门篇 xff0c 入门请阅读这篇文章 xff1a 再见 Docker xff0c 是时候拥抱下一代容器工具了 Podman 原来是 CRI O 项目的一部分 xff0c 后来被分离成一
  • 生产者消费者问题--练习题目

    10 os考研题目 改题目中p0 xff0c p1两个进程可以互斥的进入临界区 xff0c 会出现饥饿现象 xff08 答案给的是D xff0c 但是自我认为可以出现饥饿现象 xff09 互斥的访问 xff1a p0 Flag 0 61 T
  • PromQL的简单使用

    PromQL的简单使用 一 背景二 PromQL的数据类型三 字面量1 字符串字面量2 浮点数字面量 四 时间序列选择器1 即时向量选择器1 组成部分2 指标名称和匹配器的组合3 匹配器 2 区间向量选择器1 时间格式 3 偏移量修改器 五
  • 一步一步在平衡车上实现卡尔曼滤波

    这是一个翻译版本 xff0c 其中的一些公式 xff0c 符号太多 xff0c 我就不一个一个去上传 xff0c 大家可以参考下面网址去对照着看 A practical approach to Kalman filter and how t
  • 浮点数的二进制表示(IEEE 754标准)

    浮点数是我们在程序里常用的数据类型 xff0c 它在内存中到底是怎么样的形式存在 xff0c 是我了解之前是觉得好神奇 xff0c 以此记录 xff0c 作为学习笔记 现代计算机中 xff0c 一般都以IEEE 754标准存储浮点数 xff
  • vscode如何打开settings.json

    解决方案步骤 xff1a 打开vscode编辑器 xff0c 本文演示的vscode是英文版 点击左下角齿轮状的图标 在弹出的菜单中选择 Settings Settings点击后 xff0c 会出现一个设置窗口 在Settings窗口中点击
  • Failed to execute ‘createObjectURL‘ on ‘URL‘ Overload resolution failed

    vue使用二进制流下载文件 xff0c 使用 link href 61 window URL createObjectURL blob 报错 xff1a Failed to execute createObjectURL on URL Ov

随机推荐

  • openwrt安装docker并启动

    在软件包中下载docker和dockerd 也可以自行下载ipk文件安装 安装成功后启动xshell连上openwrt 执行 etc init d dockerd 启动docker的daemon服务 若要dockerd自启动则执行 ln s
  • Mysql环境变量配置

    一 mysql的环境变量配置步骤 1 1 在桌面选择 计算机 的图标 xff0c 右键 gt 属性 gt 点击 高级系统设置 gt 点击 环境变量 2 2 新建MYSQL HOME变量 xff0c 并将值设置为C Program Files
  • MySQL安装配置教程(超级详细)

    一 下载MySQL Mysql官网下载地址 xff1a MySQL Download MySQL Installer Archived Versions 1 选择要安装的版本 xff0c 本篇文章选择的是5 7 31版本 xff0c 点击D
  • mysql字符切割的四种方式

    1 从左开始截取字符串 left xff08 str length xff09 说明 xff1a left xff08 被截取字段 xff0c 截取长度 xff09 select left 39 如果暴力不是为了杀戮 xff0c 那将变得毫
  • Podman 使用指南

    原文链接 xff1a Podman 使用指南 Podman 原来是 CRI O 项目的一部分 xff0c 后来被分离成一个单独的项目叫 libpod Podman 的使用体验和 Docker 类似 xff0c 不同的是 Podman 没有
  • ClickHouse查询语句详解

    ClickHouse查询语句兼容大部分SQL语法 xff0c 并且进行了更加丰富的扩展 xff0c 查询语句模板如下 xff1a WITH expr list subquery SELECT DISTINCT ON column1 colu
  • Mysql和Redis如何保证数据一致性

    文章目录 前言一 先更新数据库 xff0c 再更新redis二 先更新redis xff0c 在更新数据库三 先更新数据库 xff0c 再删除redis四 先删除redis xff0c 再更新数据库总结 前言 如何保证数据库和缓存双写一致
  • SQL——左连接(Left join)、右连接(Right join)、内连接(Inner join)

    文章目录 前言 一 概念 二 例子 总结 前言 最近在做SQL相关的练习 发现以前那么自信的SQL放久了不碰也变得棘手起来 特别是这一块表之间的内外连接 所以这篇是关于这个内外连接的整理 一 概念 首先还是介绍一下这三个的定义 1 Left
  • UCOSII之项目实战总结

    电子IT行业博大精深 xff0c 没有人能够用笔记本天天记录自己所学的知识 xff0c 于是乎 xff0c 撰写博客就成了每个 IT民工 的专长 再者 xff0c 写一篇博客 xff0c 其意义与不但记录了自己所需的知识 xff0c 更提高
  • 明白了一句话:“加速度信号对高频敏感,位移信号对低频敏感”

    以前听别人说这些 xff0c 然后记住了 但是一直不大理解 最近在调试IEPE传感器 xff0c 正好要算位移 速度 加速度 对于相同的速度 xff0c 频率越高 xff0c 加速度值就越大 因为从公式就能看出来 xff0c 对于固定频率的
  • ubuntu 16.04使用IntelRealSense D435i调用realsense ROS包时,报symbol lookup error和undefined symbol错误的解决办法

    在ubuntu 16 04使用IntelRealSense D435i调用realsense ROS包时 xff0c 运行 roscore roslaunch realsense2 camera rs rgbd launch 出现错误 xf
  • Android浪潮

    Google的Android手机就要席卷世界了 xff01 IT技术的发展常常太出人意料 xff0c 我也想不太清楚Google的Android平台究竟吸引人在哪里 xff0c 但我相信Android会很快改变手机平台的格局 新的形势会出人
  • 卡尔曼滤波相关介绍及优缺点

    1 卡尔曼滤波算法为什么会叫滤波算法 xff1f 以一维卡尔曼滤波为例 xff0c 如果我们单纯的相信测量的信号 xff0c 那么这个信号是包含噪声的 xff0c 是很毛糙的 xff0c 但是当我们运行卡尔曼滤波算法去做估计 xff0c 我
  • STM32单片机(五)-寄存器地址理解和控制LED闪烁

    芯片 xff1a stm32f103zet6 1 存储单元一般应具有存储数据和读写数据的功能 一般以8位二进制作为一个存储单元 也就是一个字节 每个单元有一个地址 是一个整数编码 可以表示为二进制整数 2 stm32是32位单片机 xff0
  • 跨平台构建 Docker 镜像新姿势,x86、arm 一把梭

    点击 34 阅读原文 34 可以获得更好的阅读体验 在工作和生活中 xff0c 我们可能经常需要将某个程序跑在不同的 CPU 架构上 xff0c 比如让某些不可描述的软件运行在树莓派或嵌入式路由器设备上 特别是 Docker 席卷全球之后
  • 正点原子STM32F4笔记

    使用寄存器操作 xff0c 不错的博客 xff1a https blog csdn net w471176877 article category 1230060 https blog csdn net w471176877 article
  • JAVA中this用法小结

    我知道很多朋友都和我一样 xff1a 在 JAVA 程序中似乎经常见到 this xff0c 自己也偶尔用到它 xff0c 但是到底 this 该怎么用 xff0c 却心中无数 xff01 很多人一提起它 xff0c 就说 当前对象 xff
  • Linux Platform总线+SPI总线分析

    2015 07 1 11 20 本文以MPC8308 powerpc架构 xff0c HX软件包为依据 xff0c 详细内容可参考源码 CPU e300c3MPC8308 400MHz BOARD Freescale MPC8308ERDB
  • ubuntu下SD卡分区与挂载

    本来只是想借SD卡来做一个OK6410的升级 但笔记本上只装了ubuntu xff0c 一开始是可以识别sd卡的 xff0c 但按照网上的教程不小心将 dev sdb1删除了 导致ubuntu不能识别sd卡了 记录一下解决过程 1 sd的设
  • 信号量与互斥锁的一些理解

    一直对信号量和互斥锁只有一个模糊的认识 xff0c 今天特别学习了 xff0c 总结一下 一 从作用上来讲 互斥锁是用在多线程多任务互斥的 信号量用于线程的同步 二 从原理上讲 线程互斥锁 pthread mutex t 的实现原理 xff