3. 线程中常见的问题。
1) 回调函数引起的死锁。
A回调线程B中的函数,而在线程B中,再去对线程A进行操作(比如删除A)。
发生的现象:程序死掉。
2) 使用同一资源未加保护引起问题。
A和B同时去对窗体上进行绘图操作,界面可能花掉,也可能黑掉。
出现的现象:界面不再刷新,变成黑色。(最好不要在子线程中去更新界面UI,可以使用消息来更新)
3) 线程锁使用不当造成死锁。
线程A利用线程锁锁住资源A后,再去试图访问资源B,线程B利用线程锁锁住资源B后试图去访问资源A。这样就发生了线程互锁。
程序结果:线程死掉。
4) 未加线程保护产生异常。
线程A获取到了对象X(步骤1)的引用后被挂起(步骤2),而接下来线程B却删除了X(步骤3),线程A再次唤醒后访问对象X出错(步骤4)。这个问题是多线程中最容易被忽略的地方,也是异常最可能发生的情况。
600) this.width = 600;">
程序结果:线程异常。
5) 消息在线程同步中的问题。
先说说消息的一些基本问题(有关消息的处理部分在Windows 2000源码private\ntos\w32\ntuser\kernel\input.c文件中):
消息队列的建立:线程在刚建立的时候,是没有消息队列的。当有界面UI操作函数被调用的时候(比如CreateWindow),Windows就会为该程序建立一个消息队列,同样,通过调用PeekMessage/GetMessage可以强制操作系统为线程建立一个消息队列(参看MSDN关于PostThreadMessage的说明)。
消息的正常处理流程:线程通过GetMessage/PostMessage获取消息,然后通过TranslateMessage进行字符转换,接下来,通过User32.dll模块的帮助最后调用到相应窗口的窗口过程(RegisterClass时传入的窗口过程)。
消息重入问题:所谓的消息重入,就是在消息处理过程中再调用SendMessage发送消息给目标窗口。如果控制不好,就会引发异常。一个简单的例子:在WM_PAINT中再去SendMessage(hWin, WM_PAINT, 0, 0)。其结果就是堆栈溢出异常了(stack overflow)。(相当于WndProc在进行无限递归),Delphi代码如下(可以自己感受一下“Stack Overflow”是怎么一回事,等异常后调出CallStack看看^_^):
procedure OnPaint(var tMsg: TMessage); message WM_PAINT; // Interface
// Implementation
procedure TForm1.OnPaint(var tMsg: TMessage);
begin
SendMessage(Self.Handle, WM_PAINT, 0, 0);
end;
SendMessage & PostMessage: SendMessage发送一个消息给窗口,同时,操作系统会去直接调用窗口的窗口过程而不经过线程的消息队列。而PostMessage则仅仅是将消息投递到消息队列,应用程序通过GetMessage/PeekMessage获取消息处理后再交给系统分发消息。
消息在多线程中的问题:
分析一个具体过程说明在多线程中因消息而引起的问题:
线程A(主线程)通过GetMessage->DispatchMessage,接下来通过User32模块的帮助调用WndProc进行消息处理的过程对RichEdit中插入一张图片。其步骤如下:
1. 在RichEdit中定位要插入的位置(X, Y)。RichEdit->SetSel(X, Y)
2. 创建OLE对象。
3. 获取ClientSite接口插入对象。
线程B通过SendMessage调用WndProc要求在RichEdit的末尾添加一段文字。其步骤如下:
1. 定位到RichEdit末尾。RichEdit->SetSel(-1, -1);
2. 调用RichEdit->ReplaceSel(sText)插入文本。
假如线程A在步骤1刚运行完毕后,其运行时间片结束,线程被挂起,当前的状态被保存到线程A的堆栈中。线程B开始运行,线程B插入文本完成返回,线程A重新被唤醒,开始执行步骤2,3, 而这时,线程B已经改变了当前的插入位置,线程A并不知道,于是,就会出现插入的图片错位现象。
归根结底,是线程的同步问题。
结论:尽量用PostMessage而不是SendMessage。
6) 异常引起的问题。
看这段代码:
CCriticalSection m_cLock;
m_cLock.Lock;
// Do something
m_cLock.Unlock();
初看是没有什么问题,但是,如果我们在DoSomeThing的时候产生了异常,那么UnLock代码将不会被执行,于是,线程锁将一直处与加锁状态,其他线程将无法访问。
m_cLock.Lock;
try
{
// Do something
m_cLock.Unlock();
}
catch(…)
{
m_cLock.Unlock();
}
在Delphi中处理这种情况很简单:
m_cLock.Lock;
try
// Do something
finally
m_cLock.Unlock;
end;
以上是我在多线程中所遇到的一些问题的总结,希望能对大家有用。
上一页123下一页