关于互斥锁,条件变量的内核源码解析

一、解决问题和适用范围

主要是用来等待一个条件,这个条件可能需要另一个线程来满足这个条件。这个和我们平常适用的pthread_mutex_lock的最大不同在于后者保护的一般是一个代码段(也就是关键区),或者一个变量,但是由于一般来说这个变量的访问是在一个关键区中,所以可以认为是一个关键区。

但是对于条件变量,是需要的是一个事件,只有事件满足的时候才会执行后面的操作,此时就出现一个问题:如果不满足我们应该怎么办?如果如果使用简单信号量,可能另一方触发了这个条件,然后通过unlock来唤醒一个线程,但是此时经过多次唤醒其实这边根本没有等待,那么信号就可能丢失。如果这边一直的空等,那么对于CPU的利用率又非常大,所以就引发了条件等待的概念

二、网络上的例子代码

一下代码来自http://download.oracle.com/docs/cd/E19455-01/806-5257/6je9h032r/index.html
void producer(buffer_t *b, char item)  {      pthread_mutex_lock(&b->mutex);           while (b->occupied >= BSIZE)          pthread_cond_wait(&b->less, &b->mutex);        assert(b->occupied < BSIZE);        b->buf[b->nextin++] = item;        b->nextin %= BSIZE;      b->occupied++;        /* now: either b->occupied < BSIZE and b->nextin is the index         of the next empty slot in the buffer, or         b->occupied == BSIZE and b->nextin is the index of the         next (occupied) slot that will be emptied by a consumer         (such as b->nextin == b->nextout) */        pthread_cond_signal(&b->more);        pthread_mutex_unlock(&b->mutex);  }
char consumer(buffer_t *b)  {      char item;      pthread_mutex_lock(&b->mutex);      while(b->occupied <= 0)          pthread_cond_wait(&b->more, &b->mutex);        assert(b->occupied > 0);        item = b->buf[b->nextout++];      b->nextout %= BSIZE;      b->occupied--;        /* now: either b->occupied > 0 and b->nextout is the index         of the next occupied slot in the buffer, or         b->occupied == 0 and b->nextout is the index of the next         (empty) slot that will be filled by a producer (such as         b->nextout == b->nextin) */        pthread_cond_signal(&b->less);      pthread_mutex_unlock(&b->mutex);        return(item);  }
三、Glibc的实现
1、数据结构
/* Data structure for conditional variable handling.  The structure of
   the attribute type is not exposed on purpose.  */
typedef union
{
  struct
  {
    int __lock;保护多线程中cond结构本身的变量操作不会并发,例如对于total_seq进而wakup_seq的使用和递增操作
    unsigned int __futex;另一个线程和这个线程之间在条件点上同步的方式,也就是如果需要和其它线程同步的话,使用这个互斥锁替换pthread_cond_wait传入的互斥锁进行同步。
    __extension__ unsigned long long int __total_seq;这个表示在这个条件变量上有多少个线程在等待这个信号
    __extension__ unsigned long long int __wakeup_seq;已经在这个条件变量上执行了多少次唤醒操作。
    __extension__ unsigned long long int __woken_seq;这个条件变量中已经被真正唤醒的线程数目
    void *__mutex;保存pthread_cond_wait传入的互斥锁,需要保证pthread_cond_wait和pthread_cond_signal传入的值都是相同值
    unsigned int __nwaiters;表示这个cond结构现在还有多少个线程在使用,当有人在使用的时候,pthread_cond_destroy需要等待所有的操作完成
    unsigned int __broadcast_seq; 广播动作发生了多少次,也就是执行了多少次broadcast
  } __data;
  char __size[__SIZEOF_PTHREAD_COND_T];
  __extension__ long long int __align;
} pthread_cond_t;
2、lll_futex_wait的意义
      lll_futex_wait (&cond->__data.__futex, futex_val, pshared);
    lll_futex_wake (&cond->__data.__nwaiters, 1, pshared);
