线程死锁的思考

线程死锁的思考


前言

前些天在公司这边写了个豌豆荚的爬虫,用到了分区思想和自己实现的线程池,我自己觉得从这个过程中学到了很多东西,包括如何去设计接口和方便扩展以及代码的规范化。之前用小数据量测试了发现没什么问题,后来拿了W级以上的问题,发现插入的数码条目的量级和输入量级有很大差异,就算算上失效的URL也不应出现这样的情况,于是开始排查。反反复复看各个模块的代码,对应日志信息查看,最后发现时死锁问题导致的。

什么是死锁?

死锁(英语:Deadlock),又译为死锁,计算机科学名词。当两个以上的运算单元,双方都在等待对方停止运行,以取得系统资源,但是>没有一方提前退出时,这种状况,就称为死锁。在多任务操作系统中,操作系统为了协调不同进程,能否取得系统资源时,为了让系统运

作,就必须要解决这个问题。

这里指的是进程死锁,是个计算机技术名词。它是操作系统或软件运行的一种状态:在多任务系统下,当一个或多个进程等待系统资源,

而资源又被进程本身或其它进程占用时,就形成了死锁。

上面的解释引自维基,死锁有进程间的死锁和线程间的死锁,只要是并发情况,并且双方都在占有资源的情况下等待对方的资源,就会发生死锁。在实际情况下,很容易不注意锁,条件变量的时候而导致死锁。

线程池中的死锁情况

这次死锁发生在什么情况下呢?在最开始写线程池的时候,我设计了线程是可重用的,主要是通过Event信号实现,通过在每个线程核心工作代码执行完毕后会将自己归还到池中,然后等待Event信号。主线程会以循环超时阻塞的方式监视一个任务队列,当发现有任务时便会从线程池中取出一个线程,并设置它的任务和目标函数,然后去start或者resume,resume就是会设置Event信号让线程不再阻塞,这里,从池中取线程的方法_get和归还线程方法returnThread都已经加锁,_get和returnThread使用同一把互斥锁,因为在_get和returnThread方法里面对线程池对象以及分区对象都有状态修改并且有些操作有条件判断,因此必须加锁保证线程安全和同步。

这样就真正线程安全了吗?可以顺利按照预期执行了吗?看起来好像没有问题,并且我这里设置的分区数目是4,分区的初始容量是5,最大容量为20,故池的总大小为4*20=80,这样对于小数目的测试确实发现不了死锁问题。

考虑下面一种情况:

  • 线程池已经满了,任务队列里面来了任务,触发了_get方法,线程池中没有多余的线程,所以会阻塞在一个queue.get的方法上,我这里面queue是最好的分区(可用的最多)对象的一个变量,里面存放的是该分区拥有的线程的的一个唯一的id标示符,本来打算用ident即uid,但是只有在运行期再回分配,所以,采用了这个方法。
  • 正在运行的线程,核心功能函数运行完毕,想要归还自己到池中,由于主线程调用_get时获得了锁,一直不会释放,因为没有可用的线程,而想要归还自己的线程由于不能获得锁所以不能归还,就这样会一直耗着,发生了死锁。

死锁的解决

我们通过上面的描述,发现死锁的发生是因为条件等待时没有释放锁资源,仔细思考这句话,会发现其实我们也熟知的Condition就是为了解决这个问题的。

python里面的threading.Condition里面会内置Lock/RLock锁,并且可以条件等待时释放锁资源,这样,将之前的淡出的互斥锁改成condition,并且在queue.get方法时,先判断条件是否满足(有可用线程),如果可用则直接往后执行,否则cond.wait阻塞并且释放锁;另一方面,正在运行的thread通过returnThread时,也是通过cond.acquire来加锁,然后这样当主线程cond.wait的时候能够有机会获得锁,然后执行,当returnThread快要结束,已经归还后,cond.notify/conf.notify_all来通知在等待该条件的主线程。这样就能够顺利执行。

