Python-线程死锁


死锁可以被描述为并发失败模式。这是程序中的一种情况,其中一个或多个线程等待一个从未发生的条件。结果,线程无法继续进行,程序被卡住或冻结,必须手动终止。

在并发程序中,死锁情况可能以多种方式出现。死锁从来都不是故意造成的,相反,它们实际上是代码中的副作用或错误。

下面列出了线程死锁的常见原因 -

  • 尝试两次获取相同互斥锁的线程。

  • 相互等待的线程(例如,A 等待B,B 等待A)。

  • 当线程无法释放锁、信号量、条件、事件等资源时

  • 以不同顺序获取互斥锁的线程(例如,无法执行锁排序)。

如果多线程应用程序中多个线程尝试访问同一资源,例如对同一文件执行读/写操作,则可能会导致数据不一致。因此,并发处理的同步非常重要,这样当一个线程使用资源时,其他线程就可以锁定它。

Python 提供的线程模块包含一个易于实现的锁定机制,允许您同步线程。通过调用 Lock() 方法创建一个新锁,该方法返回新锁。

锁定对象

Lock 类的对象有两种可能的状态 - 锁定或解锁,首次创建时最初处于解锁状态。锁不属于任何特定线程。

Lock 类定义了 acquire() 和 release() 方法。

acquire() 方法

当状态为解锁时,该方法将状态更改为锁定并立即返回。该方法采用可选的阻塞参数。

句法

Lock.acquire(blocking, timeout)

参数

  • 阻塞- 如果设置为 False,则意味着不阻塞。如果将阻塞设置为 True 的调用会阻塞,则立即返回 False;否则,将锁设置为锁定并返回 True。

如果获取锁成功,该方法的返回值为True;如果没有则为假。

release() 方法

当状态为锁定时,另一个线程中的此方法将其更改为解锁状态。这可以从任何线程调用,而不仅仅是已获取锁的线程

句法

Lock.release()

release() 方法只能在锁定状态下调用。如果尝试释放未锁定的锁,则会引发运行时错误。

当锁被锁定时,将其重置为解锁,然后返回。如果任何其他线程在等待锁解锁时被阻塞,则只允许其中一个线程继续进行。该方法没有返回值。

例子

在下面的程序中,两个线程尝试调用synchronized()方法。其中一个获得锁并获得访问权限,而另一个则等待。当第一个线程的 run() 方法完成时,锁被释放,并且同步方法可用于第二个线程。

当两个线程加入时,程序就结束了。

from threading import Thread, Lock
import time

lock=Lock()
threads=[]

class myThread(Thread):
   def __init__(self,name):
      Thread.__init__(self)
      self.name=name
   def run(self):
      lock.acquire()
      synchronized(self.name)
      lock.release()

def synchronized(threadName):
   print ("{} has acquired lock and is running synchronized method".format(threadName))
   counter=5
   while counter:
      print ('**', end='')
      time.sleep(2)
      counter=counter-1
   print('\nlock released for', threadName)

t1=myThread('Thread1')
t2=myThread('Thread2')

t1.start()
threads.append(t1)

t2.start()
threads.append(t2)

for t in threads:
   t.join()
print ("end of main thread")

它将产生以下输出-

Thread1 has acquired lock and is running synchronized method
**********
lock released for Thread1
Thread2 has acquired lock and is running synchronized method
**********
lock released for Thread2
end of main thread

信号量对象

Python 通过另一种称为信号量的机制来支持线程同步。它是由著名计算机科学家 Edsger W. Dijkstra 发明的最古老的同步技术之一。

信号量的基本概念是使用一个内部计数器,该计数器在每次 acquire() 调用时递减,并在每次 release() 调用时递增。计数器永远不会低于零;当 acquire() 发现它为零时,它会阻塞,等待其他线程调用release()。

threading 模块中的 Semaphore 类定义了 acquire() 和 release() 方法。

acquire() 方法

如果内部计数器在输入时大于零,则将其减一并立即返回 True。

如果内部计数器在进入时为零,则阻塞直到通过调用release()唤醒。一旦被唤醒(并且计数器大于 0),计数器减 1 并返回 True。每次调用release()都会唤醒一个线程。线程唤醒的顺序是任意的。

如果阻塞参数设置为False,则不阻塞。如果不带参数的调用会阻塞,则立即返回 False;否则,执行与不带参数调用时相同的操作,并返回 True。

release() 方法

释放一个信号量,将内部计数器加 1。当它在进入时为零并且其他线程正在等待它再次变得大于零时,唤醒其中的 n 个线程。

例子

from threading import *
import time

# creating thread instance where count = 3
lock = Semaphore(4)

# creating instance
def synchronized(name):
   
   # calling acquire method
   lock.acquire()

   for n in range(3):
      print('Hello! ', end = '')
      time.sleep(1)
      print( name)

      # calling release method
      lock.release()

# creating multiple thread
thread_1 = Thread(target = synchronized , args = ('Thread 1',))
thread_2 = Thread(target = synchronized , args = ('Thread 2',))
thread_3 = Thread(target = synchronized , args = ('Thread 3',))

# calling the threads
thread_1.start()
thread_2.start()
thread_3.start()

它将产生以下输出-

Hello! Hello! Hello! Thread 1
Hello! Thread 2
Thread 3
Hello! Hello! Thread 1
Hello! Thread 3
Thread 2
Hello! Hello! Thread 1
Thread 3
Thread 2