用Python实现读写锁的示例代码


Posted in Python onNovember 05, 2018

起步

Python 提供的多线程模型中并没有提供读写锁,读写锁相对于单纯的互斥锁,适用性更高,可以多个线程同时占用读模式的读写锁,但是只能一个线程占用写模式的读写锁。

通俗点说就是当没有写锁时,就可以加读锁且任意线程可以同时加;而写锁只能有一个线程,且必须在没有读锁时才能加上。

简单的实现

import threading

class RWlock(object):
  def __init__(self):
    self._lock = threading.Lock()
    self._extra = threading.Lock()
    self.read_num = 0

  def read_acquire(self):
    with self._extra:
      self.read_num += 1
      if self.read_num == 1:
        self._lock.acquire()

  def read_release(self):
    with self._extra:
      self.read_num -= 1
      if self.read_num == 0:
        self._lock.release()

  def write_acquire(self):
    self._lock.acquire()

  def write_release(self):
    self._lock.release()

这是读写锁的一个简单的实现,self.read_num 用来保存获得读锁的线程数,这个属性属于临界区,对其操作也要加锁,所以这里需要一个保护内部数据的额外的锁 self._extra 。

但是这个锁是不公平的。理想情况下,线程获得所的机会应该是一样的,不管线程是读操作还是写操作。而从上述代码可以看到,读请求都会立即设置 self.read_num += 1,不管有没有获得锁,而写请求想要获得锁还得等待 read_num 为 0 。

所以这个就造成了只有锁没有被占用或者没有读请求时,可以获得写权限。我们应该想办法避免读模式锁长期占用。

读写锁的优先级

读写锁也有分 读优先 和 写优先。上面的代码就属于读优先。

如果要改成写优先,那就换成去记录写线程的引用计数,读和写在同时竞争时,可以让写线程增加写的计数,这样可使读线程的读锁一直获取不到, 因为读线程要先判断写的引用计数,若不为0,则等待其为 0,然后进行读。这部分代码不罗列了。

但这样显然不够灵活。我们不需要两个相似的读写锁类。我们希望重构我们代码,使它更强大。

改进

为了能够满足自定义优先级的读写锁,要记录等待的读写线程数,并且需要两个条件 threading.Condition 用来处理哪方优先的通知。计数引用可以扩大语义:正数:表示正在读操作的线程数,负数:表示正在写操作的线程数(最多-1)

在获取读操作时,先然后判断时候有等待的写线程,没有,进行读操作,有,则等待读的计数加 1 后等待 Condition 通知;等待读的计数减 1,计数引用加 1,继续读操作,若条件不成立,循环等待;

在获取写操作时,若锁没有被占用,引用计数减 1,若被占用,等待写线程数加 1,等待写条件 Condition 的通知。

读模式和写模式的释放都是一样,需要根据判断去通知对应的 Condition:

