Python的锁源码剖析

Lock

原始锁: 实现原始锁对象的类。一旦一个线程获得一个锁,会阻塞随后尝试获得锁的线程,直到它被释放;
任何线程都可以释放它。

class Lock:
    def acquire(self, blocking=True, timeout=-1):
        '''
        blocking 阻塞: 阻塞状态下会维持到timeout时间然后抛出Runtime异常
                 非阻塞: 直接返回该锁状态(已锁---False  未锁---True)
        timeout 定义超时时间,值为-1时不定义超时时间
        '''
        return False
    def release(self):
        '''
        释放锁(任何线程都可以调用这个方法)
        '''
        pass

原始锁的原理相当的简单,就是一种互斥的机制,一个线程获取了该锁,其他线程就在获取锁的过程中发生阻塞或者返回一个状态码(bool),在此基础上实现下面的几种同步概念

RLock

可重入锁(递归锁),解决在同一线程中多次上锁引发的死锁问题

class RLock:
    def __init__(self):
        # 维护一个原始锁
        self._block = Lock()
        # 重入锁当前拥有者(线程id标识)
        self._owner = None
        # 当前拥有者的上锁次数
        self._count = 0
    def acquire(self, blocking=True, timeout=-1):
        # 获取当前线程id
        me = get_ident()
        # 判断当前重入锁拥有者是否为自身
        if self._owner == me:
            # 如果为自身的就不阻塞而令count值 +1
            self._count += 1
            return 1
        # 非拥有者则抢占锁(阻塞或返回True值表示该锁未有拥有者)
        rc = self._block.acquire(blocking, timeout)
        if rc:
            # 未上锁情况下定义拥有者为自身
            self._owner = me
            self._count = 1
        return rc
    def release(self):
        # 只允许拥有者自身解锁
        if self._owner != get_ident():
            raise RuntimeError("cannot release un-acquired lock")
        # 解锁一次令count - 1
        self._count = count = self._count - 1
        # 当前线程的锁被全部解开后释放该RLock实例
        if not count:
            self._owner = None
            # 解除对其余线程的阻塞
            self._block.release()

可重入锁实际上利用了原始锁做了一层封装,利用count记录当前线程的入锁次数而并不是每次都抢占_block锁,因为在同一线程中抢占同一个锁会导致死锁,在拥有RLock实例的线程中需要入锁解锁相同次数才能让别的线程通过acquire获取到锁。

Condition

条件变量,允许一个或多个线程在被其它线程所通知之前进行等待。

class Condition:
    def __init__(self, lock=None):
        # 提供传入的锁对象, 支持RLock/Lock,可以利用同一个锁对象来控制多个条件变量
        if lock is None:
            lock = RLock()
        self._lock = lock
        self.acquire = lock.acquire
        self.release = lock.release
        # 如果lock对象存在下列方法则覆盖当前Condition类的方法
        try:
            self._release_save = lock._release_save
        except AttributeError:
            pass
        try:
            self._acquire_restore = lock._acquire_restore
        except AttributeError:
            pass
        try:
            self._is_owned = lock._is_owned
        except AttributeError:
            pass
        # 定义waiters队列
        self._waiters = _deque()
        def _release_save(self):
        self._lock.release()           

    def _acquire_restore(self, x):
        self._lock.acquire()           

    def _is_owned(self):
        if self._lock.acquire(0):
            self._lock.release()
            return False
        else:
            return True
    
    def wait(self, timeout=None):
        # lock: lock对象未上锁时不能执行wait接口
        # RLock: RLock对象则判断当前拥有者未非自身时不能执行wait接口
        # 在这里为何要判断is_owned,就是为了对公共部分(_waiters)进行上锁,故此要配合with Condition进行使用
        if not self._is_owned():
            raise RuntimeError("cannot wait on un-acquired lock")
        # 此处生成的waiter实际上就是对当前线程生成一把原始锁,提供给拥有者进行释放并且放行该线程
        waiter = _allocate_lock()
        # 先对waiter上一次锁
        waiter.acquire()
        # 放入waiter队列供给拥有者访问并释放 
        self._waiters.append(waiter)
        saved_state = self._release_save()
        gotit = False
        try:    # restore state no matter what (e.g., KeyboardInterrupt)
            if timeout is None:
                # 阻塞在当前线程并等待拥有者释放
                waiter.acquire()
                gotit = True
            else:
                if timeout > 0:
                    # 只阻塞超时时间,过时后返回False
                    gotit = waiter.acquire(True, timeout)
                else:
                    gotit = waiter.acquire(False)
            return gotit
        finally:
            self._acquire_restore(saved_state)
            if not gotit:
                try:
                    # 过时或者设定的非阻塞而返回的是False就移除该waiter
                    self._waiters.remove(waiter)
                except ValueError:
                    pass

    def notify(self, n=1):
        # 只有拥有者可以对waiter队列中的n个线程进行'唤醒'功能
        if not self._is_owned():
            raise RuntimeError("cannot notify on un-acquired lock")
        all_waiters = self._waiters
        waiters_to_notify = _deque(_islice(all_waiters, n))
        if not waiters_to_notify:
            return
        # 循环对waiter中需要唤醒的线程进行'唤醒'
        for waiter in waiters_to_notify:
            waiter.release()
            try:
                all_waiters.remove(waiter)
            except ValueError:
                pass

