Python多线程基础:变量共享、资源竞争与互斥锁实战
本文深入解析Python多线程编程中的核心问题。从基础线程创建入手,探讨全局变量在线程间的共享机制,分析因CPU调度导致的资源竞争现象。通过引入互斥锁解决数据不一致问题,并演示死锁成因及银行家算法等预防策略,帮助开发者掌握并发编程的关键技巧。
Python多线程基础示例
1、demo
首先展示多线程的基础用法,包括如何通过继承Thread类或定义目标函数来创建和启动线程。
import threading
import time
class ClassName(threading.Thread):
"""创建类,通过多线程执行"""
def run(self):
for i in range(5):
print(i)
time.sleep(1)
def sing():
for i in range(1,11):
print("唱歌第 %d 遍" % i)
time.sleep(1)
def dance():
for i in range(1,16):
print("跳舞第 %d 遍" % i)
time.sleep(1)
def main():
t1 = threading.Thread(target = sing)
t2 = threading.Thread(target = dance)
t = ClassName()
# 启动线程
t1.start()
t2.start()
t.start()
while True:
length = len(threading.enumerate())
print("正在运行的线程有 %s" %threading.enumerate())
if length <= 1:
break
time.sleep(1)
if __name__ == '__main__':
main()
运行上述代码后,可以观察到函数sing、dance以及自定义类中的逻辑是并发执行的。由于输出内容较多,此处省略运行截图。
多线程中的变量共享机制
2、多线程共享变量
接下来演示线程间如何共享数据。通过定义一个全局变量,在test1函数内部修改该全局变量,而在test2函数中读取并打印该全局变量。
import threading
import time
#定义全局变量
g_num = 0
def test1():
"""函数test1对全局变量进行更改"""
global g_num
for i in range(1,10):
g_num += 1
print("--- test1 线程 g_num = %d--- " % g_num)
def test2():
"""函数test2 打印全局变量"""
print("--- test2 线程 g_num = %d--- " % g_num)
def main():
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
# 启动线程
t1.start()
# 增加睡眠是为了保证优先执行函数test1
time.sleep(1)
t2.start()
print("--- 主线程 g_num = %d--- " % g_num)
if __name__ == '__main__':
main()
从执行结果可以看出,主线程以及创建的两个子线程读取到的全局变量值是相同的。这证明了在Python多线程环境中,全局变量是默认共享的。

资源竞争问题分析
3、资源竞争
当多个线程同时尝试修改同一个变量时,可能会引发资源竞争问题。由于CPU的执行调度机制,如果修改参数的代码块不能作为一个原子操作一次性完成,就会导致数据不一致。
import threading
import time
# 定义全局变量
g_num = 0
def test1(num):
"""函数test1对全局变量进行更改"""
global g_num
for i in range(num):
g_num += 1
print("test1 线程 g_num = %d---" % g_num)
def test2(num):
"""函数test2对全局变量进行更改"""
global g_num
for i in range(num):
g_num += 1
print("tes2 线程 g_num = %d---" % g_num)
def main():
t1 = threading.Thread(target=test1, args=(1000000, ))
t2 = threading.Thread(target=test2, args=(1000000, ))
t1.start()
t2.start()
time.sleep(1)
print("主线程 g_num = %d---" % g_num)
if __name__ == '__main__':
main()
我们可以尝试将参数设置为100,此时观察到g_num的结果为200。这是因为在参数较小时,函数代码可能恰好能一次性执行完成。然而,当参数增大到1000000时,代码无法一次性执行完毕,此时g_num的值将不再等于2000000,体现了资源竞争带来的影响。

使用互斥锁解决竞争问题
4、互斥锁
为了解决资源竞争问题,可以使用互斥锁(Mutex)。其原理非常直观:通过对关键代码段加锁,确保在该代码执行完成前,其他线程无法对其进行修改。执行完毕后解锁,其他线程即可继续执行。
import threading
import time
# 创建变量
g_num = 0
# 创建锁默认为开锁状态
mutex = threading.Lock()
def test1(num):
global g_num
for i in range(num):
# 上锁
mutex.acquire()
g_num += 1
# 解锁
mutex.release()
print("--- test1 线程 g_num = %d---" % g_num)
def test2(num):
global g_num
for i in range(num):
# 上锁
mutex.acquire()
g_num += 1
# 解锁
mutex.release()
print("--- test2 线程 g_num = %d---" % g_num)
def main():
t1 = threading.Thread(target=test1, args=(1000000, ))
t2 = threading.Thread(target=test2, args=(1000000, ))
t1.start()
t2.start()
time.sleep(1)
print("--- 主线程 g_num = %d---" % g_num)
if __name__ == '__main__':
main()
加入锁机制后,代码执行不再出现资源竞争,最终结果也符合预期。需要注意的是,加锁的范围应尽可能小,以减少对性能的影响。

死锁成因及预防策略
5、死锁
当程序中存在多个锁时,可能会发生死锁现象。如果尝试关闭一个已经处于关闭状态的锁,或者多个线程互相等待对方释放锁,程序就会陷入阻塞。如下方代码所示,当函数test1尝试关闭mutexB锁时,如果函数test2已经提前将其关闭且未解锁,程序就会一直阻塞。
import threading
import time
# 创建两个锁A, B
mutexA = threading.Lock()
mutexB = threading.Lock()
def test1():
# 对muctexA上锁
mutexA.acquire()
# mutexA上锁后,延时1秒,等待mutexB上锁
print("test1 ---do1---up---")
time.sleep(1)
# 此时会堵塞,因为mutexB已经上锁
mutexB.acquire()
print("test1 ---do1---down---")
mutexB.release()
# 对mutexA解锁
mutexA.release()
def test2():
# 对muctexB上锁
mutexB.acquire()
# mutexB上锁后,延时1秒,等待mutexA上锁
print("test2 ---do1---up---")
time.sleep(1)
# 此时会堵塞,因为mutexB已经上锁
mutexA.acquire()
print("test2 ---do1---down---")
mutexA.release()
# 对mutexA解锁
mutexB.release()
def main():
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
t1.start()
t2.start()
if __name__ == '__main__':
main()
从代码执行效果可以看到,程序会一直阻塞无法结束。 解决方法 1、在程序编写阶段就需仔细设计,避免产生死锁 2、可以参考银行家算法来预防死锁

至此,关于Python多线程及变量共享问题的介绍结束。更多相关Python多线程变量共享内容,请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