对于第一个wait,需要传入一个我们用户态判断时使用的futex值,也就是这里的第二个参数futex_val,这样内核会判断进行真正的wait挂起的时候这个地址的是不是还是这个值,如果不是这个wait失败。但是进行wakup的时候不需要传入判断值,可能是假设此时已经获得互斥锁,所以不会有其它线程来竞争了吧。
这个要和pthread_mutex_lock使用的0、1、2三值区分开来,因为这些都是C库规定的语义,内核对他们没有任何特殊要求和语义判断,所以用户态可以随意的改变这个变量的值
3、pthread_cond_wait的操作
int
__pthread_cond_wait (cond, mutex)
     pthread_cond_t *cond;
     pthread_mutex_t *mutex;
{
  struct _pthread_cleanup_buffer buffer;
  struct _condvar_cleanup_buffer cbuffer;
  int err;
  int pshared = (cond->__data.__mutex == (void *) ~0l)
    ? LLL_SHARED : LLL_PRIVATE;
  /* Make sure we are along.  */
  lll_lock (cond->__data.__lock, pshared);即将对cond结构的成员进行操作和判断,所以首先获得结构本身保护互斥锁
  /* Now we can release the mutex.  */
  err = __pthread_mutex_unlock_usercnt (mutex, 0);释放用户传入的互斥锁,此时另外一个执行pthread_cond_signal的线程可以通过pthread_mutex_lock执行可能的signal判断,但是我们还没有释放数据操作互斥锁,所以另一方执行pthread_cond_signal的时候依然可能会等待
  if (__builtin_expect (err, 0))
    {
      lll_unlock (cond->__data.__lock, pshared);
      return err;
    }
  /* We have one new user of the condvar.  */
  ++cond->__data.__total_seq;增加系统中所有需要执行的唤醒次数
  ++cond->__data.__futex;增加futex,主要是为了保证用户态数据一致性
  cond->__data.__nwaiters += 1 << COND_NWAITERS_SHIFT;增加cond结构的使用次数
  /* Remember the mutex we are using here.  If there is already a
     different address store this is a bad user bug.  Do not store
     anything for pshared condvars.  */
  if (cond->__data.__mutex != (void *) ~0l)
    cond->__data.__mutex = mutex;
  /* Prepare structure passed to cancellation handler.  */
  cbuffer.cond = cond;
  cbuffer.mutex = mutex;
  /* Before we block we enable cancellation.  Therefore we have to
     install a cancellation handler.  */
  __pthread_cleanup_push (&buffer, __condvar_cleanup, &cbuffer);注册撤销点
  /* The current values of the wakeup counter.  The "woken" counter
     must exceed this value.  */
  unsigned long long int val;
  unsigned long long int seq;
  val = seq = cond->__data.__wakeup_seq;
  /* Remember the broadcast counter.  */
  cbuffer.bc_seq = cond->__data.__broadcast_seq;
  do
    {
      unsigned int futex_val = cond->__data.__futex;
      /* Prepare to wait.  Release the condvar futex.  */
      lll_unlock (cond->__data.__lock, pshared);此处真正释放cond操作互斥锁,我们已经不再对其中的变量进行操作
      /* Enable asynchronous cancellation.  Required by the standard.  */
      cbuffer.oldtype = __pthread_enable_asynccancel ();
      /* Wait until woken by signal or broadcast.  */
      lll_futex_wait (&cond->__data.__futex, futex_val, pshared);等待在futex变量上,由于我们刚才保存了futex的原始值,所以如果在上面我们释放了data.lock之后另一个线程修改了这个变量的值,那么这里的lll_futex_wait将会返回失败,所以会继续进行下一轮的while循环,直到连个执行相同,说明我们做的判断时正确的
      /* Disable asynchronous cancellation.  */如果执行到这里,说明我们已经被signal唤醒
      __pthread_disable_asynccancel (cbuffer.oldtype);
      /* We are going to look at shared data again, so get the lock.  */
      lll_lock (cond->__data.__lock, pshared);访问变量,需要获得互斥锁
      /* If a broadcast happened, we are done.  */
      if (cbuffer.bc_seq != cond->__data.__broadcast_seq)
 goto bc_out;
      /* Check whether we are eligible for wakeup.  */
      val = cond->__data.__wakeup_seq;
    }
  while (val == seq || cond->__data.__woken_seq == val); 当val!=seq&&cond->data.wokenup!=val的时候可以进行唤醒,也就是另一个放修改了已经执行了唤醒的次数并且已经被唤醒的线程还有名额的时候
  /* Another thread woken up.  */
  ++cond->__data.__woken_seq;增加系统中已经被唤醒的线程的数目
 bc_out: broadcast跳转到这里
  cond->__data.__nwaiters -= 1 << COND_NWAITERS_SHIFT;
  /* If pthread_cond_destroy was called on this varaible already,
     notify the pthread_cond_destroy caller all waiters have left
     and it can be successfully destroyed.  */
  if (cond->__data.__total_seq == -1ULL
      && cond->__data.__nwaiters < (1 << COND_NWAITERS_SHIFT))
    lll_futex_wake (&cond->__data.__nwaiters, 1, pshared);
  /* We are done with the condvar.  */
  lll_unlock (cond->__data.__lock, pshared);
  /* The cancellation handling is back to normal, remove the handler.  */
  __pthread_cleanup_pop (&buffer, 0);
  /* Get the mutex before returning.  */
  return __pthread_mutex_cond_lock (mutex);再次获得mutex互斥锁,可能会睡眠,因为我们的这个释放是对上层透明的,而在进入函数的时候我们已经释放了这个互斥锁,所以此时还要进行一次获得操作,从而配对
}
4、pthread_cond_signal的操作
int
__pthread_cond_signal (cond)
     pthread_cond_t *cond;
{
  int pshared = (cond->__data.__mutex == (void *) ~0l)
  ? LLL_SHARED : LLL_PRIVATE;
  /* Make sure we are alone.  */
  lll_lock (cond->__data.__lock, pshared);
  /* Are there any waiters to be woken?  */
  if (cond->__data.__total_seq > cond->__data.__wakeup_seq)如果待唤醒次数比已经唤醒的次数多,那么此时就进行一个唤醒操作。
    {
      /* Yes.  Mark one of them as woken.  */
      ++cond->__data.__wakeup_seq;
      ++cond->__data.__futex;改变futex的值,这个值的具体意义并不重要,只是为了告诉另一方,这个值已经变化,如果另一方使用的是原始值,那么对futex的wait操作将会失败
      /* Wake one.  */
      if (! __builtin_expect (lll_futex_wake_unlock (&cond->__data.__futex, 1,
           1, &cond->__data.__lock,
           pshared), 0))
 return 0;
      lll_futex_wake (&cond->__data.__futex, 1, pshared);
    }
  /* We are done.  */
  lll_unlock (cond->__data.__lock, pshared);
  return 0;
}
5、__pthread_cond_broadcast
int
__pthread_cond_broadcast (cond)
     pthread_cond_t *cond;
{
  int pshared = (cond->__data.__mutex == (void *) ~0l)
  ? LLL_SHARED : LLL_PRIVATE;
  /* Make sure we are alone.  */
  lll_lock (cond->__data.__lock, pshared);
  /* Are there any waiters to be woken?  */
  if (cond->__data.__total_seq > cond->__data.__wakeup_seq)判断是否有等待唤醒的线程
    {
      /* Yes.  Mark them all as woken.  */
      cond->__data.__wakeup_seq = cond->__data.__total_seq;
      cond->__data.__woken_seq = cond->__data.__total_seq;
      cond->__data.__futex = (unsigned int) cond->__data.__total_seq * 2;
      int futex_val = cond->__data.__futex;
      /* Signal that a broadcast happened.  */
      ++cond->__data.__broadcast_seq;
      /* We are done.  */
      lll_unlock (cond->__data.__lock, pshared);
      /* Do not use requeue for pshared condvars.  */
      if (cond->__data.__mutex == (void *) ~0l)
 goto wake_all;
      /* Wake everybody.  */
      pthread_mutex_t *mut = (pthread_mutex_t *) cond->__data.__mutex;
      /* XXX: Kernel so far doesn't support requeue to PI futex.  */
      /* XXX: Kernel so far can only requeue to the same type of futex,
  in this case private (we don't requeue for pshared condvars).  */
      if (__builtin_expect (mut->__data.__kind
       & (PTHREAD_MUTEX_PRIO_INHERIT_NP
          | PTHREAD_MUTEX_PSHARED_BIT), 0))
 goto wake_all;
      /* lll_futex_requeue returns 0 for success and non-zero
  for errors.  */
      if (__builtin_expect (lll_futex_requeue (&cond->__data.__futex, 1,
            INT_MAX, &mut->__data.__lock,
            futex_val, LLL_PRIVATE), 0))把futex上的转移到data.lock中并唤醒,如果失败则直接唤醒而不转移
 {
   /* The requeue functionality is not available.  */
 wake_all:
   lll_futex_wake (&cond->__data.__futex, INT_MAX, pshared);这里的INT_MAX就是告诉内核唤醒所有在这个变量上等待的线程
 }
      /* That's all.  */
      return 0;
    }
  /* We are done.  */
  lll_unlock (cond->__data.__lock, pshared);
  return 0;
}

