[Flash/Flex] 使用gdb查找多线程bug
发布时间:2020-12-15 18:13:04 所属栏目:百科 来源:网络整理
导读:? 多线程是一个有用的工具,但是使用多线程的程序会很容易产生各种类型的在单线程程序中不会发生的bug。当多个线程同时访问相同的数据时就会存在竞态条件。同步基元,如互斥体和环境变量,在避免竞态条件是很有用的,但是当使用不当时会导致其它问题。尤其是
? 多线程是一个有用的工具,但是使用多线程的程序会很容易产生各种类型的在单线程程序中不会发生的bug。当多个线程同时访问相同的数据时就会存在竞态条件。同步基元,如互斥体和环境变量,在避免竞态条件是很有用的,但是当使用不当时会导致其它问题。尤其是当多个线程争用同一个互斥体,以这种方式哪个线程都无法进行下去时,死锁的情况就发生了。这篇文章演示的就是如何在FlasCC程序中使用gdb查找死锁及类似的线程bug。
死锁是什么样的 ? ? 用两个线程就可能产生一个简单的死锁,线程1和线程2,还有两个互斥体,A和B。假设线程1获取互斥体A而线程2获取互斥体B。然后线程1试图获取互斥体B,因此会等待线程2将互斥体B释放。如果线程2试图获取互斥体A,它会等待线程1的释放,然后就会导致两个线程永远的等待。 ? ? 为了发现和调试一个死锁,你可以使用调试器中断等待的线程并检查它们的状态,如它们的堆栈跟踪。例如一旦你得到这个信息,你就可以确定实际上线程已经处于死锁状态而并不是仅仅执行一些长时间运行的操作。 背景 ? ? 在开始使用gdb之前,让我们讨论一个底层Flash调试器是如何工作以及它是如何与线程相关联的。当你编译ActionScript代码进行调试时,编译器将被称为debuglines的特殊操作码插入到它所产生的ActionScript字节码中。Debuglines有两个目的:他们为编译器提供行编码信息并且它们能够在字节码中标调试器能够暂停执行你的程序的位置。当Flash播放器遇到一个debugline时,它会检查看是否需要暂停这个程序,因为其进入断点,已经完成进一步操作,或者其它任何原因。一个线程的执行只有当它遇到debugline时才会停止。〔1〕 ? ? 互斥体也可以将一个线程的执行停止:如果一个线程尝试去获取一个当前被一个不同的线程所持有的互斥体,它就会一直等待直到这个互斥体可用为止。如果几个线程同时尝试获取同一个互斥体,只有一个线程会成功并且继续执行;其它的就会等待。假设此时调试器试图中断这些线程。获取到互斥体并且正在执行的那个线程将最终遇到一个debugline,暂停运行,并将控制权交给调试器。但是那些等待获取互斥体的线程并不会将控制权交给调试器,因为在接收到调试器的停止请求后他们还没有发现debugline。因此,那些等待着互斥体的线程直到获取到互斥体,恢复运行,并且遇到debugline才会被调试器所中断。 一些FlasCC本质 ? ? 上面所讨论的适用于使用flash.concurrent.Mutex同步类的ActionScript程序。大多数FlasCC程序会使用一个互斥体的pthread版本 pthread_mutex_t 来代替。毫不奇怪,pthread_mutex_t是使用互斥体来实施的,但是pthread_mutex_t和ActionScript的互斥体类实例之间却没有一对一的映射。相反,pthread中所有的同步是由一个单独的ActionScript互斥体来处理的。当几个线程处于等待中时,因为它们调用了如pthread_mutex_lock或pthread_cond_wait函数,他们每一个短时间地持有这个互斥体。这样当一个线程持有了互斥体,调试器能够将其中断,但是这样做就阻止了其它线程获取互斥体。由于处于等待中的线程只有当它持有了互斥体才能被调试器所中断,所以在任何一个时间点只有一个等待的线程可以被调试器所控制。 一个简单的示例 ? ? 考虑到这些限制,让我们调试一个简单的死锁程序。下面就是代码: ? ?? ? ? 注意这个示例中使用了一个条件来确保两个线程确实互相形成死锁。这是为了演示示例,并不是非常实现的。通常死锁情况只是时有发生。 ? ? 开始通过编译这个程序进行调试: ? ? gcc -o dlock.swf -g -O0 -pthread -emit-swf dlock.c ? ? 在调试器中运行该程序,不必麻烦地设置任何断点或任何类似的事,我们只是让程序运行至死锁然后我们将其中断看看程序卡在哪里了。由于我们单独调试每个线程,所以首先要确保启用non-stop模式:
? ? 一旦“about to deadlock”这段文字出现,在gdb中按下CTRL+C来将程序中断。你应该会看到如下的一些信息: ? ? 此时,调试器已经中断了UI线程,但是我们的死锁线程仍然试图在后台运行。但是既然他们处于死锁状态,那么它们就不能做什么。我们用info threads命令让gdb给我们展示一下每个线程在做什么:
? ? 向后追踪的结果显示这个线程已经调用了__pthread_mutex_lock,因此我们知道了它正处于试图锁定一个互斥体的过程中。由于这个线程当前处在avm2_msleep,我们可以说它正在等待另一个线程释放这个互斥体。第12帧显示了我们从程序的第31行运行到这里: ? ? pthread_mutex_lock(&second_lock); ? ? 为了看3号线程的堆栈跟踪,我们需要让其获取同步互斥体。一旦我们这样做了,它就会运行直到遇到一个debugline并将控制权交给调试器。虽然通常线程不会持有一个同步互斥体很长时间,但是当前互斥体被2号线程所持有,而这个线程在调试器中被暂停了。恢复2号线程会允许其释放互斥体并且给了3号线程一个机会,让它把控制权交给调试器:
|