这个多线程程序错在哪儿?(书上的一个例子) ( 积分: 300 )

  • 主题发起人 主题发起人 刘麻子
  • 开始时间 开始时间
好的,谢谢各位,才上来,我看看先。[:D]
 
SuspendThread和MessageBox放在一个线程里,估计就会出问题.
 
问题已经初步解决!!
非常感谢wolaixue大哥拔刀相助,后又幸得QQ上的详细指点,使小弟豁然开朗。
总体来讲,可以认为是系统内部的问题,但也是由于我们不注意安全而引发的。
 
WaitForSingleObject()虽然使得新线程的SuspendThread()失去意义,因为程序本来是想演示如何控制线程的,但是他可以保证主线程立即停止,不执行到未知的可能需要互斥的地方,这样也就间接说明了出错的原因..原来说的Sleep()就不出错,也是不对的,是一种假相,它只是使出错的几率变小..好吧,暂且这样吧,谢谢各位参与. :)
 
icysword兄, 原来的代码中, 试图挂起的是主线程, 而不是自己
 
另外关于CreateDialog,作一点补充,和本题无关,权当增加dfw资料库.. :D
1.调用CreateDialog实际上是调用了CreateDialogParam,它作了什么呢,大致如此:
HWND CreateDialogParam(HINSTANCE hinst, LPCSTR lpTemplateName, HWND hwndOwner,
DLGPROC pfnDlgProc, LPARAM lParamInit)
{
// Find the dialog box template resource in the EXE file.
HRSRC hrsrc = FindResource(hinst, lpTemplateName, RT_DIALOG);
HGLOBAL hglblRes = LoadResource(hinst, hrsrc);
// Obtain the memory address of the dialog box template.
PDLGTEMPLATEEX pDlgTemplate = (PDLGTEMPLATEEX) LockResource(hglblRes);
// Create the parent window and child windows using the
// template contained in the memory block.
HWND hwndDlg = CreateDialogIndirectParam(hinst, pDlgTemplate, hwndOwner, pfnDlgProc, lParamInit);
// NOTE: Windows NTdo
es not require that resources be unlocked or freed;
// however, Windows 95do
es have this requirement. It is always safer to
// clean up than to leak memory for your process.
UnlockResource(pDlgTemplate);
FreeResource(hglblRes);
// Return the handle of the parent window.
return(hwndDlg);
}

2.而CreateDialogIndirectParam的动作,应该就是分析内存块,为每个窗口(包括控件)调用
CreateWindowEx,所以,CreateDialog和CreateWindowEx区别应该不大,只要是更方便 ..
3.详见《Windows 95 程式设计指南》 http://www.2ccc.com/article.asp?articleid=2063
 
呵呵,原来如此
 
这个问题,很可能是系统在MessageBox处理的时候,需要访问主线程导致的。
如果在MessageBox时,如果访问主线程,就一定会导致这个现象。可以把GetActiveWindow改成GetForegroundWindow,一定会死掉!因为主线程无法相应消息,无法返回。即使按照wolaixue的方法,同样会有这个问题。
所以,如果要真正的保证没有问题,一定要保证线程中不需要任何访问到主线程的地方。
为什么一个简单的MessageBox也会导致这个问题?而且不是每次都出来,很奇怪,即使把GetActiveWindow改为0,也会可能死掉。
 
对啊,开始我也猜测是GetActiveWindow()的问题,当时我是这样想的,如果,主线程挂起,然后
子线程SendMessage()给主线程,肯定死,因为不同线程之间SendMessage()是阻塞式的..如果
MessageBox()指定了属主窗口,则MessageBox()可能会对属主窗口作一些动作,其中就可能包
括给它发消息..但是,后来俺试着把MessageBox()第一个参数改成0也不行..所以就不知道为
什么了.. 欢迎继续讨论..
 
进一步的猜测:
即使不为MessageBox()指定属主窗口,是否也会造成调用MessageBox()的线程对
当前窗口SendMessage()某些消息,比如,可能SendMessage()一个WM_KILLFOCUS?
所以我在建立线程前尝试使主窗口最小化(就不是当前窗口了),代码如下:
IDC_SUSPEND: // 挂起主线程
begin
// 加了这一句: 最小化
SendMessage(hWnd, WM_SYSCOMMAND, SC_MINIMIZE, 0);
// 建立线程前禁用按钮
EnableWindow(hWndCtl, FALSE);
// 虚拟句柄 -> 实句柄
DuplicateHandle(
GetCurrentProcess(), GetCurrentThread(),
GetCurrentProcess(), @hThreadPrimary,
THREAD_SUSPEND_RESUME, FALSE, DUPLICATE_SAME_ACCESS);
// 建立线程并减少计数
CloseHandle(begin
Thread(nil, 0, @ThreadFunc, Pointer(hThreadPrimary), 0, dwThreadID));
end;