转载于:https://www.cnblogs.com/c-slmax/p/5853784.html

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

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

相关文章

【CASS精品教程】CASS 9.2绘制地籍图详细实验案例教程

文章目录 4.1 绘制地籍图4.1.1 生成平面图4.1.2 生成权属信息数据文件4.1.3 绘权属地籍图4.1.4 图形编辑4.3 绘制宗地图4.3.1 单块宗地4.3.2 批量处理4.4 绘制地籍表格4.4.1 界址点成果表4.4.2 界址点坐标表4.4.3 以街坊为单位界址点坐标表4.4.4 以街道为单位宗地面积汇总表4.4…

jquery.cookie.js 使用小结

先引入jquery&#xff0c;再引入&#xff1a;jquery.cookie.js添加一个"会话cookie"$.cookie(the_cookie, the_value);这里没有指明 cookie有效时间&#xff0c;所创建的cookie有效期默认到用户关闭浏览器为止&#xff0c;所以被称为 “会话cookie&#xff08;sessio…

Android开发 人民币符号(¥)显示不一致的问题

不小心踩了个坑&#xff0c;发现这个人民币符号在不同机器上显示不一致&#xff0c;有的显示一横&#xff0c;有的显示两横。 百度查了一下&#xff0c;找到好很多解决办法&#xff0c;改字体&#xff0c;用图片等等。 最后发现原来用的是全角的“&#xffe5;”的原因&#xf…

