一. 引言:
在 Python 的 threading 模块中,当多个线程同时使用一个公共资源时,可能会导致线程抢占资源,造成数据错跑。比如:跑200个线程,这200个线程都会去访问counter(公共资源),并对该资源进行处理(counter += 1),如下:
#!/usr/bin/python
import threading
import time
counter = 0
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global counter
time.sleep(10);
counter += 1
print "I am %s, set counter:%s" % (self.name, counter)
if __name__ == "__main__":
for i in range(0, 200):
my_thread = MyThread()
my_thread.start()
部分输出如下:
I am Thread-117, set counter:116
I am Thread-119, set counter:117
I am Thread-124, set counter:118
I am Thread-120, set counter:120
I am Thread-122, set counter:122
I am Thread-121, set counter:119
I am Thread-123, set counter:121
I am Thread-128, set counter:123
可发现, 确实有抢占资源的情况,导致输出错乱。问题产生的原因就是没有控制多个线程对同一资源的访问,使得线程运行的结果不可预期。这种现象称为“线程不安全”。因此,在开发过程中我们必须要避免上文中的资源抢占问题
二. 关于互斥锁
Python编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为” 互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。在Python中我们使用threading模块提供的Lock类。添加一个互斥锁变量thr = threading.Lock(),然后在争夺资源的时候之前我们会先抢占这把锁trh.acquire(),对资源使用完成之后我们在释放这把锁thr.release() , 修改后代码如下:
#!/usr/bin/python
import threading
import time
counter = 0
thr = threading.Lock()
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global counter
time.sleep(1);
if thr.acquire(): #添加
counter += 1
print "I am %s, set counter:%s" % (self.name, counter)
thr.release()
if __name__ == "__main__":
for i in range(0, 200):
my_thread = MyThread()
my_thread.start()
删除如下:
I am Thread-3, set counter:1
I am Thread-4, set counter:2
I am Thread-2, set counter:3
I am Thread-5, set counter:4
I am Thread-1, set counter:5
I am Thread-8, set counter:6
I am Thread-6, set counter:7
I am Thread-7, set counter:8
I am Thread-9, set counter:9
I am Thread-10, set counter:10
I am Thread-13, set counter:11
结果并不会出现错乱的情况。
三. 更简洁的用法: with 语句:
lock 支持上下文管理器协议,可使用 with 语句自动获取和释放锁,避免手动调用 acquire() 和 release() 导致的遗漏或错误:
def run(self):
global counter
time.sleep(1);
with thr:
counter += 1
print "I am %s, set counter:%s" % (self.name, counter)
四. 关于死锁:
当一个线程调用Lock对象的acquire()方法获得锁时,就会进入“locked”状态。因为每次只有一个线程可以获得锁,所以如果此时另一个线程试图获得这个锁,该线程就会变为“blo同步阻塞状态。直到拥有锁的线程调用锁的release()方法释放锁之后,该锁进入 “unlocked”状态。线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。 当两个或多个线程互相等待对方释放锁而陷入无限阻塞, 称之为“死锁”。
如下:
lock1 = threading.Lock()
lock2 = threading.Lock()
def thread1():
with lock1:
# 线程1持有lock1,等待lock2
with lock2:
print("Thread1 完成")
def thread2():
with lock2:
# 线程2持有lock2,等待lock1
with lock1:
print("Thread2 完成")
t1 = threading.Thread(target=thread1)
t2 = threading.Thread(target=thread2)
t1.start()
t2.start() # 大概率发生死锁
避免死锁的方法:
-
按固定顺序获取锁(如所有线程都先获取
lock1再获取lock2)。 -
给
acquire()设置超时时间(lock.acquire(timeout=5)),超时后放弃并释放已获取的锁。 -
减少锁的持有时间,仅在必要的代码段(临界区)加锁。
---------------------------------------------------------------------------------------------------------------------
深耕运维行业多年,擅长运维体系建设,方案落地。欢迎交流!
“V-x”: ywjw996
《 运维经纬 》
1667

被折叠的 条评论
为什么被折叠?



