多线程共同操作同一个数据的时候,怎么保证同步?其实啊,这玩意儿面试里特别爱考,写代码的时候也天天能踩坑。你想啊,多个线程一块儿去改一个变量,要是不加限制,那不是谁抢到就写,最后数据乱七八糟,结果完全不对了嘛。
先举个特别生活化的例子,你想象一下公司茶水间放一罐糖,大家下班都去抓两颗吃,要是没有规矩,三个人同时伸手进去,可能一个人还没拿完,另一个人就跟着抓,结果数目完全对不上。那在代码里就是线程安全问题,Python 里就得用互斥锁(Lock)去控制,保证同一时刻只有一个人能去动那罐糖。
说到这儿,你们是不是立马想到 threading 模块?对对,就是那个。里面有个 Lock,就像门口挂了个锁,你要进去必须先拿到钥匙,拿到了才能动里面的资源,等你用完了再把钥匙还回去,别人才能继续用。比如下面这个小例子,我昨天在地铁上敲的:
你要是把 with lock: 那段去掉,最后结果十有八九不是 500000,可能少个几千几万,这就是竞态条件。加了锁之后就规矩了,每次只能一个线程加 1,数据稳稳当当。
不过呢,光知道用锁还不够。像我上次在家熬夜改一个爬虫脚本,手一抖锁的位置放错了,结果线程都卡死在那等,谁也不释放,死锁了。就像两个人同时换钥匙,一个说“你先给我”,另一个说“你先给我”,最后谁也进不去。这种情况在多锁的场景特别容易发生,比如线程 A 先拿了锁1 又去等锁2,而线程 B 正好拿了锁2 去等锁1,两个人就互相干瞪眼。所以实际写的时候,要么尽量少用锁,要么就固定顺序拿,别乱抢。
还有个点就是 Python 的 GIL(全局解释器锁),面试官爱追问。你要说清楚,虽然 GIL 限制了同一时刻只能有一个线程执行 Python 字节码,但像 I/O 操作的时候线程还是能切换的,所以锁依旧重要。尤其在操作共享数据的时候,GIL 并不能替代互斥锁,不然数据早飞了。
其实我后来更喜欢用 threading.RLock,也就是可重入锁。上次我在项目里写日志模块,一个函数里调了另一个也要加锁的函数,要是用普通锁直接卡死自己了,用 RLock 就没事,它允许同一个线程多次获取,不会死锁。这个小技巧要是说出来,面试官一般会点点头,说明你懂门道。对了,说到同步,有人会提 Condition 或者 Semaphore,这些场景也不少。像 Semaphore 就像公司食堂窗口的托盘数,拿完托盘就得等别人吃完还回来再轮到你。适合控制“最多允许几个线程同时执行”的场景。Condition 就是更高级的玩法了,让线程可以等一个条件满足才继续跑,比如“等到数据准备好再开始计算”。
我有次写多线程下载器,每个线程负责一个片段,但最后得等所有片段都下完才能合并文件,我就用 threading.Barrier,这东西相当于一个集合点,大家跑到这里先等一等,人都到齐了再一起走,挺好用。
面试要答得明白:为什么要同步?——因为有共享数据会乱。怎么同步?——互斥锁、RLock、Semaphore、Condition、Barrier 这些都能用,还得顺便提一下 GIL 的影响,别让面试官以为你光背 API。要是再补充点“死锁怎么避免”,那基本上就能把这个问题拿下了。
行了行了,我口干舌燥了,去倒杯水先。你们要不要我下次再写一个“多进程跟多线程在 Python 里各自适合的场景”?
以上就是“Python多线程共同操作同一个数据互斥锁同步?”的详细内容,想要了解更多Python教程欢迎持续关注编程学习网。
扫码二维码 获取免费视频学习资料
- 本文固定链接: http://phpxs.com/post/13388/
- 转载请注明:转载必须在正文中标注并保留原文链接
- 扫码: 扫上方二维码获取免费视频资料