《iVX 高仿美团APP制作移动端完整项目》09 订单页制作

订单页制作比较简单&#xff0c;界面如下&#xff1a; 一、标题 首先创建一个页面&#xff0c;命名为订单页&#xff0c;并且给予背景色为黄色&#xff1a; 随后创建一个行&#xff0c;命名为主要并给予对应的基础属性&#xff1a; 接着给予这个主要行对应的上下内边距使其…

AutoDesk CAD 2014安装VBA Enabler图文教程(附AutoCAD_2014_VBA_Win_64bit下载)

在利用CASS将dwg地形图转为ArcGIS支持的Shapefile格式数据时,为了提高转换效率,通常我们会写一些VBA代码来实现,但是VBA不提供与AutoCAD OEM 安装介质,需要单独下载VBA环境。 Visual Basic for Applications (VBA) 引擎不再提供与 AutoCAD OEM 安装介质。 请联系您的 Auto…

C语言试题十八之根据以下攻势计算s,计算结果作为函数值返回;n通过形参传入。S=1+1/(1+2)+1/(1+2+3)+…….+1/(1+2+3+4+……+n)

📃个人主页:个人主页 🔥系列专栏:C语言试题200例目录 💬推荐一款刷算法、笔试、面经、拿大公司offer神器 👉 点击跳转进入网站 ✅作者简介:大家好,我是码莎拉蒂,CSDN博客专家(全站排名Top 50),阿里云博客专家、51CTO博客专家、华为云享专家 1、题目 编写函数f…

开源项目 英雄联盟 之WPF

WPF 英雄联盟作者&#xff1a;Devncore 组织 来自 韩国&#xff0c;首尔原文链接&#xff1a;https://github.com/devncore/leagueoflegends感谢分享者晨晞gg[1]&#xff1b;框架使用.NET6&#xff1b;C# 10.0;Visual Studio 2022;您可以了解如何正确实施 WPF 项目。描述了如何…

1055 最长等差数列

1055 最长等差数列基准时间限制&#xff1a;2 秒 空间限制&#xff1a;262144 KB N个不同的正整数&#xff0c;找出由这些数组成的最长的等差数列。 例如&#xff1a;1 3 5 6 8 9 10 12 13 14等差子数列包括(仅包括两项的不列举&#xff09;1 3 51 5 9 133 6 9 123 8 135 9 136…

Android studio 的快捷键 MAC 和Win版本

功能描述keymap对应名字MacWin/Linux提示错误解决方案Show Intention Actionsoption enteralt enterAS配置界面Preferencescommand ,controlaltS工程项目配置界面Project Structurecommand ;Control&#xff0b;AltShiftS快速构成代码Code Generatecommand Nalt insert代…