部分代码

  1. def get(self):
  2. self.cond.acquire()
  3. try:
  4. if self._shutdown:
  5. raise RuntimeError(‘ThreadPool already shutdown.‘)
  6. else:
  7. for partition in self.partitions:
  8. logger.info("parition #%d status:" % partition.get_partition_no())
  9. logger.info("current load: %.2f" % partition.get_load())
  10. logger.info("used size: %d" % partition.get_used_size())
  11. logger.info("max size: %d" % partition.get_max_size())
  12. partition = self.get_best_partition()
  13. logger.info("best partition: %d" % partition.get_partition_no())
  14. if partition.get_load() >= self.config.get_partition_upper_load_factor():
  15. self.expand_pool_for_partition(partition, self.config.get_partition_increase_step())
  16. logger.debug("partition avail size: %d" % partition.get_avail_size())
  17. if partition.get_avail_size() == 0:
  18. self.cond.wait()
  19. tid = partition.take()
  20. thread = self.object_pool[tid]
  21. del self.object_pool[tid]
  22. #update access time
  23. thread.set_atime(time.time())
  24. partition.increase_active_thread_count()
  25. logger.debug("active thread count after get: %d" % self.get_active_thread_count())
  26. return thread
  27. finally:
  28. self.cond.release()
  1. def _return(self,thread):
  2. self.cond.acquire()
  3. logger.info("return back...")
  4. try:
  5. if (time.time() - thread.get_atime()) > self.config.get_timeout():
  6. logger.info("destroy thread #%d" % thread.ident)
  7. self.factory.destroy(thread)
  8. else:
  9. self.object_pool[thread.tid] = thread
  10. self.partitions[thread.get_partition()].put(thread.tid)
  11. self.partitions[thread.get_partition()].decrease_active_thread_count()
  12. logger.info("return thread #%d back to pool on partition #%d" % (thread.ident,thread.get_partition()))
  13. for partition in self.partitions:
  14. logger.info("partition #%d status:" % partition.get_partition_no())
  15. logger.info("current load: %.2f" % partition.get_load())
  16. logger.info("used size: %d" % partition.get_used_size())
  17. logger.info("max size: %d" % partition.get_max_size())
  18. logger.debug("active thread count after return: %d" % self.get_active_thread_count())
  19. self.cond.notify()
  20. except Exception,e:
  21. print e
  22. #if return error, we should kill this thread
  23. self.factory.destroy(thread)
  24. finally:
  25. self.cond.release()
时间: 2024-10-10 19:56:41

线程死锁的思考的相关文章

线程系列08,实现线程锁的各种方式,使用lock,Montor,Mutex,Semaphore以及线程死锁

当涉及到多线程共享数据,需要数据同步的时候,就可以考虑使用线程锁了.本篇体验线程锁的各种用法以及线程死锁.主要包括: ※ 使用lock处理数据同步※ 使用Monitor.Enter和Monitor.Exit处理数据同步※ 使用Mutex处理进程间数据同步※ 使用Semaphore处理数据同步※ 线程死锁 □ 使用lock处理数据同步 假设有一个类,主要用来计算该类2个字段的商,在计算商的方法之内让被除数自减,即被除数有可能为零.使用lock语句块保证每次只有一个线程进入该方法. class Th

线程死锁

所谓的线程死锁,是指在多线程运行的过程中,线程1拥有锁a,而需要锁b来继续执行, 而此时,线程2拥有锁b而需要锁a来继续执行,那么此时会形成死锁,两个线程会同时等待. 在编程的过程中应尽量的避免线程死锁. 有时在面试中会要求写出一个死锁的程序演示,如下: 1 //写一个死锁程序 2 public class DeadLock { 3 //主程序执行 4 public static void main(String[] args) { 5 Thread thread1 = new Thread(n

在Linux下线程死锁的四个条件

