首页 > Linux操作系统 > Linux操作系统 > Delphi5学习笔记之四
142.线程同步问题:可以使用临界区、互斥量、信号量和事件来实现线程同步。
临界区:它就是一次只能由一个线程执行的一段代码。在使用临界区的方法:
第1步:调用过程procedure InitializeCriticalSection(
var lpCriticalSection: TRTLCriticalSection);初始化临界区。
第2步:调用过程procedure EnterCriticalSection(
var lpCriticalSection: TRTLCriticalSection);进入临界区。
第3步:执行临界区中的代码
第4步:调用过程LeaveCritcalSection(var lpCriticalSection: TRTLCriticalSection);退出临界区。
第5步:调用过程DeleteCriticalSection(var lpCriticalSection: TRTLCriticalSection);删除临界区。
举例说明:
var
CS: TRTLCriticalSection;
Procedure SomeThread.Execute;
Var
I: integer;
Begin
EnterCriticalSection(CS);
For I := 0 to 900000 do begin
GI := Round(abs(sqrt(I)));
End;
LeaveCriticalSection(CS);
End;
Initicalization
IniticalizeCriticalSection(CS);//一般情况,初始化临界区都是与线程创建放到一起
Finallization
DeleteCriticalSection(CS);//在使用临界区的所有线程都终止后删除临界区
End.//初始化临界区和删除临界区不一定放到程序的初始化和终止区。
[@more@]互斥量:它与临界区的区别:互斥量可以跨进程的线程同步,还有一个更大的区别就是性能方面,临界区要比事件对象(如:互斥对象)快很多,这是因为事件对象使用了系统内核。
函数CreateMutex用来创建一个互斥量,
function CreateMutex(lpMutexAttributes: PsecurityAttributes;
bInitialOwner: Bool;lpName: PChar):THandle;
参数说明:
lpMutexAttributes是一个指向PsecurityAttributes结构的指针。此参数一般设置为0,表示使用默认的安全属性。
BInitialOwner表示创建这个互斥对象的线程是否要成为这个互斥对象的拥有者。
LpName是此互斥对象的名称。
注:当使用完互斥对象时,应当使用CloseHandle来关闭它。
使用互斥量对象的步骤:
在创建完互斥量对象后要进行如下几步来使用它:
第一步:使用WaitForSingleObject来防止其它线程进入互斥量保护的代码。
第二步:执行你要在互斥量下保护的代码
第三步:使用ReleaseMutex来释放互斥量对象。
第四步:使用CloseHandle来关闭互斥量对象。
举例说明:
const MaxSize = 200;
var
GI: Integer = 0;
hMutex: THandle = 0;
ThreadDone: Byte = 0;//用于记录终止的线程个数
GArray: array [1..MaxSize] of Integer;//全局数组GArray
…
hMutex := CreateMutex(nil,False,nil);//传递False表示创建互斥量的线程不拥有该互斥量
//创建两个线程
TCustomThread.Create(False);
TCustomThread.Create(False);
…
procedure TCustomThread.Execute;
var
I: integer;
begin
FreeOnTerminate := True;(*设置FreeOnTerminate,
在执行完Execute后就会触发OnTerminate事件,
这样就有机会在OnTerminate事件里清除线程对象了
*)
OnTerminate := MainForm.CustomeThreadOnTerminate;
if WaitForSingleObject(hMutex,INFINITE) = WAIT_OBJECT_0 then begin
(*当第一次执行WaitForSingleObject时,由于创建的互斥对象不属于任何线程,因此
由hMutex标识的互斥对象是有信号的,因此本条件为真,可以执行下面的代码,当执行完
WaitForSingleObject后,WaitForSingleObject会将hMutex标识的互斥对象变成无信号状态,这样,再有其它线程执行到WaitForSingleObject时,它的返回值就不是WAIT_OBJECT_0,
这样就不能执行下面的代码. 注:WAIT_OBJECT_0表示互斥对象有信号.
*)
if not Terminated then begin
for i := 1 to MaxSize do begin
GArray[i] := GetGI;//GetGI是一个给数组Garray初始化的函数
end;
end;
end;
ReleaseMutex(hMutex);
//释放互斥量对象,使互斥量对象变成无信号状态,这样其它的线程再次调用WaitForSingleObject函数时,就可以得知目前该互斥对象所保护的代码是可以访问的,即WaitForSingleObject会返回WAIT_OBJECT_0。不过我在实现上述代码时,发现,全局数组GArray在第一个线程终止后输出时,由于第二个线程此时可以访问互斥对象保护的代码,也就是可以访问全局数组GArray,即改写全局数组GArray,而此时第一个线程终止时GArray的输出还未结束,这就造成数据输出逻辑错误。
end;
procedure TMainForm.CustomeThreadOnTerminate(Sender: TObject);//线程终止时需执行的过程CustomThreadOnTerminate
var
I: integer;
begin
Inc(ThreadDone);
if ThreadDone = 1 then begin//在第一个线程终止后输出全局数组里的数据时会发生逻辑错误。目前还未解决。
for I := 1 to MaxSize do begin
MemoThread.Lines.Add(IntToStr(GArray[i]));
end;
end;
if ThreadDone = 2 then begin CloseHandle(hMutex); end;
end;
信号量:首先说明几点,一、信号量不属于任何线程,二、信号量一般用于对资源的控制。
使用函数CreateSemaphore来创建信号量,
function CreateSemaphore(lpSemaphoreAttributes: PsecurityAttributes;
lInitialCount,lMaximumCount: LongInt;lpName: PChar):THandle;
参数说明:
lInitialCount是信号量的计数值,它必须在0到lMaximumCount之间,如果lInitialCount大于零,说明信号量处于有信号状态,当调用WaitForSingleObject等函数时lInitialCount就减少1。当调用函数ReleaseSemaphore时lInitialCount就加1。
lMaximumCount是计数的最大值,如果信号量代表的是资源,那么lMaximumCount就代表资源的总数。
lpName是信号量的名称。
使用信号量的方法与使用互斥量的方法及步骤相同。不过释放信号量的函数ReleaseSemaphore要复杂一些,
function ReleaseSemaphore(hSemaphore: THandle;lReleaseCount: Longint;
lpPreviousCount: Pointer): BOOL;
参数说明:
lReleaseCount表示在使用该函数时给信号量计数增加的值,如果说把它赋值为9那么该函数就会将信号量的计数增加9,而互斥量释放函数一次只能释放一个。
LpPreviousCount如果该参数的值不为nil,那么它将用来存储原有信号量的计数值。
注在使用完信号量后也要使用CloseHandle来关闭信号量的句柄。
143.使用任何线程对象的方法来操作用户界面时,一定要通过Synchronize()方法来调用,或者通过消息来控制用户界面。
144.图形单元Graphic.pas是线程安全的,包括TCanvas、TPen、TBrush、TFont、TBitmap、TMetafile、TPicture、TIcon类。之所以说它们是安全的,是因为这些类中使用了方法Lock和UnLock。这两个方法类似临界区的功能。如下所示:
Canvas.Lock;
Canvas.TextOut(x,y,’’);//Lock与UnLock间夹着的部分Canvas.TextOut被保护
Canvas.UnLock;
如上所示,夹在Canvas.Lock和Canvas.UnLock方法间的对Canvas的操作就被保护了起来,当一个线程执行中间的部分(Canvas.TextOut(x,y,’’))时,如果其它线程也要执行这段代码是不可以的,只有当当前线程执行完中间部分后,其它线程才可以执行。
145.复习记录中的Packed,为了使访问方便,向数组和记录这样的结构化类型在内存中都是按照字或双字排列的,这样会占用掉多于实际需要的空间。不过在记录声明时加上保留字packed时,就可以数据存储是紧凑存储的。
146. 可以通过将文本文件和二进制文件变量转换为TTextRec和TFileRec来获得文件的句柄,如:TfileRec(SomeFileVar).Handle;
147. 映射文件的三种典型应用如下:
• 在Win32系统中,利用内存映射文件实现调入和执行EXE文件及DLL文件。这样节省了页交换文件的空间,从而减少了文件调入的时间。
• 仅仅利用一个指向内存区域的指针就可实现对映射文件的数据访问,这样不仅简化了对文件的访问,也不需要编写各种缓冲方案。
• 在同一台机器上,利用内存映射文件可以实现多个进程共享数据。
148. 当创建内存映射文件时,实际上是使文件与进程虚拟空间的一段区域关联。为建立关联,首先需要创建一个文件映射对象。要查看或编辑文件的内容,必须获得文件映射对象的视图。这样,当通过指针访问文件的内容时,就如同访问内存区域一样了。
149. 当向文件视图写入数据时,系统负责处理数据的缓存、缓冲、写入和调入以及内存的分配和释放。这好像是在一块内存区域中编辑数据。文件的输入/输出完全由系统来处理。这就是内存映射文件的美妙之处。
150. 使用映射文件
1.创建/打开一个内存映射文件的第一步是获取一个文件的句柄。你可以调用FileCreate()或FileOpen()。FileCreate()在SysUtils.pas单元中声明,此函数创建一个文件名为FileName的新文件。如果函数调用成功则返回一个有效的文件句柄。否则,返回INVA LID_HANDLE_VALUE常量。
FileOpen()用来以某种模式打开一个已存在的文件。如果函数调用成功则返回一个有效的文件句柄。否则,返回INVALID_HANDLE_VALUE的常量。FileOpen()在SysUtils.pas单元中声明
2.创建文件映射对象,无论是命名的或是无命名的内存映射文件对象,你都可以使用CreateFileMapping()函数来创建。如果CreateFileMapping()调用成功,则返回文件映射对象的句柄。如果此句柄代表一个已有的文件映射对象,GetLastError()将返回ERROR_ALREADY_EXISTS。如果CreateFileMapping()调用失败,将返回0。此时,必须调用GetLastError()得到失败的原因。
3.映射文件的视图到进程的地址空间,MapViewOfFile()能把文件的视图映射到进程地址空间。
4.取消文件视图的映射,UnmapViewOfFile()用来解除文件视图与进程地址空间的映射关系。完成对文件的处理后,必须调用UnmapViewOfFile();否则,只能等到进程终止,系统才会释放映射区域所占的内存。
5.关闭文件映射和文件内核对象,对FileOpen()和CreateFileMapping()的调用都将打开内核对象,你必须负责关闭这些内核对象。这可以通过调用CloseHandle()函数来实现。
151.使用函数GetDriveType可以得到驱动器的一些信息,它有一个Pchar类型的参数,它的返回值为整数,表示驱动器的类型,如:
var
I: integer;
C: String;
Dtype: Integer;
DriveString: String;
Begin
For I := 65 to 90 do
Begin
C := Chr(i) + ‘:’;
Dtype := GetDrvieType(Pchar(C));
Case DType of
0://不能确定驱动器类型
1: //根目录不存在
DRIVE_REMOVABLE: //驱动器可移动
DRIVE_FIXED: //驱动器不可移动
DRIVE-REMOTE: //驱动器为远程网络驱动器
DRIVE_CDROM: //驱动器为CD-ROM驱动器
DRIVE_RAMDISK: //驱动器为RAM磁盘
End;
End;
End;
152.GetDiskFreeSpace可以得到驱动器的以下信息:每一簇的扇区数、 每一扇区的字节数、 可用簇数、簇的总数、可用的磁盘字节数、总的磁盘字节数。来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/7416120/viewspace-908612/ ,如需转载,请注明出处,否则将追究法律责任。