[nptl][rwlock]pthread rwlock原理分析

概念:

In computer science, a readers–writer (RW) or shared-exclusive lock (also known as a multiple readers/single-writer lock or multi-reader lock) is a synchronization primitive that solves one of the readers–writers problems. An RW lock allows concurrent access for read-only operations, while write operations require exclusive access. This means that multiple threads can read the data in parallel but an exclusive lock is needed for writing or modifying data. When a writer is writing the data, all other writers or readers will be blocked until the writer is finished writing. A common use might be to control access to a data structure in memory that cannot be updated atomically and is invalid (and should not be read by another thread) until the update is complete.

摘自Wikipedia的一段(原文https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock)。

简而言之,就是一次只有一个writer可以占有写模式的读写锁,但是多个reader可用同时占有读模式的读写锁。

分析:

代码选自glibc-2.23。以x86为例。

数据结构定义在glibc-2.23/sysdeps/x86/bits/pthreadtypes.h

typedef union
{
  struct
  {
    int __lock;
    unsigned int __nr_readers;
    unsigned int __readers_wakeup;
    unsigned int __writer_wakeup;
    unsigned int __nr_readers_queued;
    unsigned int __nr_writers_queued;
    int __writer;
    int __shared;
    signed char __rwelision;
    unsigned long int __pad2;
    /* FLAGS must stay at this position in the structure to maintain
       binary compatibility.  */
    unsigned int __flags;
# define __PTHREAD_RWLOCK_INT_FLAGS_SHARED    1
  } __data;
  char __size[__SIZEOF_PTHREAD_RWLOCK_T];
  long int __align;
} pthread_rwlock_t;

其中,

__nr_readers字段表示当前有多少个reader正在使用lock;

__readers_wakeup字段表示需要被唤醒的reader的个数;

__writer_wakeup字段表示需要被唤醒的writer的个数;

__nr_readers_queued字段表示正在排队等待锁的reader个数;

__nr_writers_queued字段表示正在排队等待锁的writer的个数;

__writer字段表示正在使用锁的writer的线程id,用来防止死锁;

read锁函数实现在glibc-2.23/nptl/pthread_rwlock_rdlock.c