一.死锁的原因和必要条件 1.死锁的概念 一般情况下,如果同一个线程先后两次调用lock,在第一次调用时,由于锁已经被占,该线程会挂起等待别的线程释放锁,然而锁正是被自己占着的,该线程又被挂起,没有机会释放锁,因此,就永远处于挂起等待状态了,这叫做死锁(Deadlock).另种典型的死锁情形是这样:线程A获 得了锁1,线程B获得了锁2,这时线程A调用lock试图获得锁2,结果是需要挂起等待线程B释放锁2,而这时线程B也调用lock试图获得锁1,结果是需要挂起等待线程A释放锁1,于是线程A和B都永

Java笔记六.线程同步、线程死锁

线程同步.线程死锁 在上一篇文章中,有一个模拟售卖火车票系统,在卖车票的程序代码中,极有可能碰到一种意外,就是同一张票号被打印两次多次,也可能出现打印出0甚至负数的票号.具体表现为:假设tickets的值为1的时候,线程1刚执行完if(tickets>0)这行代码,正准备执行下面的代码,就在这时,操作系统将CPU切换到了线程2上执行,此时tickets的值仍为1,线程2执行完上面两行代码,tickets的值变为0后,CPU又切回到了线程1上执行,线程1不会再执行if(tickets>0)这行代

JAVA笔记14__多线程共享数据(同步)/ 线程死锁 /

/** * 多线程共享数据 * 线程同步:多个线程在同一个时间段只能有一个线程执行其指定代码,其他线程要等待此线程完成之后才可以继续执行. * 多线程共享数据的安全问题,使用同步解决. * 线程同步两种方法: * 1.同步代码块 * synchronized(要同步的对象){ 要同步的操作 } * 2.同步方法 * public synchronized void method(){ 要同步的操作 } */ public class Main { public static void main(

atitit.线程死锁 卡住无反应 的原因in cmd调用的解决方案  v3 q39

atitit.线程死锁 卡住无反应 的原因in cmd调用的解决方案  v3 q39 1. 问题::线程死锁  卡住无反应1 1.1. 分类:: cmd调用,  net io  , file  io 调用, multi thread调用same var的时候儿..1 1.2. 原因readLine()是阻塞方法1 1.3. 调用same var1 2. 解决之道::2 2.1. 使用了cmd /k走死锁兰...改成个/c佐ok兰..2 2.2. Watchdog2 3. Ref3 1. 问题::

进程/线程死锁产生的原因以及如何避免死锁

线程死锁产生的必要条件: (1)互斥条件:一个资源每次只能被一个进程使用. (2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放. (3)不可剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺. (4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系. 如何避免死锁,这点我觉得Erlang模型值得参考.在编程过程中,有一些避免死锁的经验: (1) 等待某个资源时,使用超时机制(例如Erlang中的receive可以加一个超时): (2) 采用消息通信的通信机

GCD中的线程死锁问题

GCD 确实好用 ,很强大,相比NSOpretion 无法提供 取消任务的功能. 如此强大的工具用不好可能会出现线程死锁. 如下代码: - (void)viewDidLoad { [super viewDidLoad]; NSLog(@"=================4"); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"=================5"); }); NSLog(@"===

Atitit.线程 死锁 跑飞 的检测与自动解除 与手动解除死锁 java c# .net php javascript.

Atitit.线程 死锁 跑飞 的检测与自动解除 与手动解除死锁 java c# .net php javascript. 1. 现象::主程序卡住无反应,多行任务不往下执行 1 2. 原因::使用jv jprofile查看线程,原来俩个线程死锁了.. 1 3. Java的缺点,默认不能自动解除死锁 1 4. 自动检测与解除死锁::使用看门狗watchdog 2 4.1. 死锁检测算法(太麻烦,不推荐) 2 4.2. 硬件看门狗 2 4.3. 软件看门狗的实现--TIMER 2 4.4. LIN