最近被GUI多线程代码的死锁问题搞的有点头大,本文总结一下自己的所思所得,并不代表完全正确,因为毕竟没有去阅读tk的源代码。

mainloop,故名思议,它就应该是python程序的mainThread。这个GUI的mainloop,靠事件响应驱动。比如有一个button(A),点A,就是给这个mainloop传递了一个事件,这个事件最后会传递到A绑定的commnad函数中去执行。

不要阻塞tkinter的mainloop,就是不要在事件响应的函数中阻塞住,否则GUI会卡死。常见的现象是,A绑定的函数执行需要那么一点点时间,点击下去后,GUI就会卡住那么一点点时间。所以,大多数编程教材都建议使用多线程来规避GUI的卡死,即点击A后启动一个线程,A绑定的函数立即返回,释放GUI(表示GUI有可能接收别的事件的响应了),具体工作由线程完成。

由线程来完成工作,并规避GUI的卡死,是个很好的最佳实践,不过,我遇到的问题是,线程被GUI卡死了!

计算模型大概是这样的:多个线程通过操作一个Text控件就是往里写log;写log的执行流程代码中,有mutex,用来多线程间控制Text的状态;GUI的事件响应中,也有mutex.acquire;只要GUI出发这个事件,整个程序就死锁了。

为什么会这样呢?我想来想去,觉得出问题的地方,就是线程被GUI卡死了。为什么线程会被卡死呢?因为线程在mutex.acquire中在向GUI发事件(操作Text),而此时GUI在等待mutex,于是死锁。

GUI在一个事件没有处理完的情况下,是无法处理别的事件的。比如我们在A的绑定函数中sleep,整个GUI上其它地方都点不了(OS会记录这些事件,在GUI被释放后再把这些事件灌给它)。线程中对Text的操作无法返回,很可能就是因为GUI已经被卡死了。

奇怪的是,如果A绑定代码中也操作Text,不会卡死,而只是在GUI被释放后,一口气把所有的log全显示出来,log显示的过程只是感觉不流畅而已。区别在于mainloop中触发事件和非mainloop中触发(我个人的理解哈),也许存在内部事件和外部事件这样的区别,外部事件会被阻塞,即线程操作Text的代码被阻塞,而mainloop中操作Text的代码,只是更新延后。

总之,在GUI中如果有mutex.acquire这样的代码,是非常危险的,一不留神就死锁。不要阻塞mainloop的事件响应,用线程是OK的,用户友好。多线程之间有mutex没问题,只要GUI的mainloop没有被卡死。

-- EOF --