/* 函数实现的关键部分,部分代码省略 */
int __pthread_rwlock_rdlock (pthread_rwlock_t *rwlock)
{
  /* Make sure we are alone.  */
  lll_lock (rwlock->__data.__lock, rwlock->__data.__shared);
  /* Get the rwlock if there is no writer...  */
  if (rwlock->__data.__writer == 0 //当前不是writer占用lock
      && (!rwlock->__data.__nr_writers_queued //当前的等待lock的writer个数是0
      || PTHREAD_RWLOCK_PREFER_READER_P (rwlock))) //或者iattr->lockkind不是PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP类型,默认是PTHREAD_RWLOCK_DEFAULT_NP
    {
      /* Increment the reader counter.  Avoid overflow.  */
      if (__glibc_unlikely (++rwlock->__data.__nr_readers == 0))//这里的代码实现很谨慎,防止溢出
    {
      /* Overflow on number of readers.     */
      --rwlock->__data.__nr_readers;
      result = EAGAIN;
    }
      else
    {
      LIBC_PROBE (rdlock_acquire_read, 1, rwlock);
      if (rwlock->__data.__nr_readers == 1
          && rwlock->__data.__nr_readers_queued > 0
          && rwlock->__data.__nr_writers_queued > 0)
        {//优先唤醒正在排队中的reader
          ++rwlock->__data.__readers_wakeup;
          wake = true;
        }
    }
      /* We are done, free the lock.  */
      lll_unlock (rwlock->__data.__lock, rwlock->__data.__shared);
      if (wake)
    futex_wake (&rwlock->__data.__readers_wakeup, INT_MAX, futex_shared);
      return result;//所以,如果不是writer占用锁或者lockkind是PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP的情况下,reader都可以获取到锁。
    }
  return __pthread_rwlock_rdlock_slow (rwlock);//
}
static int __attribute__((noinline))
__pthread_rwlock_rdlock_slow (pthread_rwlock_t *rwlock)
{
  /* Lock is taken in caller.  */
  while (1)
    {
      if (__builtin_expect (rwlock->__data.__writer
                == THREAD_GETMEM (THREAD_SELF, tid), 0))
    {//死锁检查无处不在
      result = EDEADLK;
      break;
    }
      /* Remember that we are a reader.  */
      if (__glibc_unlikely (++rwlock->__data.__nr_readers_queued == 0))
    {//如果获取不到锁,就会增加__nr_readers_queued,但是防止溢出
      /* Overflow on number of queued readers.  */
      --rwlock->__data.__nr_readers_queued;
      result = EAGAIN;
      break;
    }
      int waitval = rwlock->__data.__readers_wakeup;
      /* Free the lock.  */
      lll_unlock (rwlock->__data.__lock, rwlock->__data.__shared);

      /* Wait for the writer to finish.  We do not check the return value
     because we decide how to continue based on the state of the rwlock.  */
      futex_wait_simple (&rwlock->__data.__readers_wakeup, waitval,
             futex_shared);//这里是使用futex wait在等待唤醒,即排队中

      /* Get the lock.  */
      lll_lock (rwlock->__data.__lock, rwlock->__data.__shared);
      --rwlock->__data.__nr_readers_queued;//代码跑到这里,那么已经被唤醒了,所以要减少__nr_readers_queued
      /* Get the rwlock if there is no writer...  */
      if (rwlock->__data.__writer == 0
      /* ...and if either no writer is waiting or we prefer readers.  */
      && (!rwlock->__data.__nr_writers_queued
          || PTHREAD_RWLOCK_PREFER_READER_P (rwlock)))
    {//哪怕reader被唤醒,依然要检查writer,因为rwlock原则上就不能让writer占用锁的时候,reader可以拿到锁
      /* Increment the reader counter.  Avoid overflow.  */
      if (__glibc_unlikely (++rwlock->__data.__nr_readers == 0))
        {
          /* Overflow on number of readers.     */
          --rwlock->__data.__nr_readers;
          result = EAGAIN;
        }
      else
        {
          LIBC_PROBE (rdlock_acquire_read, 1, rwlock);
          /* See pthread_rwlock_rdlock.  */
          if (rwlock->__data.__nr_readers == 1
          && rwlock->__data.__nr_readers_queued > 0
          && rwlock->__data.__nr_writers_queued > 0)
        {
          ++rwlock->__data.__readers_wakeup;
          wake = true;
        }
        }
      break;
    }
    }

  /* We are done, free the lock.  */
  lll_unlock (rwlock->__data.__lock, rwlock->__data.__shared);
  if (wake)
    futex_wake (&rwlock->__data.__readers_wakeup, INT_MAX, futex_shared);
  return result;
}
write锁函数实现在glibc-2.23/nptl/pthread_rwlock_wrlock.c
总体来说,实现起来就简单一些,只要不是有其他writer占用锁并且没有reader占用锁,就可以获取到。否则等待锁释放,唤醒自己。
unlock锁函数实现在glibc-2.23/nptl/pthread_rwlock_unlock.c
/* 函数实现的关键部分,部分代码省略 */
int __pthread_rwlock_unlock (pthread_rwlock_t *rwlock)
{
  if (rwlock->__data.__writer)
    rwlock->__data.__writer = 0;//如果是writer占用,则释放writer。
  else
    --rwlock->__data.__nr_readers;//不是writer占用,则reader占用个数减少
  if (rwlock->__data.__nr_readers == 0)
    {//这里要么是writer释放的锁,要么是最后一个reader释放锁
      if (rwlock->__data.__nr_writers_queued)
    {//这里的逻辑是先判断是否有writer在排队,并优先唤醒writer
      ++rwlock->__data.__writer_wakeup;
      lll_unlock (rwlock->__data.__lock, rwlock->__data.__shared);
      futex_wake (&rwlock->__data.__writer_wakeup, 1, futex_shared);
      return 0;
    }
      else if (rwlock->__data.__nr_readers_queued)
    {//再判断是否有reader在排队,有的话,就唤醒。
      ++rwlock->__data.__readers_wakeup;
      lll_unlock (rwlock->__data.__lock, rwlock->__data.__shared);
      futex_wake (&rwlock->__data.__readers_wakeup, INT_MAX,
              futex_shared);
      return 0;
    }
    }
  lll_unlock (rwlock->__data.__lock, rwlock->__data.__shared);
  return 0;
}

