多线程编程中的主界面安全处理

来源:互联网 发布:java version是什么 编辑:程序博客网 时间:2024/05/16 19:01
 现在,随着多核心CPU的流行,很多开发人员,不管自己的程序是否适合用多线程,都会使用多线程编程,通常就是一个主界面线程加N个工作线程,而且,常常用单线程编程的思维来操作主界面,将某些界面对象,例如CList,传到工作线程中去,让工作线程直接操作界面对象,大多数情况下,还可以运行的挺好,没问题,但是真的没问题吗?哈,问题大了。例如
UNIT MyWorkThread(LPVOID pParam)
{
....
CList * plist;
plist=(CList *)pParam;
CString str;
int i,iresult;
for(...)
{
   //something you do
   str.Format("Hi , this is work thread :%d , current result is:%d",i,iresult);
   plist->AddString(str);
}

return 0;
}
这样的代码多的不得了,而且,在很多时候,它会正常工作的,但是,它不安全,为什么呢?很多书籍,包括WINDOWS核心编程,[当然UNIX中书籍里讲到主界面的就更少了],都没有提及,主界面的操作其实是个带锁的操作,也就是操作系统将强制保证,同一时间一个进程内最多只有一个界面操作,例如你的消息响应函数就是一个界面操作,不管你的电脑有几个CPU核心,不管你的进程有多少个工作线程,一个界面同一时间只能有一个操作,其他操作将被阻塞,直到前一操作完成。 上面这样的代码,违反了 工作线程不直接操作界面对象 的规则,是很危险的,代码量一大,用到具体的程序中,很容易导致死锁定,例如,在网络程序中,假如采用这样的代码,当连接断开的时候,主界面收到消息,要断开连接,并等待工作线程退出,而工作线程正等待修改主界面对象,而主界面对象的操作,由于正在执行断开消息的响应,在等待工作线程结束,这样就发生了死锁,这个教训我是在开发FtpAnywhere客户端软件的时候,经历了上百次的模拟才发现问题的根本并解决的,不过你的运气比较好,因为前面已经有我给你铺了路,你可以少走很多歪路了。。。。。。

这种方法不行,于是,有人改进了,采用发送消息的方法,我见得太多了,说实话,从看见的第一眼起,我就觉得,用这个代码的人可能对多线程和对象安全根本没概念.....
AfxGetMainWnd( )->PostMessage(.....)
它将(const char *)str作为lparam参数传递过去了,汗,大汗啊,CString对象是线程安全吗?准确的说,CString类是线程安全,但是它的具体对象,则不是线程安全,在跨线程的调用中,必须被同步保护,如果在主界面响应消息前,str中的缓冲被重新分配了,怎么办?内容修改了,怎么办?也许,你100次运行前99次都成功了,你能保证第100次也能成功?其实,稍微用点心思,就可以发现问题了,两个线程中的CString,没有任何同步,出问题是正常,不出问题,那才是见鬼了

既然直接操作界面对象不行,用消息传递也不理想,那么如何解决呢?其实很简单,

全局 缓冲->一般需要你自己编写一个类,然后用CArray或者vector等定义为一个数组,用来缓冲各类消息
锁->这是针对全局缓冲的,多线程操作中,对这个消息缓冲必须加锁
消息映射->定义在全局缓冲中加了需要操作的界面数据后,发送什么消息,例如 WM_USER+1
一个简单点的数据定义
Class MyMsgClass
{
public:
  int itype;//消息类型
  int iobj;//修改哪个界面对象,例如EDIT还是LIST或者其它
  CString str;//字符串消息....
  .....你自己的其他定义
  .........
}


当工作线程有数据需要显示,
加锁,
MyMsgClass然后放到全局缓冲
解锁
并给主界面一个消息 AfxGetMainWnd( )->PostMessage(...)


主界面接到消息后
主界面会调用消息响应函数,该函数执行
加锁
从全局缓冲读数据,并删除缓冲中指定数据MyMsgClass
解锁
然后根据MyMsgClass中的信息,主界面负责写数据到具体的界面对象

这种操作方法也许不是最高效,但是它是多线程安全的,因为它是用我自己代码的无数次失败换来的,如果你有更高效的处理方法,请告诉我,非常感谢您。
danscort#nbip.net   #请换成@

原创粉丝点击