线程真不是好玩的

早就耳闻线程是一个可怕的东西,弄不好会让人疯掉的,所以长期以来,我一向对线程相当谨慎,甚至有点敬而远之,我相信
1) 决不设计过于复杂的多线程代码;
2) 必须依靠多线程实现的逻辑,一定要能够相当简单。
如果多线程逻辑复杂了,到时候出了错都不知道什么地方错了,而且错误无法复现,这是最要命的。

最近需要用QtE做一个UI相关的库,问题是,使用这个UI库的程序是在非UI线程上调用这个库的,而QT的创作者Trolltech的技术人员谆谆教诲 我们不要在非UI线程上做UI操作。说道这个要求,倒不是只有QT这么干的。因为UI操作最终要作用于存储显示内容的内存资源,如果容许多个线程能够操作 这个资源,毫无疑问要想不出岔子,就需要用上线程同步,线程同步的代码谁来写呢?如果UI库自己来作同步,那么效率就很成问题,如果让使用UI库的 programmer来做同步,那么programmer又会很难受,所以几乎所有的UI库干脆要求只有一个线程能够做UI操作,这个线程就是UI线程, 其工作就是和一个抽水泵一样不停的接受事件然后分配给特定的widget。但是在QT上面,如果违反这个原则,在非UI线程上做UI操作,很有可能不会发 生任何异常,但在某个时候,问题又会产生,“偷走你的银行存款,偷走你的女朋友“(Trolltech开发人员如此描述可能发生的问题),所以,要想保证 质量,必须保证所有的UI操作都在UI线程上面进行。

回到我的工作上来, 为了达到能够在非UI线程(在QT的应用程序里,除了UI进程,其他的进程都叫“非UI线程“),我不得不用上postEvent,用这种方式,在非UI 线程上抛出一个event,这个event会进入进程的event loop,然后被UI线程的抽水泵抓住,这样,非UI线程就通过这种方式把UI操作转移到UI线程上。

一切都进行得很好,单元测试也很顺利,但是使用我的库的程序员报告说调用一个函数的时候偶尔会出现Segmentation Fault,因为不是总发生这种错误,第一反应就是和线程相关的逻辑有问题。为了复现这个错误,我编了一个只使用这个库里一个功能的程序,为了模拟在非 UI线程中调用这个函数,我在一个死循环中通过pthread_create创建线程,在线程里面创建并显示一个widget,然后隐藏并释放它。这也算 是一个压力测试。结果程序运行到261次循环的时候就被Killed了,这和报告的Segementation Fault不一样,Killed一般意味着内存不足了,我仔细看了一下库的代码,逻辑很简单,new过的东西都被delete了,应该不会产生内存泄露, 我有运行了一遍这个测试程序,还是被Killed了,而且还是杂261次循环的时候,我就心存疑问了,为什么总是在261次的时候,肯定是什么资源没有被 释放,261次之后就消耗光了。当天没有找出什么端倪来,在回家的路上想到,pthread_create一般是需要被pthread_join的,会不 会是因为没有pthread_join,所有有资源没有释放呢。第二天把程序修改了一下,不用pthread_join,可以把pthread的属性设成 不用join
  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  pthread_t tid;
  pthread_create(&tid, &attr, &thread_show, this);
  pthread_attr_destroy(&attr);

这样killed的问题没有,但是,Segementation Fault的问题出现了,经过反复运行测试程序,发现Fault出现的地方总是在delete一个widget之后,原来,我原来的代码中虽然创建和显示 widget的code保证是在UI线程上执行的,但是delete这个widget却很马虎的是从非UI线程上执行的。把这个修改过来之后, Segementation Fault就再没发生过。

这个bug给了这样的教训
1) pthread_create产生的线程占用资源,通过pthread_join回收资源,或者设成detached thread,这样就不需要pthread_join了,线程结束运行自动释放资源;
2) 对UI的擦操作必须在UI线程上,不仅指的是widget的创建和显示,还包括widget的删除。
阅读更多
上一篇有限状态机的实现
下一篇Diff程序的原理
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