前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[nptl][rwlock]pthread rwlock原理分析

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

作者头像
皮振伟
发布2018-04-09 10:14:01
3.2K0
发布2018-04-09 10:14:01
举报
文章被收录于专栏:皮振伟的专栏皮振伟的专栏

概念:

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

代码语言:javascript
复制
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

代码语言:javascript
复制
/* 函数实现的关键部分,部分代码省略 */
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,使用场景应该是读多写少的情况下,并且尽可能让锁的占用时间短一些。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-01-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 AlwaysGeek 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档