我这边暂时没有发现程序再死掉了,欢迎继续讨论.. :)
 
我虽然对 API 很感兴趣,可是最近事情特别多,有一段时间没有在这些细节上工作。
文中的代码现在已经看不太懂了。
我写程序时基本上是实用主义,能解决问题就可以了。对于多线程,我的理解是:只
要分成主线程(GDI线程)和工作线程(非GDI线程)二类。工作线程无论如何不要去
调用 GDI 代码,代之以同步手段将事件投递至主线程工作,以免不必要的麻烦。
上面说的是我的思路,有点文不对题,权当技术能力不足的一个借口吧 :)
 
肯定不是GetActiveWindow的问题!
GetActiveWindow是取当前线程的活动窗口,与主线程无关,其实在这个线程里肯定是0,所以说改成0还有问题!
真正的问题应该是出在第二线程SuspendThread主线程时,主线程的执行代码处于未知位置(可能每次都不一样),某些位置(至于何时出现这种可能,需要再分析)会导致死锁(两个线程都在等待)。
我加上WaitForSingleObject等待第二个线程结束就是为了保证可以控制!
在主线程中加入Sleep一般不会出错,是因为经过Sleep,相对“加快”了第二个线程执行的速度,当它SuspendThread主线程时,主线程的执行代码可能还在Sleep中,或者刚执行到下一条语句,没有走到引起死锁的地方,但不是没有出错的可能啊,不信你在第二线程SuspendThread主线程之前加几个Sleep试试。
 
我写了一个线程, 好像没有问题的
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

SusTest = class(TThread)
private
{ Private declarations }
protected
procedure Execute;
override;
end;

var
Form1: TForm1;
hMain: THandle;
implementation
{$R *.dfm}
{ SusTest }
procedure SusTest.Execute;
begin
MessageBox(0, 'Thread started', 'hello', MB_ICONINFORMATION);
SuspendThread(hMain);
// 弹出消息框
MessageBox(GetActiveWindow(),
'The Primary thread is suspended.'#13#10 +
'It no longer responds to input and produces no output.'#13#10 +
'Press OK to resume the primary thread &
exit this secondary thread.'#13#10,
'SchedLab', 0);
// 恢复主线程
ResumeThread(hMain);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
t: SusTest;
begin
t := SusTest.Create(True);
t.Resume;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
hMain := GetCurrentThread;
DuplicateHandle(GetCurrentProcess, hMain,
GetCurrentProcess, @hMain,
0, False,
DUPLICATE_SAME_ACCESS);
end;

end.
 
应该不是GetActiveWindow, 在MessageBox处下断点调试,当它中断的时候, GetActiveWindow获得的并不是主线程窗口,一样会死.
 
会不会是MessageBox需要释放引起的问题???
如果把MessageBox的部分替换成WriteFile,就没问题...
 
晚上才上来, 谢谢各位关注, 谢谢. :-)
1. 原来GetActiveWindow()是取调用线程活动窗口,没注意看帮助,嘿嘿.. :D
2. 为什么先把主线程的窗口最小化,就没问题了呢,这样就不会和新线程争夺GDI资源了?
3. 另外,如果仅仅调用MessageBox()而不调用SuspendThread()就没问题,
反过来,仅仅调用SuspendThread()而不调用MessageBox(),也没问题..
4. 对于老王大哥(wolaixue)的观点,我的初步理解:
"主线程执行到需要互斥的地方被挂起了,然后,新线程也执行到了那个地方.."
感觉有一定说服力,可以接受,注意,这里出问题的(需要互斥的部分)应该是系统内部代码
先回复这么多, 其他朋友的代码我马上看看, 先谢了.. :)
 
savetime兄: 您说的很对!! 平时俺也一直力争那样作,即界面部分只由一个线程来负责.
icysword兄: 是的,这里应该不是GetActiveWindow()的问题
lich兄: 惨,您的代码在俺这边导致多次死机重启,后来试着把
MessageBox(0, 'Thread started', 'hello', MB_ICONINFORMATION);
注释掉,就没死机了,而且程序也不会死掉.这是我的初步测试结果 :)
 
(2005-04-16 08:34:20) 老王
某些位置(至于何时出现这种可能,需要再分析)会导致死锁(两个线程都在等待)
----我仔细调试了一下,是在第二线程SuspendThread主线程后调用MessageBox时程序死锁,具体原因可以跟踪MessageBox这个API函数来分析

(2005-04-16 09:05:25) 老王
我这么估计:
主线程被停时处于临界区内,第二线程也需要进入该临界区,于是第二线程要等主线程退出临界区,而主线程却又等第二线程来恢复它,于是。。。。。。
 
谢谢老王,有机会分析一下MessageBox的代码. :-)
 
后退
顶部