Delphi的库函数是否是线程安全的?(50分)

X

XiDao

Unregistered / Unconfirmed
GUEST, unregistred user!
大多数的Delphi书籍中都提到过,VCL不是线程安全的,就是说,在
线程中不能简单的直接调用VCL内容,必须通过一些同步机制,使用主线
程来间接调用VCL内容。
但是书籍上却没有提到过Delphi的运行时刻库(Sysutils)函数是否
是线程安全的,能否重入。
在《Delphi4编程技术内幕》一书中P106(5.2线程:一个简单的例子)
中,作者在例子中建立如下线程,其中用到了IntToStr函数:
function ThreadFunc(p:pointer):LongInt;stdcall;
var
i:integer;
DC:HDC;
s:string;
begin
DC:=GetDC(Form1.Handle);
for i:=0 to 10000000do
begin
s:=IntToStr(i);
TextOut(DC,10,10,PChar(s),Length(S));
end;
ReleaseDC(Form1.Handle,DC);
end;

并用如下方式启动线程:
procedure TForm1.Button1Click(Sender: TObject);
var
hThread:THandle;
ThreadID:DWORD;
begin
hthread:=CreateThread(nil,
0,
@THreadFunc,
nil,
0,
ThreadID);
if hthread=0 then
MessageBox(Handle,'NO Thread',nil,MB_OK);
end;

作者想通过这个例子来演示,如果使用了多线程,那么线程工作的时候,
程序仍能够很好的响应用户的输入操作,如拖动窗口移动等等。但是,不知道
作者想到过没有,如果连续按了两下Button1将会出现什么情况。
如果连续按了两下或者更多下Button1,则启动了多个循环显示的线程;
但是却出了很大的问题。在Win2000中报告,系统内存读取错误,这个错误就
是出在IntToStr这个函数上,看来这个函数重入时发生了错误!
Delphi的运行时刻库难道是不可重入的吗?就是说Delphi的基本函数
对线程来讲是不安全的吗?这个问题怎么解决?
 
你把绘图相关的语句全部注释掉再试一试.
 
是不是CreateThread方式不对?D4 Unleashed 说他的例子只
可以用在Win9x, NT(or maybe win2k) 要改一点的。
 
对于上面的问题,我做了许多尝试,如,开始的时候,我也以为可能是绘图
相关的语句是不能重入的,但后来看了一些书籍,无论是Delphi方面的还是Win32
方面的,都一再强调,对Form上的绘图,微软为了提高性能,都允许重入,所以,
我将IntToStr函数再线程中去掉,只留下绘图函数,改为如下:
function ThreadFunc(p:pointer):LongInt;stdcall;
var
i:integer;
DC:HDC;
s:string;
begin
s;='12342342134123';
DC:=GetDC(Form1.Handle);
for i:=0 to 10000000do
begin
// s:=IntToStr(i);
//不再使用IntToStr(i)函数。
TextOut(DC,10,10,PChar(s),Length(S));
end;
ReleaseDC(Form1.Handle,DC);
end;

然后,关于启动线程函数我改为:
procedure TForm1.Button1Click(Sender: TObject);
var
hThread:THandle;
ThreadID:DWORD;
i:integer;
begin
for i:=0 to 50do
//启动50个线程(可以是任意多个线程,只要计算机资源足够)
begin
hthread:=CreateThread(nil,
0,
@THreadFunc1,
nil,
0,
ThreadID);
if hthread=0 then
MessageBox(Handle,'NO Thread',nil,MB_OK)
else
closehandle(hThread);
end;
end;

经过上面改动之后,无论是Win2000、WinNT还是Win98,程序都运行的很好。
看来,绘图函数真的像微软说的,是允许重入的。那问题看来就是出在Delphi
的运行时刻库上了。(注,未去IntToStr函数的程序我在Win98上也试过,只要
线程一启动第二个,也会出问题)。
难道在多线程编程中,调用Delphi的运行时刻库函数也要进行手工同步
处理吗?是否Delphi提供了允许多线程调用库函数的同步开关?
 
此问题应该和inttostr无关.
我同样可以用一个例子证明: 只要将你上面语句中的绘图部分全部用
form.caption:=s
来代替,就一样可以正确执行, 说明问题处在pchar(s)这里.
 
呵呵,listen
 
不同进程s应该不同吧,那怎么会出问题呢?
 
delphi HELP
Consider that you have a local string variable that you need to initialize by calling a function that takes a PChar. One approach is to create a local array of char and pass it to the function, then
assign that variable to the string:
// assume MAXSIZE is a predefined constant
var
i: Integer;
buf: array[0..MAX_SIZE] of char;
S: string;
begin
i := GetModuleFilename(0, @buf, SizeOf(buf)); // treats @buf as a PChar
S := buf;
//statements
end;

