Python中的多线程是共享所在进程的资源和内存地址的,因此当多个线程同时操作同一个数据的时候,就容易出错。如下
from threading import Thread,currentThread
import time
def task():
global n
tem = n
time.sleep(0.01) # 在此处休息0.01秒,足够开启所有的线程,他们所获取到的n都是0,这就导致了最后输出的n为1
tem = tem + 1
n = tem
print("%s :n = %d" % (currentThread().name, n))
if __name__ == '__main__':
n = 0
th_li = []
for i in range(10):
th = Thread(target=task)
th.start()
th_li.append(th)
for i in th_li:
i.join() # 等待所有线程执行完毕
print(n)
最后输出n等于1。为了避免这种情况的发生,我们可以在进程start()后马上join()这样就能够将并发改为串行。还有一种方式就是加锁,和多进程中的互斥锁一样。
from threading import Thread,currentThread,Lock
import time
def task():
metux.acquire()
global n
tem = n
time.sleep(0.01)
tem = tem + 1
n = tem
print("%s :n = %d" % (currentThread().name, n))
metux.release()
if __name__ == '__main__':
n = 0
th_li = []
metux = Lock()
for i in range(10):
th = Thread(target=task)
th.start()
th_li.append(th)
for i in th_li:
i.join()
print(n)
我们可以通过加锁和解锁来将并发改为串行,保证了数据的安全。但如果我们加锁之后,忘记了解锁,这样程序就会卡死。为此Python提供了一种便捷的写法
def task():
metux.acquire()
global n
tem = n
time.sleep(0.01)
tem = tem + 1
n = tem
print("%s :n = %d" % (currentThread().name, n))
metux.release()
上面这段代码可以简写为
def task():
with metux:
global n
tem = n
time.sleep(0.01)
tem = tem + 1
n = tem
print("%s :n = %d" % (currentThread().name, n))
这样就不用担心加锁和解锁的问题。加锁是为了将任务的某个部分由并发改为串行,从而保证数据的安全。我们有时候也需要给任务加多把锁。但多把锁也容易产生新的问题:死锁
from threading import Thread,Lock
import time
class MyThread(Thread):
def th1(self):
l1.acquire()
print(self.name," 拿到了 LOKC-1")
l2.acquire()
print(self.name, " 拿到了 LOKC-2")
l2.release()
l1.release()
def th2(self):
l2.acquire()
print(self.name," 拿到了 LOKC-2")
time.sleep(1)
l1.acquire()
print(self.name, " 拿到了 LOKC-1")
l1.release()
l2.release()
def run(self):
self.th1()
self.th2()
if __name__ == "__main__":
l1 = Lock()
l2 = Lock()
for i in range(10):
mt = MyThread()
mt.start()
执行结果
Thread-1 拿到了 LOKC-1
Thread-1 拿到了 LOKC-2
Thread-1 拿到了 LOKC-2
Thread-2 拿到了 LOKC-1
这里Thread-1拿到了 LOKC-2,准备拿LOKC-1。这里Thread-2拿到了 LOKC-1,准备拿LOKC-2。Thread-1需要的LOKC-1在Thread-2手中还没有释放掉,Thread-2需要的LOKC-2在Thread-1手中还没有释放掉。2个线程都在等待对方释放自己所需要的锁。程序就在此处卡死了。这就是死锁现象。为了解决这个问题,Python中的递归锁(RLock)就产生。
RLock内部包含着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:
l1 = l2 = RLock()