当前位置:首页 > 科技  > 软件

聊聊 Python 中的同步原语,为什么有了 GIL 还需要同步原语

来源: 责编: 时间:2024-04-03 09:08:52 269观看
导读前言• 在前面的文章中我们介绍了 Python 中的全局解释器锁 GIL,我们知道 GIL 可以保证在多线程场景下同一时刻只有一个线程运行,但是并不能保证线程安全(所谓线程安全简单来说就是程序在多线程环境中运行时,线程在交替运

前言

• 在前面的文章中我们介绍了 Python 中的全局解释器锁 GIL,我们知道 GIL 可以保证在多线程场景下同一时刻只有一个线程运行,但是并不能保证线程安全(所谓线程安全简单来说就是程序在多线程环境中运行时,线程在交替运行时能正常的访问共享资源,不会造成数据不一致或者死锁,最后都能达到预期的结果),比如我们看下面的两个例子:b5S28资讯网——每日最新资讯28at.com

对 counter 进行累加

import threadingimport timecounter = 0temp_count = 0def increment():    global counter, temp_count    for _ in range(1000):        counter += 1        temp = temp_count        time.sleep(0.0001)        temp_count = temp + 1start = time.time()threads = []for _ in range(10):    t = threading.Thread(target=increment)    threads.append(t)    t.start()for t in threads:    t.join()end = time.time()print("Final counter value:", counter)print("Final temp_count value:", temp_count)print(f"总共耗时:{end - start}")# 运行结果Final counter value: 10000Final temp_count value: 1001总共耗时:0.5465419292449951

• 上面我们对 counter 做多线程累积时,尽管 counter += 1 是非原子操作,但是由于 CPU 执行太快,因此我们很难复现线程不安全的情况,因此我们使用 temp_count 写法进行手动模拟。b5S28资讯网——每日最新资讯28at.com

账户取款

import threadingclass BankAccount:    def __init__(self, balance):        self.balance = balance    def withdraw(self, amount):        if self.balance >= amount:            # 发生线程切换            self.balance -= amount            print(f"Withdrawal successful. Balance: {self.balance}")        else:            print("Insufficient funds")    def deposit(self, amount):        self.balance += amount        print(f"Deposit successful. Balance: {self.balance}")if __name__ == "__main__":    account = BankAccount(1000)    # 创建多个线程进行取款存款操作    threads = []    for _ in range(5):        t = threading.Thread(target=account.withdraw, args=(account, 200))        threads.append(t)        t.start()    for t in threads:        t.join()

• 上面的代码同样是线程不安全的,考虑这个场景,如果此时账户余额中剩余200,线程1执行完 self.balance >= amount 后切换到线程2,线程2正常取款200,然后切换回线程1,导致此时余额为-2200。b5S28资讯网——每日最新资讯28at.com

使用同步原语保证线程安全

• 从上面的两个案例中我们可以看出,GIL 并不能保证线程安全,我们需要使用同步原语来进行线程同步保证线程安全。b5S28资讯网——每日最新资讯28at.com

locked、release 显式获取锁和释放锁

• 在一些比较老的 python 代码中,我们可以看到很多使用 locked、release 显式获取锁和释放锁 的用法。b5S28资讯网——每日最新资讯28at.com

import threadingclass BankAccount:    def __init__(self, balance):        self.balance = balance        self.lock = threading.Lock()    def withdraw(self, amount):        self.lock.locked()        if self.balance >= amount:            self.balance -= amount            print(f"Withdrawal successful. Balance: {self.balance}")        else:            print("Insufficient funds")        self.lock.release()    def deposit(self, amount):        self.lock.locked()        self.balance += amount        print(f"Deposit successful. Balance: {self.balance}")        self.lock.release()if __name__ == "__main__":    account = BankAccount(1000)    # 创建多个线程进行取款存款操作    threads = []    for _ in range(5):        t = threading.Thread(target=account.withdraw, args=(account, 200))        threads.append(t)        t.start()    for t in threads:        t.join()

使用 with 语句同步原理

• 相比于这种显式调用的方法,with 语句更加优雅,也更不容易出错,特别是程序员可能会忘记调用 release() 方法或者程序在获得锁之后产生异常这两种情况(使用 with 语句可以保证在这两种情况下仍能正确释放锁)。b5S28资讯网——每日最新资讯28at.com

import threadingclass BankAccount:    def __init__(self, balance):        self.balance = balance        self.lock = threading.Lock()    def withdraw(self, amount):        with self.lock:            if self.balance >= amount:                self.balance -= amount                print(f"Withdrawal successful. Balance: {self.balance}")            else:                print("Insufficient funds")    def deposit(self, amount):        with self.lock:            self.balance += amount            print(f"Deposit successful. Balance: {self.balance}")if __name__ == "__main__":    account = BankAccount(1000)    # 创建多个线程进行取款存款操作    threads = []    for _ in range(5):        t = threading.Thread(target=account.withdraw, args=(account, 200))        threads.append(t)        t.start()    for t in threads:        t.join()

其它支持同步原语:RLock 和 Semaphore

RLock

• 一个 RLock (可重入锁)可以被同一个线程多次获取,主要用来实现基于监测对象模式的锁定和同步。在使用这种锁的情况下,当锁被持有时,只有一个线程可以使用完整的函数或者类中的方法。b5S28资讯网——每日最新资讯28at.com

import threadingclass SharedCounter:    '''    A counter object that can be shared by multiple threads.    '''    _lock = threading.RLock()    def __init__(self, initial_value = 0):        self._value = initial_value    def incr(self,delta=1):        '''        Increment the counter with locking        '''        with SharedCounter._lock:            self._value += delta    def decr(self,delta=1):        '''        Decrement the counter with locking        '''        with SharedCounter._lock:             self.incr(-delta)

• 在上边这个例子中,没有对每一个实例中的可变对象加锁,取而代之的是一个被所有实例共享的类级锁。这个锁用来同步类方法,具体来说就是,这个锁可以保证一次只有一个线程可以调用这个类方法。不过,与一个标准的锁不同的是,已经持有这个锁的方法在调用同样使用这个锁的方法时,无需再次获取锁。比如 decr 方法。 这种实现方式的一个特点是,无论这个类有多少个实例都只用一个锁。因此在需要大量使用计数器的情况下内存效率更高。不过这样做也有缺点,就是在程序中使用大量线程并频繁更新计数器时会有争用锁的问题。b5S28资讯网——每日最新资讯28at.com

Semaphore

• 信号量对象是一个建立在共享计数器基础上的同步原语。如果计数器不为0,with 语句将计数器减1,线程被允许执行。with 语句执行结束后,计数器加1。如果计数器为0,线程将被阻塞,直到其他线程结束将计数器加1。b5S28资讯网——每日最新资讯28at.com

import urllib.requestfrom threading import Semaphore# At most, five threads allowed to run at once_fetch_url_sema = Semaphore(5)def fetch_url(url):    with _fetch_url_sema:        return urllib.request.urlopen(url)

本文链接:http://www.28at.com/showinfo-26-81056-0.html聊聊 Python 中的同步原语,为什么有了 GIL 还需要同步原语

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 我们一起聊聊软件架构伸缩性法则

下一篇: React 19 要来了!究竟带来了哪些实用的特性呢?

标签:
  • 热门焦点
  • K60至尊版狂暴引擎2.0加持:超177万跑分斩获性能第一

    Redmi的后性能时代战略发布会今天下午如期举办,在本次发布会上,Redmi公布了多项关于和联发科的深度合作,以及新机K60 Ultra在软件和硬件方面的特性,例如:“K60 至尊版,双芯旗舰
  • 5月iOS设备性能榜:M1 M2依旧是榜单前五

    和上个月一样,没有新品发布的iOS设备性能榜的上榜设备并没有什么更替,仅仅只有跑分变化而产生的排名变动,刚刚开始的苹果WWDC2023,推出的产品也依旧是新款Mac Pro、新款Mac Stu
  • 消息称迪士尼要拍真人版《魔发奇缘》:女主可能也找黑人演员

    8月5日消息,迪士尼确实有点忙,忙着将不少动画改成真人版,继《美人鱼》后,真人版《白雪公主》、《魔发奇缘》也在路上了。据外媒消息称,迪士尼将打造真人版
  • 0糖0卡0脂 旭日森林仙草乌龙茶优惠:15瓶到手29元

    旭日森林无糖仙草乌龙茶510ml*15瓶平时要卖为79.9元,今日下单领取50元优惠券,到手价为29.9元。产品规格:0糖0卡0脂,添加草本仙草汁,清凉爽口,富含茶多酚,保留
  • 分布式系统中的CAP理论,面试必问,你理解了嘛?

    对于刚刚接触分布式系统的小伙伴们来说,一提起分布式系统,就感觉高大上,深不可测。而且看了很多书和视频还是一脸懵逼。这篇文章主要使用大白话的方式,带你理解一下分布式系统
  • 如何正确使用:Has和:Nth-Last-Child

    我们可以用CSS检查,以了解一组元素的数量是否小于或等于一个数字。例如,一个拥有三个或更多子项的grid。你可能会想,为什么需要这样做呢?在某些情况下,一个组件或一个布局可能会
  • 在线图片编辑器,支持PSD解析、AI抠图等

    自从我上次分享一个人开发仿造稿定设计的图片编辑器到现在,不知不觉已过去一年时间了,期间我经历了裁员失业、面试找工作碰壁,寒冬下一直没有很好地履行计划.....这些就放在日
  • 每天一道面试题-CPU伪共享

    前言:了不起:又到了每天一到面试题的时候了!学弟,最近学习的怎么样啊 了不起学弟:最近学习的还不错,每天都在学习,每天都在进步! 了不起:那你最近学习的什么呢? 了不起学弟:最近在学习C
  • 疑似小米14外观设计图曝光:后置相机模组变化不大

    下半年的大幕已经开启,而谁将成为下半年手机圈的主角就成为了大家关注的焦点,其中被传有望拿下新一代骁龙8 Gen3旗舰芯片的小米14系列更是备受大家瞩
Top