class RWLock(object):
  def __init__(self):
    self.lock = threading.Lock()
    self.rcond = threading.Condition(self.lock)
    self.wcond = threading.Condition(self.lock)
    self.read_waiter = 0  # 等待获取读锁的线程数
    self.write_waiter = 0  # 等待获取写锁的线程数
    self.state = 0     # 正数:表示正在读操作的线程数  负数:表示正在写操作的线程数(最多-1)
    self.owners = []    # 正在操作的线程id集合
    self.write_first = True # 默认写优先,False表示读优先

  def write_acquire(self, blocking=True):
    # 获取写锁只有当
    me = threading.get_ident()
    with self.lock:
      while not self._write_acquire(me):
        if not blocking:
          return False
        self.write_waiter += 1
        self.wcond.wait()
        self.write_waiter -= 1
    return True

  def _write_acquire(self, me):
    # 获取写锁只有当锁没人占用,或者当前线程已经占用
    if self.state == 0 or (self.state < 0 and me in self.owners):
      self.state -= 1
      self.owners.append(me)
      return True
    if self.state > 0 and me in self.owners:
      raise RuntimeError('cannot recursively wrlock a rdlocked lock')
    return False

  def read_acquire(self, blocking=True):
    me = threading.get_ident()
    with self.lock:
      while not self._read_acquire(me):
        if not blocking:
          return False
        self.read_waiter += 1
        self.rcond.wait()
        self.read_waiter -= 1
    return True

  def _read_acquire(self, me):
    if self.state < 0:
      # 如果锁被写锁占用
      return False

    if not self.write_waiter:
      ok = True
    else:
      ok = me in self.owners
    if ok or not self.write_first:
      self.state += 1
      self.owners.append(me)
      return True
    return False

  def unlock(self):
    me = threading.get_ident()
    with self.lock:
      try:
        self.owners.remove(me)
      except ValueError:
        raise RuntimeError('cannot release un-acquired lock')

      if self.state > 0:
        self.state -= 1
      else:
        self.state += 1
      if not self.state:
        if self.write_waiter and self.write_first:  # 如果有写操作在等待(默认写优先)
          self.wcond.notify()
        elif self.read_waiter:
          self.rcond.notify_all()
        elif self.write_waiter:
          self.wcond.notify()

  read_release = unlock
  write_release = unlock

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
简单介绍Python2.x版本中的cmp()方法的使用
May 20 Python
python处理二进制数据的方法
Jun 03 Python
django框架模板语言使用方法详解
Jul 18 Python
Django 权限认证(根据不同的用户,设置不同的显示和访问权限)
Jul 24 Python
Python 类,property属性(简化属性的操作),@property,property()用法示例
Oct 12 Python
python几种常用功能实现代码实例
Dec 25 Python
python 按钮点击关闭窗口的实现
Mar 04 Python
python:批量统计xml中各类目标的数量案例
Mar 10 Python
numpy库ndarray多维数组的维度变换方法(reshape、resize、swapaxes、flatten)
Apr 28 Python
python 安装移动复制第三方库操作
Jul 13 Python
conda安装tensorflow和conda常用命令小结
Feb 20 Python
详解Python描述符的工作原理
Jun 11 Python
详解如何为eclipse安装合适版本的python插件pydev
Nov 04 #Python
详解Python下Flask-ApScheduler快速指南
Nov 04 #Python
Python中修改字符串的四种方法
Nov 02 #Python
Python中flatten( )函数及函数用法详解
Nov 02 #Python
[原创]Python入门教程5. 字典基本操作【定义、运算、常用函数】
Nov 01 #Python
Python拼接字符串的7种方法总结
Nov 01 #Python
在python中bool函数的取值方法
Nov 01 #Python
You might like
php中convert_uuencode()与convert_uuencode函数用法实例
2014/11/22 PHP
WordPress中缩略图的使用以及相关技巧
2015/11/24 PHP
PHP经典算法集锦【经典收藏】
2016/09/14 PHP
利用switch语句进行多选一判断的实例代码
2016/11/14 PHP
PHP实现Snowflake生成分布式唯一ID的方法示例
2020/08/30 PHP
js中document.getElementByid、document.all和document.layers区分介绍
2011/12/08 Javascript
Javascript页面跳转常见实现方式汇总
2015/11/28 Javascript
AngularJS  $modal弹出框实例代码
2016/08/24 Javascript
利用React-router+Webpack快速构建react程序
2016/10/27 Javascript
火狐和ie下获取javascript 获取event的方法(推荐)
2016/11/26 Javascript
JavaScript错误处理和堆栈追踪详解
2017/04/18 Javascript
jQuery实现动态删除LI的方法
2017/05/30 jQuery
AngularJS实现select的ng-options功能示例
2017/07/12 Javascript
vue2 全局变量的设置方法
2018/03/09 Javascript
详解vue-cli项目中怎么使用mock数据
2018/05/29 Javascript
微信小程序冒泡事件及其阻止方法实例分析
2018/12/06 Javascript
浅谈Layui的eleTree树式选择器使用方法
2019/09/25 Javascript
vue实现节点增删改功能
2019/09/26 Javascript
jQuery实现图片随机切换、抽奖功能(实例代码)
2019/10/23 jQuery
[01:12]DOTA2次级职业联赛 - Newbee.Y 战队宣传片
2014/12/01 DOTA
python检测远程udp端口是否打开的方法
2015/03/14 Python
python获取当前计算机cpu数量的方法
2015/04/18 Python
Python pass详细介绍及实例代码
2016/11/24 Python
python中os和sys模块的区别与常用方法总结
2017/11/14 Python
DRF跨域后端解决之django-cors-headers的使用
2019/01/27 Python
python操作gitlab API过程解析
2019/12/27 Python
推荐技术人员一款Python开源库(造数据神器)
2020/07/08 Python
浅析PyCharm 的初始设置(知道)
2020/10/12 Python
英国领先的大码时装品牌之一:Elvi
2018/08/26 全球购物
养殖项目策划书范文
2014/01/13 职场文书
禁毒宣传标语
2014/06/19 职场文书
财务统计员岗位职责
2015/04/14 职场文书
2015年机械设备管理工作总结
2015/05/04 职场文书
导游词之杭州西湖
2019/09/19 职场文书
Nginx 路由转发和反向代理location配置实现
2021/11/11 Servers
《勇者辞职不干了》ED主题曲无字幕动画MV公开
2022/04/13 日漫