编程学习网 > 编程语言 > Python > Python多线程共同操作同一个数据互斥锁同步??
2025
09-16

Python多线程共同操作同一个数据互斥锁同步??


多线程访问共享数据这件事,说简单也简单,说麻烦也是真麻烦。尤其是当多个线程同时对一个变量读写时,要是不加控制,程序分分钟给你来个“结果不一致”的惊喜,这在面试里是个高频考点,实战中更是事故多发区。咱今天就用Python来聊聊这事,重点放在互斥锁这块。

我先抛个问题:你知道什么叫“线程不安全”吗?比如有一个变量叫count,初始值是0,我们让多个线程去对它做自增操作,看起来很简单,但运行结果可能让你傻眼。

理论上是1000000,对吧?10个线程,每人加10万次。但你多跑几次会发现,每次输出的结果都不一样,有时候差一万,有时候差几千。这就叫“线程不安全”——多个线程同时读写同一块内存,互相踩了脚,就像一群人同时在白板上写字,结果没人能看清最后写的是什么。

为啥会这样?主要是自增操作不是原子性的。count += 1其实拆解起来就是先读,再加,再写,中间任意一步要是被另一个线程打断,那结果就寄了。

那怎么办呢?加锁。

Python里的锁主要有两种:threading.Lock() 和 threading.RLock()。先说简单的Lock,这个东西就像是门闩,一个线程进来后把门一插,其他线程就只能等着。我们改改上面的代码:

现在你怎么跑都是1000000了。这个with lock语法糖其实是lock.acquire()lock.release()的简写,Python用上下文管理器封装得挺舒服的。

这里有个点必须提一下:锁的粒度很关键。你加锁的位置不对,要么不起作用,要么性能极差。比如你要是把锁写到整个函数外面,相当于串行执行,那还不如不用线程。锁的粒度应该尽可能小,只包住共享数据的读写那几行代码。

再说说RLock,也就是可重入锁。有时候你在一个函数里加了锁,结果这个函数又调用了另一个也加了同一个锁的函数,这时候用普通的Lock就死锁了,因为锁已经被自己占着了,自己还等自己释放,永远等不到。但RLock就不会,它知道是同一个线程在用,可以多次获得,只需要释放同样多次就行。

还有个高级点的玩法是条件变量Condition,这个适合生产者-消费者模型,咱今天重点讲互斥锁,就不展开说了。

说到这,有些同学可能要问了,那多线程加锁是不是就万事大吉了?我只能说:别太乐观。Python的GIL(全局解释器锁)机制决定了,同一时刻只能有一个线程执行Python字节码。也就是说,多线程在CPU密集型任务下并没有你想象中那么高效。

不过,GIL也不是洪水猛兽,它对I/O密集型任务,比如文件读写、网络请求,还是挺友好的。你别指望用Python多线程去做高性能计算,想跑得快就上多进程或者用C扩展。

对了,还有人问我“多线程用不用锁?”我一般就反问一句:“你敢不用?”除非你能确保线程之间的数据完全独立,否则你不加锁,迟早翻车。我们线上系统出过这种事,一个请求进来修改全局变量,结果另一个线程刚好也在改,数据错乱了,最后查了一天才定位到问题。

你也许听过“锁是把双刃剑”,没错,锁能保安全,也能拖性能。死锁、活锁、锁饥饿,这些都是锁带来的副作用。设计得不好,线程之间互相等着释放锁,系统就卡住不动了。我就见过有个哥们为了“保险”把所有线程的入口都加了一把大锁,结果性能比单线程还差。

所以说,锁这玩意儿用得好是救命稻草,用不好就是催命符。建议多看看经典的并发模式,比如“读写锁”、“信号量”、“线程池”,别老想着手撸一套,库里有的就别重复造轮子。

最后再唠一句,Python3.2之后还有个好东西叫concurrent.futures.ThreadPoolExecutor,线程池管理起来比你手动创建线程轻松多了,而且还能用submit提交任务,用as_completed收集结果,优雅得很。如果你在面试里要用多线程处理任务队列,推荐优先用这个。

总的来说,多线程不是洪水猛兽,但也绝对不是银弹。该加锁就加锁,但别滥用;共享变量要当心,线程安全不是自动给你兜底的;写多线程代码,脑子一定要清醒,别让并发问题给你埋个定时炸弹,炸的是自己。

你要是真想在面试里聊出彩,不光得知道怎么加锁,还得能讲出几个你线上踩过坑的故事——那才叫“有血有肉的技术经历”。你说呢?

以上就是“Python多线程共同操作同一个数据互斥锁同步?的详细内容,想要了解更多Python教程欢迎持续关注编程学习网。

扫码二维码 获取免费视频学习资料

Python编程学习

查 看2022高级编程视频教程免费获取