This approach is useful if the size of the buffer is relatively small, since it is allocated on the stack. It is also safe, since the conversion between an array of char and a string is automatic. When GetModuleFilename returns, the Length of the string correctly indicates the number of bytes written to buf.
To eliminate the overhead of copying the buffer, you can cast the string to a PChar (if you are certain that the routinedo
es not need the PChar to remain in memory). However, synchronizing the length of the stringdo
es not happen automatically, as itdo
es when you assign an array of char to a string. You should reset the string Length so that it reflects the actual width of the string. If you are using a function that returns the number of bytes copied, you cando
this safely with one line of code:
var
S: string;
begin
SetLength(S, 100); // when casting to a PChar, be sure the string is not empty
SetLength(S, GetModuleFilename( 0, PChar(S), Length(S) ) );
// statements
end;
 
好象如此
g
 
我把XiDao第一个帖子中的代码运行了一遍,但是没有出现任何问题。我的环境是 d5+win2000 96m
我至少按了上百次button,所以我的系统变得很慢,但是没有出现XiDao所说的情况
 
我将线程中所有有关显示的语句去掉,现在线程如下:
function ThreadFunc(p:pointer):LongInt;stdcall;
var
i:integer;
DC:HDC;
s:string;
begin
// DC:=GetDC(Form1.Handle);
for i:=0 to 10000000do
begin
s:=IntToStr(i);
// TextOut(DC,10,10,PChar(s),Length(S));
end;
// ReleaseDC(Form1.Handle,DC);
end;

此时线程运行,还是发生错误。进一步改动如下:
function ThreadFunc(p:pointer):LongInt;stdcall;
var
i:integer;
DC:HDC;
s:string;
begin
// DC:=GetDC(Form1.Handle);
for i:=0 to 10000000do
begin
IntToStr(i);
// TextOut(DC,10,10,PChar(s),Length(S));
end;
// ReleaseDC(Form1.Handle,DC);
end;

此时,上面线程一旦启动两个以上,还是照样发生错误。
看来只要调用IntToStr(i)函数,一定会出错!大家可以在
自己的计算机中试一下就可以了明白了。只有将IntToStr(i)
此函数从程序中去掉,如改为下:
function ThreadFunc(p:pointer):LongInt;stdcall;
var
l:integer;
i:integer;
DC:HDC;
s:string;
begin
s:='12344312423';
l:=0;
DC:=GetDC(Form1.Handle);
for i:=0 to 10000000do
begin
// s:=IntToStr(i);
l:=l+i;
TextOut(DC,10,10,PChar(s),Length(S));
end;
ReleaseDC(Form1.Handle,DC);
end;

如上,一旦不调用IntToStr(i)函数,则线程非常好的运行开了。IntToStr(i)
函数就是不可以重入的!
Kangxy同志提到的我看了看,只是说有些函数需要pchar类型的参数,如何使
String类型用在pchar类型的地方,要进行的一些改动。不过IntToStr这个函数同
pchar类型没有任何关系,我们可以看一下Borland关于IntToStr函数的定义如下:
function IntToStr(Value: Integer): string;
begin
FmtStr(Result, '%d', [Value]);
end;

如果进一步查询Delphi源程序,则如下:
{ FmtStr formats the argument list given by Args using the format string
given by Format into the string variable given by Result. For further
details, see the description of the Format function. }

procedure FmtStr(var Result: string;
const Format: string;
const Args: array of const);

再继续查询如下:
function Format(const Format: string;
const Args: array of const): string;
查询到这里,就没有更多的资料可以查询了。
 
你只要不用Pcahr(s)就肯定没问题.
 
咦,怎么没人看见我的帖子呢。
再次强调一遍: 偶的机子上运行 inttostr(i) 就没出问题 。
 
elan: 你把循环次数改小一点,然后快速连续点击按钮就会出问题.
 
出问题了,我改称循环到 10000,然后狂点,
 
至少有一个解决办法。。。。。。。。。。。不要狂点。
这样就安全了。 :)
 
是个办法. 我经常这么干,每次点击之后就button1.enabled:=false;
执行完成后再enabled. 习习. :)
不过这里讨论的似乎不是这个,而是"的库函数是否是线程安全的"?
 
cAkk同志,这个问题肯定不是发生在pchar上面,今天我把线程改为:
function ThreadFunc(p:pointer):LongInt;stdcall;
var
i:integer;
DC:HDC;
s:string;
begin
// DC:=GetDC(Form1.Handle);
for i:=0 to 10000000do
begin
s:=IntToStr(i);
// TextOut(DC,10,10,PChar(s),Length(S));
end;
// ReleaseDC(Form1.Handle,DC);
end;

并先后在以下四台计算机上运行,均错误:
计算机A: 磐英双CPU主板+双PII400+256M内存+PWin2000W(2195)
计算机B: 华硕双CPU主板+双PIII450+256M内存+PWinNT4.0S(SP6)
计算机C: 华硕单CPU主板+PIII450+256M内存+PWin2000W(2195)
计算机D: 华硕单CPU主板+PIII450+256M内存+PWin98SE(2222)
这个错误肯定不是pchar导致的,也同Windows的版本不存在任何关系。
唯一需要注意的就是,在单CPU计算机上,错误并不是马上出现,而是运行
一段时间(时间长短不确定)才出现,双CPU计算机则立刻出错。看来是指针
问题导致出错。至于确切原因,我打算进一步查询一下。
 
顶部