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

  • 主题发起人 主题发起人 XiDao
  • 开始时间 开始时间
没办法了,我没有双CPU, 如果不用pchar在我机器上面是没问题的.
 
在sysutils的后面有其它其现的代码:
procedure FmtStr(var Result: string;
const Format: string;
const Args: array of const);
var
Len, BufLen: Integer;
Buffer: array[0..4097] of Char;
begin
BufLen := SizeOf(Buffer);
if Length(Format) < (BufLen - (BufLen div 4)) then
Len := FormatBuf(Buffer, BufLen - 1, Pointer(Format)^, Length(Format), Args)
else
begin
BufLen := Length(Format);
Len := BufLen;
end;
if Len >= BufLen - 1 then
begin
while Len >= BufLen - 1do
begin
Inc(BufLen, BufLen);
Result := '';
// prevent copying of existing data, for speed
SetLength(Result, BufLen);
Len := FormatBuf(Pointer(Result)^, BufLen - 1, Pointer(Format)^,
Length(Format), Args);
end;
SetLength(Result, Len);
end
else
SetString(Result, Buffer, Len);
end;
看看代码,是可能出问题,
不过你可以把
Buffer: array[0..4097] of Char;
改成动态分配再释放可能就没问题了。
或重新定义一个inttostr吧,反正:
function IntToStr(Value: Integer): string;
overload;
 
出问题了,我改称循环到 10000,然后狂点,
 
应该是DELPHIString类型的问题
程序该为如下,决无问题
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls ;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1 : TForm1 ;
Index : Integer ;
implementation
{$R *.DFM}
{$H+}
function ThreadFunc(p:Pointer):LongInt;stdcall;
var
i : LongInt ;
DC : HDC;
c : array [0..128] of Char ;
begin
Result := 0 ;
DC := GetDC(Form1.Handle);
for i := 0 to 1000000do
begin
FillChar(c , SizeOf(c) , #0) ;
FloatToText(c , i , fvCurrency , ffGeneral , 0 , 0) ;
TextOut(DC,10,10,c,StrLen(c));
end ;
ReleaseDC(Form1.Handle,DC);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
hThread :THandle;
ThreadID : array [1..255] of DWORD;
begin
Index := Index + 1 ;
if Index > 255 then
exit ;
hthread := CreateThread(nil,
0,
@THreadFunc,
nil,
0,
ThreadID[Index]) ;
if hthread = 0 then
MessageBox(Handle,'NO Thread',nil,MB_OK);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Index := 0 ;
end ;
end.
 
Delphi 自己定义的字符串类型(AnsiString,String,WideString,ShortString)
可能就不是可以线程重入的
 
赞同amo同志的意见,问题是出在IntToStr函数上。
分析出问题的原因,更赞同cmldy同志的意见,导致IntToStr函数出现问题的根
本原因在于它使用了Delphi内置的一个非常特殊的类型:String类型。(备注:
cmldy同志提出的例子可以正确运行)
特作以下程序,这个程序非常简单,但是因为使用了String类型,而会导致线程
发生错误。
function ThreadFunc3(p:Pointer):LongInt;stdcall;
var
a:String;
l:integer;
begin
for l:=0 to 10000000do
begin
a:='Hello!';
a:=a+'你好!';
end;
end;

procedure TForm1.Button3Click(Sender: TObject);
var
hThread:THandle;
ThreadID:DWORD;
i:integer;
begin
for i:=1 to 10do
//连续启动十个线程,只要启动线程超过两个就会发生错误
//如果仅仅启动一个线程,绝对不会发生错误
begin
hthread:=CreateThread(nil,
0,
@THreadFunc3,
nil,
0,
ThreadID);
if hthread=0 then
MessageBox(Handle,'NO Thread',nil,MB_OK)
else
closehandle(hthread);
end;
end;

上面的线程函数ThreadFunc3非常简单,仅仅是来回改变字符串a:string的数
值,一旦启动两个以上的线程,则会出错。
线程重入发生错误的原因一般是因为不同线程之间共享一共同资源,而因没有引
入同步访问此共同资源的同步控制器而导致发生错误。共同资源如:全局变量、全局
堆等。而局部变量(函数自己定义的简单变量)、局部栈等的访问则不会导致发生线
程重入的错误。
String类型我们可以对它做一些仔细的分析,String在Delphi中分三种子类型,
分别是ShortString、AnsiString(LongString)、WideString。这三种类型中,
ShortString属于一种简单的类型,是可以在函数(线程)局部堆栈内存取的。
而Delphi默认的String类型,是AnsiString类型,这个类型就是一个很复杂的类型。
AnsiString类型长度从4字节到2GB字节,是一个动态可以改变大小的类型。线程
的局部堆栈是难以储存实现的,对函数中a:string类型的内存实现,实际上函数线程
栈中仅仅保存String类型变量的地址。它的字符串存储空间则是在系统(全局堆)空
间生成的。
对字符串类型变量的付值,实际上是给变量付一个地址,而字符串类型变量的运
算,则就复杂很多,首先,系统调用@getleng算出长度,在调用@getmem在系统(全
局堆)空间申请mem,然后再调用@strcpy拷贝其他的字符串长度。(以上是通过观察
单步运行Delphi中的CPU汇编指令观察到的)
就是说,对String的大多数操作,都涉及到全局堆栈的操作。相对单线程的系统
来讲,使用String类型绝对不会出错,而多线程,则存在潜在的冲突的机会。特别是
大量的String类型运算,一般会引起系统堆竞争操作,则及有可能发生严重的存储冲
突,而导致系统崩溃。
概括上面的分析,就是String类型是线程不安全的,所有涉及到String类型的函
数也存在线程不安全的因素。对系统内存(全局堆)的操作,应该用同步机制访问方可。
上面的分析,仅仅代表XiDao(小刀)的个人意见,不代表Borland官方意见。
(hehe,小刀没有买正式版的Delphi,所以也没有办法获取Borland的帮助)仅仅供
同小刀一样使用盗版的朋友参考,所以可能存在很多错误,希望大家给予纠正。以后
小刀也会买正式版的Delphi了 :)
小刀 2000.5.13
 