条件变量比较关键的接口解析如上所述,实际上是包装了一个锁对象,可以是原始锁Lock或者是可重入锁RLock,多个线程通过共用一把锁来进行身份控制(主从),当有线程对其锁对象上锁后则其余线程对此条件变量就只有wait方法。

Semaphore

信号量,初始化管理一个内部计数器,当计数器未为0时将不会阻塞线程

class Semaphore:
    def __init__(self, value=1):
        # 设定内部计数器不能小于0
        if value < 0:
            raise ValueError("semaphore initial value must be >= 0")
        self._cond = Condition(Lock())
        self._value = value

    def acquire(self, blocking=True, timeout=None):
        if not blocking and timeout is not None:
            raise ValueError("can't specify timeout for non-blocking acquire")
        rc = False
        endtime = None
        with self._cond:
            while self._value == 0:
                if not blocking:
                    break
                if timeout is not None:
                    if endtime is None:
                        endtime = _time() + timeout
                    else:
                        timeout = endtime - _time()
                        if timeout <= 0:
                            break
                # 当计数值为0时需要等待条件唤醒
                self._cond.wait(timeout)
            else:
                self._value -= 1
                rc = True
        return rc

    def release(self):
        with self._cond:
            # 其中一个线程进行释放的时候就会唤醒一个等待队列中的线程,并令计数值不为0
            self._value += 1
            self._cond.notify()

信号量封装了条件变量,利用其对其余线程的条件阻塞控制并发。

Event

事件对象,维护着一个标识位(bool),通过set方法被设定为True,clear方法设定为False,wait方法将阻塞到标志位为True的时候

class Event:
    def __init__(self):
        self._cond = Condition(Lock())
        self._flag = False

    def _reset_internal_locks(self):
        # 重新设定条件变量里面的锁
        self._cond.__init__(Lock())

    def is_set(self):
        """Return true if and only if the internal flag is true."""
        return self._flag

    def set(self):
        with self._cond:
            self._flag = True
            # 唤醒所有的waiter
            self._cond.notify_all()
    
    def clear(self):
        with self._cond:
            self._flag = False
    
    def wait(self, timeout=None):
        # 等待同一事件唤醒所有等待该事件的线程
        with self._cond:
            signaled = self._flag
            if not signaled:
                signaled = self._cond.wait(timeout)
            return signaled

事件对象封装了条件变量,对标志位为False的进行阻塞,直至有线程对事件对象进行set操作。

总结

python原生库里面控制线程并发的几个对象:Lock, Rlock, Condition, Semaphore, Event。实际上都是在基础锁的功能下进行封装修改,通过对这几个对象的相互配合就能应对较为复杂的线程并发开发需求。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • 线程状态新建,就绪,运行,阻塞,死亡。 线程同步多线程可以同时运行多个任务,线程需要共享数据的时候,可能出现数据不...
    KevinCool阅读 793评论 0 0
  • 线程 操作系统线程理论 线程概念的引入背景 进程 之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有...
    go以恒阅读 1,630评论 0 6
  • 关于Python多线程的概述 由于GIL的存在,Python的多线程在CPU密集型任务并没有多大的优势,任何Pyt...
    千鸟月读阅读 541评论 0 0
  • 写在前面的话 代码中的# > 表示的是输出结果 输入 使用input()函数 用法 注意input函数输出的均是字...
    FlyingLittlePG阅读 2,729评论 0 8
  • 锁是一种同步机制,用于多线程环境中对资源访问的限制iOS中常见锁的性能对比图(摘自:ibireme): iOS锁的...
    LiLS阅读 1,503评论 0 6