2014-10-31

SingleThread下遇到的并发问题

手下某个小弟,有一天报告我说他写的某个Win32 Application有一个奇怪的Bug,搞了半天搞不定,向我寻求支援。Bug现象是:下载文件,完毕弹框提示,点掉之后报错,Crash。

通常而言,这种问题,往往是因为在释放、删除什么东西的时候,该做的事情没做对,比如对着一个对象的指针进行了重复delete之类。但看了下代码,没觉得这方面有什么问题。因为这是个SingleThread的程序,于是尝试用单步跟踪跟了一下,发现有一段代码似乎在所属对象析构之后还在跑。这就有点奇怪了:SingleThread的Application,不应该有这种属于MultiThread的毛病才对。Socket模型用的是AsyncSelect,也就是说“异步”是用Windows消息做出来的,并不是真的“并发”。那么到底是哪里不对劲呢?

再接下来分析发现,虽然是SingleThread,但最后出错前弹的那个提示框,是在OnReceive的时候通过SendMessage去弹的。这样就有眉目了:ModalDialog并不阻塞ParentWindow的消息循环,所以在弹框等待用户确认的时候,消息循环收到了OnClose,于是Socket对象在用户点击确认按钮之前,其实已经被Destroy了。之前还没跑完的OnReceive,接着再跑的话,当然只能Crash了。

分析到这里,问题就已经很明白了:这就跟MultiThread下临界区没加锁一样嘛。你以为SingleThread下每个函数就都是原子操作,不会被乱入的东西打搅?呵呵,你一DoModal就会给你再嵌个消息循环进去的。可怜很多小弟连DoModel的原理都没搞懂就开始写程序了。我上次还听几个小弟在争论相关问题呢。不是说写程序必须啥都弄明白才能开始,但若是只拎半壶水就开跑,将来就难免会碰上这种“奇怪”的问题。

要修正这个问题,也很简单,改成用PostMessage让MainWindow自己去处理弹框的事情就可以了。不过有点奇怪的是,在XP下好像不会看到错误现象。Win7下直接运行EXE也不报错。只有通过两层以上的CreateProcess去调用,才会看到现象。难怪没什么用户报告这个问题。是不是OS觉得这个EXE反正会很快地Over掉,有些错误就不报算了?看来微软在私底下还是有一些没告诉大家的小动作的哈哈。

总结一下,这个案例教育我们:

  1. 不要以为只要是SingleThread就一定不会遇到并发问题。
  2. 前/后台逻辑应该要区分清晰,是后台代码就别抢前台的活儿。
  3. 还有,SendMessage/PostMessage不要不经大脑就乱用。