我把XiDao第一个帖子中的代码运行了一遍,但是没有出现任何问题。我的环境是 d5+win2000:64M:c433
我至少按了上百次button,所以我的系统变得很慢,但是没有出现XiDao所说的情况
然后,改小循环次数,狂点.没有问题,除了慢.
另外我不赞成狂点后出来问题,问题在D5上的可能性不大,是否与操作系统有关,时下
正忙,容后再探之.
 
为什么不试一下delphi自己的线程同步机制?我一直用delphi自己的同步机制,感觉很好用!
 
线程中直接更改VCL的确不安全,但是delphi提供了一个自己的同步机制。我多次使用,感觉是安全的。可以将button1click中的线程用delphi的线程改写一下,采用delphi的同步机制也许就行了。
 
本问题已经解决,有兴趣的朋友可以参考Todd Miller&amp;David Powell合著的
《Delphi3 开发使用手册》,"第24章 线程处理"P567页。
作者指出,在Delphi中,“直接调用Win32 API CreateThread不是件好事情,
因为CreareThread对Delphi的应用来说太低了。仅调用它不足以保证Delphi运行库
能知道这个新线程。。。”
“。。。存在两个问题,第一个十如果直接调用CreateThread,Delphi的全局
变量ISMultiThrea(...)将不置成True。而Delphi的堆管理程序(heap manager)用
此变量来了解进程中何时有多个线程在运行,只有知道了...它才会保护其内部结构
不受几个线程同时修改。。。”
“另一个问题是,begin
Thread会为新线程建立一个异常处理的框架,而
CreateThread不会这么做。。。”
“注意:永远不要直接用CreateThread来创建线程。就Delphi运行时刻库所
涉及,它不能安全的建立起一个新线程。。。”
Delphi提供的begin
Thread程序如下:
function begin
Thread(SecurityAttributes: Pointer;
StackSize: LongWord;
ThreadFunc: TThreadFunc;
Parameter: Pointer;
CreationFlags: LongWord;
var ThreadId: LongWord): Integer;
var
P: PThreadRec;
begin
New(P);
P.Func := ThreadFunc;
P.Parameter := Parameter;
IsMultiThread := TRUE;
Result := CreateThread(SecurityAttributes, StackSize, @ThreadWrapper, P,
CreationFlags, ThreadID);
end;

确实如此,如果在建立线程之前,将IsMultiThread设置为True,则前面所有的
例子将不会发生错误。
 
后退
顶部