总结:

1,总体大原则还是:一次只有一个writer可以占有写模式的读写锁,但是多个reader可用同时占有读模式的读写锁。

2,如果有reader在占用锁,那么其它的很多reader继续来使用,如果reader量真的很大的时候,那么writer可能会饿死,一直得不到执行。这里可以在rdlock中看到。

3,如果有writer在占用锁,后面又来了很多writer,那么reader会一直得不到执行,reader可能会饿死。这里在unlock中可以看到。

4,使用场景应该是读多写少的情况下,并且尽可能让锁的占用时间短一些。

原文发布于微信公众号 - AlwaysGeek(gh_d0972b1eeb60)

原文发表时间:2017-01-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏xingoo, 一个梦想做发明家的程序员

[Logstash-input-redis] 使用详解

redis插件的完整配置 input { redis { batch_count => 1 #返回的事件数量,此属性仅在list模式下起...

438100
来自专栏北京马哥教育

Linux 中命令链接操作符,让你的代码更简洁!

Linux命令中链接的意思是,通过操作符的行为将几个命令组合执行。Linux中的链接命令,有些像你在shell中写短小的shell脚本,并直接在终端中执行。链接...

8920
来自专栏JetpropelledSnake

RESTful源码笔记之RESTful Framework的Mixins小结

本篇对drf中的mixins进行简要的分析总结。 Mixins在drf中主要配合viewset共同使用,实现http方法与mixins的相关类与方法进行关联。

7910
来自专栏小勇DW3

使用Redis作为分布式锁的一些注意点

最简单的方法是使用setnx命令。key是锁的唯一标识,按业务来决定命名,value为当前线程的线程ID。

3.4K50
来自专栏cloudskyme

gsoap开发webservice

gSOAP编译工具提供了一个SOAP/XML 关于C/C++ 语言的实现,从而让C/C++语言开发web服务或客户端程序的工作变得轻松了很多。绝大多数的C++w...

34960
来自专栏好好学java的技术栈

“面试不败计划”:Java多线程和并发基础面试问答

多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一。在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应...

9120
来自专栏程序员同行者

django rest framework mixins小结

由上图可以看出这个类的一个逻辑,其中,perform_create( )对serializer直接进行save保存,当在一些情境下,我们需要对perform...

18730
来自专栏北京马哥教育

Ansible 详细用法说明(二)

例:获取某台主机的变量 ansible 10.1.6.68 -m setup ===================================== sc...

37850
来自专栏武军超python专栏

2018年8月25日多线程编程总结

PYTHON 本身也支持多任务处理,并且提供了如下的操作方式 多线程多任务处理机制   (比较常用) 多进程多任务处理机制   (不常用,大型项目开发或者系...

12440
来自专栏老马说编程

(22) 代码的组织机制 / 计算机程序的思维逻辑

使用任何语言进行编程都有一个类似的问题,那就是如何组织代码,具体来说,如何避免命名冲突?如何合理组织各种源文件?如何使用第三方库?各种代码和依赖库如何编译连接为...

209100

扫码关注云+社区

领取腾讯云代金券