资源描述:
《delphi多线程编程2》由会员上传分享,免费在线阅读,更多相关内容在行业资料-天天文库。
delphi多线程编程2在这段程序中,有三个线程几乎是同时建立,向窗体中的ListBox1中写数据,最后写出的结果是这样的: 能不能让它们别打架,一个完了另一个再来?这就要用到多线程的同步技术. 前面说过,最简单的同步手段就是"临界区". 先说这个"同步"(Synchronize),首先这个名字起的不好,我们好像需要的是"异步";其实异步也不准确... 管它叫什么名字呢,它的目的就是保证不**、有次序、都发生. "临界区"(CriticalSection):当把一段代码放入一个临界区,线程执行到临界区时就独占了,让其他也要执行此代码的线程先等等;这和前面用的Lock和UnLock差不多;使用格式如下:varCS:TRTLCriticalSection; {声明一个TRTLCriticalSection结构类型变量;它应该是全局的}InitializeCriticalSection(CS);{初始化}EnterCriticalSection(CS); {开始:轮到我了其他线程走开}LeaveCriticalSection(CS); {结束:其他线程可以来了}DeleteCriticalSection(CS); {删除:注意不能过早删除}//也可用TryEnterCriticalSection替代EnterCriticalSection. 用上临界区,重写上面的代码,运行效果图: 代码文件:unitUnit1;interfaceuses Windows,Messages,SysUtils,Variants,Classes,Graphics,Controls, Forms, Dialogs,StdCtrls;type TForm1=class(TForm) ListBox1:TListBox; Button1:TButton; procedureFormCreate(Sender:TObject); procedureFormDestroy(Sender:TObject); procedureButton1Click(Sender:TObject); end;var Form1:TForm1;implementation{$R*.dfm}var CS:TRTLCriticalSection;functionMyThreadFun(p:Pointer):DWORD;stdcall;var i:Integer;begin EnterCriticalSection(CS); fori:=0to99doForm1.ListBox1.Items.Add(IntToStr(i)); LeaveCriticalSection(CS); Result:=0;end;procedureTForm1.Button1Click(Sender:TObject);var ID:DWORD;begin CreateThread(nil,0,@MyThreadFun,nil,0,ID); CreateThread(nil,0,@MyThreadFun,nil,0,ID); CreateThread(nil,0,@MyThreadFun,nil,0,ID);end;procedureTForm1.FormCreate(Sender:TObject);begin ListBox1.Align:=alLeft; InitializeCriticalSection(CS);end;procedureTForm1.FormDestroy(Sender:TObject);begin DeleteCriticalSection(CS);end;end. Delphi在SyncObjs单元给封装了一个TCriticalSection类,用法差不多,代码如下:unitUnit1;interfaceuses Windows,Messages,SysUtils,Variants,Classes,Graphics,Controls,Forms, Dialogs,StdCtrls;type TForm1=class(TForm) ListBox1:TListBox; Button1:TButton; procedureFormCreate(Sender:TObject); procedureFormDestroy(Sender:TObject); procedureButton1Click(Sender:TObject); end;var Form1:TForm1;implementation{$R*.dfm}usesSyncObjs;var CS:TCriticalSection;functionMyThreadFun(p:Pointer):DWORD;stdcall;var i:Integer;begin CS.Enter; fori:=0to99doForm1.ListBox1.Items.Add(IntToStr(i)); CS.Leave; Result:=0;end;procedureTForm1.Button1Click(Sender:TObject);var ID:DWORD;begin CreateThread(nil,0,@MyThreadFun,nil,0,ID); CreateThread(nil,0,@MyThreadFun,nil,0,ID); CreateThread(nil,0,@MyThreadFun,nil,0,ID);end;procedureTForm1.FormCreate(Sender:TObject);begin ListBox1.Align:=alLeft; CS:= TCriticalSection.Create;end;procedureTForm1.FormDestroy(Sender:TObject);begin CS.Free;end;end.多线程编程(9)-认识等待函数WaitForSingleObject。 一下子跳到等待函数WaitForSingleObject,是因为下面的Mutex、Semaphore、Event、WaitableTimer等同步手段都要使用这个函数;不过等待函数可不止WaitForSingleObject它一个,但它最简单.functionWaitForSingleObject( hHandle:THandle; {要等待的对象句柄} dwMilliseconds:DWORD {等待的时间,单位是毫秒}):DWORD;stdcall; {返回值如下:}WAIT_OBJECT_0 {等着了,本例中是:等的那个进程终于结束了}WAIT_TIMEOUT {等过了点(你指定的时间),也没等着}WAIT_ABANDONED{好不容易等着了,但人家还是不让咱执行;这一般是互斥对象}//WaitForSingleObject的第二个参数一般给常数值INFINITE,表示一直等下去,死等. WaitForSingleObject等待什么?在多线程里就是等待另一个线程的结束,快来执行自己的代码;不过它可以等待的对象可不止线程;这里先来一个等待另一个进程结束的例子,运行效果图: 代码文件:unitUnit1;interfaceuses Windows,Messages,SysUtils,Variants,Classes,Graphics,Controls,Forms, Dialogs,StdCtrls;type TForm1=class(TForm) Button1:TButton; procedureButton1Click(Sender: TObject); end;var Form1:TForm1;implementation{$R*.dfm}var hProcess:THandle;{进程句柄}{等待一个指定句柄的进程什么时候结束}functionMyThreadFun(p:Pointer):DWORD;stdcall;begin ifWaitForSingleObject(hProcess,INFINITE)=WAIT_OBJECT_0then Form1.Text:=Format('进程%d已关闭',[hProcess]); Result:=0;end;{启动一个进程,并建立新线程等待它的结束}procedureTForm1.Button1Click(Sender:TObject);var pInfo:TProcessInformation; sInfo:TStartupInfo; Path:array[0..MAX_PATH-1]ofChar; ThreadID:DWORD;begin {先获取记事本的路径} GetSystemDirectory(Path,MAX_PATH); StrCat(Path,'notepad.exe'); {用CreateProcess打开记事本并获取其进程句柄,然后建立线程监视} FillChar(sInfo,SizeOf(sInfo),0); ifCreateProcess(Path,nil,nil,nil,False,0,nil,nil,sInfo,pInfo)then begin hProcess:=pInfo.hProcess; {获取进程句柄} Text:=Format('进程%d已启动',[hProcess]); CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID);{建立线程监视} end;end;end.窗体文件:objectForm1:TForm1 Left=0 Top=0 Caption='Form1' ClientHeight=124 ClientWidth=241 Color=clBtnFace Font.Charset=DEFAULT_CHARSET Font.Color= clWindowText Font.Height=-11 Font.Name='Tahoma' Font.Style=[] OldCreateOrder=False PixelsPerInch=96 TextHeight=13 objectButton1:TButton Left=88 Top=56 Width=75 Height=25 Caption='Button1' TabOrder=0 OnClick=Button1Click endend多线程编程(10)-多线程同步之Mutex(互斥对象)。 原理分析: 互斥对象是系统内核对象,各线程都可以拥有它,谁拥有谁就能执行; 执行完毕,用ReleaseMutex函数释放拥有权,以让其他等待的线程使用. 其他线程可用WaitForSingleObject函数排队等候(等候也可以理解为排队申请). 使用过程:varhMutex:THandle;{应该先声明一个全局的互斥句柄}CreateMutex {建立一个互斥对象}WaitForSingleObject {用等待函数排队等候}ReleaseMutex {释放拥有权}CloseHandle {最后释放互斥对象} ReleaseMutex、CloseHandle的参数都是CreateMutex返回的句柄,关键是CreateMutex函数:functionCreateMutex( lpMutexAttributes:PSecurityAttributes; bInitialOwner:BOOL;{是否让创建者(此例中是主线程)拥有该互斥对象} lpName:PWideChar {可以给此互斥对象取个名字,如果不要名字可赋值为nil}):THandle;{1、第一个参数前面说过.2、第二个参数在这里一定要是False,如果让主线程拥有互斥,从理论上讲,得等程序退出后其他线程才有机会; 取值False时,第一个执行的线程将会最先拥有互斥对象, 一旦拥有其他线程就得先等等.3、第三个参数,如果给个名字,函数将从系统中寻找是否有重名的互斥对象,如果有则返回同名对象的存在的句柄; 如果赋值为nil将直接创建一个新的互斥对象;下个例子将会有名字.} 本例效果图: 代码文件:unitUnit1;interfaceuses Windows,Messages,SysUtils,Variants,Classes,Graphics,Controls,Forms, Dialogs,StdCtrls;type TForm1=class(TForm) Button1:TButton; procedureButton1Click(Sender:TObject); procedureFormCreate(Sender:TObject); procedureFormDestroy(Sender:TObject); end;var Form1:TForm1;implementation{$R*.dfm}var f:Integer; {用这个变量协调一下各线程输出的位置} hMutex:THandle;{互斥对象的句柄}functionMyThreadFun(p:Pointer):DWORD;stdcall;var i,y:Integer;begin Inc(f); y:=20*f; fori:=0to50000do begin ifWaitForSingleObject(hMutex,INFINITE)=WAIT_OBJECT_0 then begin Form1.Canvas.Lock; Form1.Canvas.TextOut(20,y,IntToStr(i)); Form1.Canvas.Unlock; Sleep(0);{稍稍耽搁一点,不然有时Canvas会协调不过来} ReleaseMutex(hMutex); end; end; Result:=0;end;procedureTForm1.Button1Click(Sender:TObject);var ThreadID:DWORD;begin Repaint; f:=0; CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID); CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID); CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID); CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID); CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID);end;procedureTForm1.FormCreate(Sender:TObject);begin hMutex:=CreateMutex(nil,False,nil);end;procedureTForm1.FormDestroy(Sender:TObject);begin CloseHandle(hMutex);end;end. 窗体文件:objectForm1:TForm1 Left=0 Top=0 Caption='Form1' ClientHeight=140 ClientWidth=192 Color=clBtnFace Font.Charset=DEFAULT_CHARSET Font.Color=clWindowText Font.Height=-11 Font.Name='Tahoma' Font.Style= [] OldCreateOrder=False OnCreate=FormCreate PixelsPerInch=96 TextHeight=13 objectButton1:TButton Left=109 Top=107 Width=75 Height=25 Caption='Button1' TabOrder=0 OnClick=Button1Click endend SyncObjs单元下有封装好的TMutex类,好像不如Api快,内部机制也稍有区别,但使用方法差不多:unitUnit1;interfaceuses Windows,Messages,SysUtils,Variants,Classes,Graphics,Controls,Forms, Dialogs,StdCtrls;type TForm1=class(TForm) Button1:TButton; procedureButton1Click(Sender:TObject); procedureFormCreate(Sender:TObject); procedureFormDestroy(Sender:TObject); end;var Form1:TForm1;implementation{$R*.dfm}usesSyncObjs;var f:Integer; MyMutex:TMutex;functionMyThreadFun(p:Pointer):DWORD;stdcall;var i,y:Integer;begin Inc(f); y:=20*f; fori:=0to50000 do begin ifMyMutex.WaitFor(INFINITE)=wrSignaledthen begin Form1.Canvas.Lock; Form1.Canvas.TextOut(20,y,IntToStr(i)); Form1.Canvas.Unlock; MyMutex.Release; end; end; Result:=0;end;procedureTForm1.Button1Click(Sender:TObject);var ThreadID:DWORD;begin Repaint; f:=0; CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID); CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID); CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID); CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID); CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID);end;procedureTForm1.FormCreate(Sender:TObject);begin MyMutex:=TMutex.Create(False);end;procedureTForm1.FormDestroy(Sender:TObject);begin MyMutex.Free;end;end.多线程编程(11)-多线程同步之Mutex(互斥对象)[续]。 Mutex作为系统核心对象是可以跨进程的(临界区就不行),我们可以利用互斥对象禁止程序重复启动. 工作思路: 先用OpenMutex尝试打开一个自定义名称的Mutex对象,如果打开失败说明之前没有这个对象存在; 如果之前没有这个对象,马上用CreateMutex建立一个, 此时的程序应该是第一次启动; 再重复启动时,那个OpenMutex就有结果了,然后强制退出. 最后在程序结束时用CloseHandle释放Mutex对象.functionOpenMutex( dwDesiredAccess:DWORD;{打开权限} bInheritHandle:BOOL; {能否被当前程序创建的进程继承} pName:PWideChar {Mutex对象的名称}):THandle;stdcall; {成功返回Mutex的句柄;失败返回0} 注意,这里的CreateMutex函数应该有个名了,因为OpenMutex要用到; 另外,CreateMutex的第二个参数已经不重要了(也就是True和False都行),因为这里是用其名称来判断的. 程序可以这样写:unitUnit1;interfaceuses Windows,Messages,SysUtils,Variants,Classes,Graphics,Controls,Forms, Dialogs;type TForm1=class(TForm) procedureFormCreate(Sender:TObject); procedureFormDestroy(Sender:TObject); end;var Form1:TForm1;implementation{$R*.dfm}var hMutex:THandle;const NameMutex='MyMutex';procedureTForm1.FormCreate(Sender:TObject);begin ifOpenMutex(MUTEX_ALL_ACCESS,False,NameMutex)<>0then begin ShowMessage('该程序已启动'); Application.Terminate; end; hMutex:=CreateMutex(nil,False, NameMutex);end;procedureTForm1.FormDestroy(Sender:TObject);begin CloseHandle(hMutex);end;end. 这一般都是写在dpr主程序里,省得让后启动的程序执行些无用的代码:programProject1;uses Forms,Windows, Unit1in'Unit1.pas'{Form1};{$R*.res}var hMutex:THandle;const NameMutex='MyMutex';begin {主线程入口} ifOpenMutex(MUTEX_ALL_ACCESS,False,NameMutex)<>0then begin MessageBox(0,'该程序已启动','提示',MB_OK); Application.Terminate; end; hMutex:=CreateMutex(nil,False,NameMutex); Application.Initialize; Application.MainFormOnTaskbar:=True; Application.CreateForm(TForm1,Form1); Application.Run; CloseHandle(hMutex); {主线程出口}end.多线程编程(12)-多线程同步之Semaphore(信号对象)。 之前已经有了两种多线程的同步方法: CriticalSection(临界区)和Mutex(互斥),这两种同步方法差不多,只是作用域不同; CriticalSection(临界区)类似于只有一个蹲位的公共厕所,只能一个个地进; Mutex(互斥)对象类似于接力赛中的接力棒,某一时刻只能一个人持有, 谁拿着谁跑. 什么是Semaphore(信号或叫信号量)呢? 譬如到银行办业务、或者到车站买票,原来只有一个服务员,不管有多少人排队等候,业务只能一个个地来. 假如增加了业务窗口,可以同时受理几个业务呢? 这就类似与Semaphore对象,Semaphore可以同时处理等待函数(如:WaitForSingleObject)申请的几个线程. Semaphore的工作思路如下: 1、首先要通过CreateSemaphore(安全设置,初始信号数,信号总数,信号名称)建立信号对象; 参数四:和Mutex一样,它可以有个名称,也可以没有,本例就没有要名称(nil);有名称的一般用于跨进程. 参数三:信号总数,是Semaphore最大处理能力,就像银行一共有多少个业务窗口一样; 参数二:初始信号数,这就像银行的业务窗口很多,但打开了几个可不一定,如果没打开和没有一样; 参数一:安全设置和前面一样,使用默认(nil)即可. 2、要接受Semaphore服务(或叫协调)的线程,同样需要用等待函数(如:WaitForSingleObject)排队等候; 3、当一个线程使用完一个信号,应该用ReleaseSemaphore(信号句柄,1,nil)让出可用信号给其他线程; 参数三:一般是nil,如果给个数字指针,可以接受到此时(之前)总共闲置多少个信号; 参数二:一般是1,表示增加一个可用信号; 如果要增加CreateSemaphore时的初始信号,也可以通过ReleaseSemaphore.4、最后,作为系统内核对象,要用CloseHandle关闭. 另外,在Semaphore的总数是1的情况下,就和Mutex(互斥) 一样了. 在本例中,每点击按钮,将建立一个信号总数为5的信号对象,初始信号来自Edit1;同时有5个线程去排队. 本例也附上了Delphi中TSemaphore类的例子,但没有过多地纠缠于细节,是为了尽快理出多线程的整体思路. 本例效果图: 代码文件:unitUnit1;interfaceuses Windows,Messages,SysUtils,Variants,Classes,Graphics,Controls,Forms, Dialogs,StdCtrls;type TForm1=class(TForm) Button1:TButton; Edit1:TEdit; procedureButton1Click(Sender:TObject); procedureFormCreate(Sender:TObject); procedureFormDestroy(Sender:TObject); procedureEdit1KeyPress(Sender:TObject;varKey:Char); end;var Form1:TForm1;implementation{$R*.dfm}var f:Integer; {用这个变量协调一下各线程输出的位置} hSemaphore:THandle;{信号对象的句柄}functionMyThreadFun(p:Pointer):DWORD;stdcall;var i,y:Integer;begin Inc(f); y:=20*f; ifWaitForSingleObject(hSemaphore,INFINITE)=WAIT_OBJECT_0 then begin fori:=0to1000do begin Form1.Canvas.Lock; Form1.Canvas.TextOut(20,y,IntToStr(i)); Form1.Canvas.Unlock; Sleep(1);{以免Canvas忙不过来} end; end; ReleaseSemaphore(hSemaphore,1,nil); Result:=0;end;procedureTForm1.Button1Click(Sender:TObject);var ThreadID:DWORD;begin {不知是不是之前创建过Semaphore对象,假如有先关闭} CloseHandle(hSemaphore); {创建Semaphore对象} hSemaphore:=CreateSemaphore(nil,StrToInt(Edit1.Text),5,nil); Self.Repaint; f:=0; CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID); CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID); CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID); CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID); CreateThread(nil,0,@MyThreadFun,nil,0,ThreadID);end;{让Edit只接受12345五个数}procedureTForm1.Edit1KeyPress(Sender:TObject;varKey:Char);begin ifnotCharInSet(Key,['1'..'5'])thenKey:=#0;end;procedureTForm1.FormCreate(Sender:TObject);begin Edit1.Text:='1';end;procedureTForm1.FormDestroy(Sender:TObject);begin CloseHandle(hSemaphore);end;end.