java中try 与catch的使用

(2011-10-08 17:08:43) 转载▼标签&#xff1a; 杂谈 分类&#xff1a; Javatry{//代码区}catch(Exception e){//异常处理}代码区如果有错误&#xff0c;就会返回所写异常的处理。 首先要清楚&#xff0c;如果没有try的话&#xff0c;出现异常会导致程序崩溃。而try则可以保证…

iVX 倒计时制作

需求&#xff1a;点击开始计时计时&#xff0c;并且开始计时按钮文本编程停止计时文本&#xff0c;点击记录事件可以记录当前时间并显示到下面的记录时间列中。 一、页面制作 首先创建一个相对应用项目&#xff0c;命名为计时器&#xff1a; 接着创建一个页面&#xff0c;设…

打造自己的装机U盘(二)

打造自己的装机U盘&#xff08;二&#xff09;二、 在PE U盘加入系统安装的GHOST镜像文件现在我们在上篇的PE U盘加入系统安装的GHOST镜像文件。这一步其实最简单,说白了就是搞一个GHOST文件复制、粘贴到u盘。系统安装的GHOST镜像文件从何而来&#xff1f;百度、谷歌输入“GHOS…

到底什么是国土空间规划?

文章目录 一、什么是国土空间规划?二、为什么要建立国土空间规划体系?三、国土空间规划的主要目标是什么?四、国土空间规划的编制要求是什么?五、国土空间规划体系由哪几部分组成?六、国土空间规划分哪几个层级?七、国土空间规划分哪几个类型?八、国土空间总体规划、详细…

Bootstrap入门(八)组件2:下拉菜单

Bootstrap入门&#xff08;八&#xff09;组件2&#xff1a;下拉菜单先引入本地的CSS文件和JS文件&#xff08;注&#xff1a;1.bootstrap是需要jQuery支持的。2.需要在<body>当中添加&#xff09;<link href"css/bootstrap.min.css" rel"stylesheet&q…

MAUI 迁移指南

前言为了能够让大家更好的理解全新的MAUI框架, 那么本次迁移指南主要给大家讲解从Xamarin.Forms升级到MAUI带来了哪些全新的变化, 下面将围绕以下几点给大家重点介绍。单个代码库演变启动配置演变统一资源管理依赖注入隐式using 指令Essentials合并全新命名空间您仅需要具备Xam…

ivx动效按钮 基础按钮制作 01

一、准备工作 首先创建一个相对定位应用&#xff1a; 接着创建一个页面&#xff1a; 随后我们切换一下屏幕&#xff0c;更改为 PC 端 web&#xff0c;因为手机移动端一般是没有鼠标悬浮事件的&#xff1a; 为了使按钮显示方便观察&#xff0c;我们设置水平和垂直对其为居中…

android中xml tools属性详解

第一部分 安卓开发中&#xff0c;在写布局代码的时候&#xff0c;ide可以看到布局的预览效果。但是有些效果则必须在运行之后才能看见&#xff0c;比如这种情况&#xff1a;TextView在xml中没有设置任何字符&#xff0c;而是在activity中设置了text。因此为了在ide中预览效果&a…

C语言试题十九之根据以下公式求p的值,结果由函数值带回。M与n为两个正整数,且要求m>n。 p=m!/n!(m-n)!

📃个人主页:个人主页 🔥系列专栏:C语言试题200例目录 💬推荐一款刷算法、笔试、面经、拿大公司offer神器 👉 点击跳转进入网站 ✅作者简介:大家好,我是码莎拉蒂,CSDN博客专家(全站排名Top 50),阿里云博客专家、51CTO博客专家、华为云享专家 1、题目 编写函数f…

Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析

在前面一篇文章Android系统匿名共享内存Ashmem&#xff08;Anonymous Shared Memory&#xff09;驱动程序源代码分析中&#xff0c;我们系统地介绍了Android系统匿名共享内存的实现原理&#xff0c;其中着重介绍了它是如何辅助内存管理系统来有效地管理内存的&#xff0c;在再前…

转帖-Linux学习(Find命令使用实例)

为什么80%的码农都做不了架构师&#xff1f;>>> find / -name httpd.conf find / -name access_log 2>/dev/null find /etc -name *srm* find / -amin -10 # 查找在系统中最后10分钟访问的文件 find / -atime -2 # 查找在系统中最后48小时访问的文件 find / -mm…