|
1、进程: 磁盘上的一个可执行文件在被启动后,就是一个进程。如果想手动创建一个进程应调用CreateProcess。Windows加载一个进程时,它只是打开一个内存映象文件,然后把可执行文件的内容或DLL加载进内存。进程是非活跃的,它们什么也不做,真正活动起作用的是线程。 每个进程都被系统分配虚拟的4G内存空间,每个进程生活在自己的内存空间“宇宙”中,认为自己拥有整台计算机,进程只认识自己的宇宙,并不清楚还有许多“平行宇宙”存在,也不知道其他平行宇宙中生活其他进程。因为每个进程在计算机中都是独立的,它们生活的宇宙都是平行的,所以互相覆盖的机会非常少。当然也有进程内通信,现在不是流行“穿越”嘛,也许是虫洞,也是外星人。 一个进程就是一个加载过的程序,它不是正在运行,也不是正在执行。它们只是存在于一个巨大的4G虚拟地址空间中,拥有2GB空间的直接访问权,而操作系统拥有剩余部分空间。当要访问内存时,才将真正的物理内存映射给它。线程才是执行者。 所以进程包含:一个私有的地址空间,它是进程可以使用的一组虚拟内存地址空间; 程序的相关代码、数据源; 系统资源,比如操作系统同步对象等; 至少包含一个线程(主线程)。 2、线程: 每个进程都有一个或多个线程。线程是程序的执行单位(线程本身并不包括程序代码,真正拥有代码的是进程),每个进程至少包括一个线程,称为主线程,一个进程如果有多个线程,就可以共享同一进程的资源,并可以并发执行。 每个线程都有一个叫Context(环境)的东东,TContext记录包含有关线程状态信息的数据结构,当线程运行时,它包含了关于CPU中寄存器状态的信息。 PContext = ^TContext; _CONTEXT = record ContextFlags: DWORD; Dr0: DWORD; Dr1: DWORD; Dr2: DWORD; Dr3: DWORD; Dr6: DWORD; Dr7: DWORD; FloatSave: TFloatingSaveArea; SegGs: DWORD; SegFs: DWORD; SegEs: DWORD; SegDs: DWORD; Edi: DWORD; Esi: DWORD; Ebx: DWORD; Edx: DWORD; Ecx: DWORD; Eax: DWORD; Ebp: DWORD; Eip: DWORD; SegCs: DWORD; EFlags: DWORD; Esp: DWORD; SegSs: DWORD; end; TContext = _CONTEXT; CONTEXT = _CONTEXT;
创建线程的API函数为CreateThread,函数原型如下: function CreateThread( lpThreadAttributes: Pointer; {安全设置:指向SECURITY_ATTRIBUTES结构的指针,
一般使用默认安全设置,设为nil值} dwStackSize: DWORD; {堆栈大小:线程可将多少地址用于自身堆栈,
一般设为0,表示与应用程序堆栈大小相同} lpStartAddress: TFNThreadStartRoutine; {入口函数:线程入口函数的地址} lpParameter: Pointer; {函数参数:线程入口函数的参数,为指针类型,
一般定义一个记录型指针,作为参数传入} dwCreationFlags: DWORD; {启动选项:只有两个值可选,0表示线程创建后立即执行,
CREATE_SUSPENDED表示创建线程后挂起} var lpThreadId: DWORD {输出线程ID:使用这个地址来存放系统分配给新线程的ID } ): THandle; stdcall; {返回线程句柄}
每个线程必须拥有一个进入点函数,线程从这个进入点开始运行。线程函数可以使用任何合法的名字。可以给线程函数传递单个指针型参数。线程函数必须由一个返回值,它将成为该线程的退出代码。必须注明为stdcall方式调用。线程函数应该尽可能的使用函数参数和局部变量。 //定义一个记录型指针,用于作为参数。 type TInfo = Record Count:Integer; x:Integer; y:Integer; End; PInfo = ^TInfo; //线程函数的例子,注意要有返回值与stdcall。 function ThreadFun(p:Pointer):DWord;stdcall; var Pf:PInfo; i:Integer; begin Pf:=PInfo(p); for i := 0 to Pf.Count-1 do begin Form1.Canvas.TextOut(pf.x,pf.y,IntToStr(i)); end; Result:=0; end;
多线程调用或直接调用比较: //直接调用过程,循环运行时将锁住界面, //无法进行拖动、Resize等其他操作 procedure TForm1.Button1Click(Sender: TObject); var p:PInfo; begin New(p); p.Count:=500000; p.x:=20; p.y:=20; ThreadFun(p); Dispose(p); end; //在多线程中调用循环 //界面操作不受影响 procedure TForm1.Button2Click(Sender: TObject); var p:PInfo; hThread:THandle; ID:DWORD; begin New(p); p.Count:=500000; p.x:=20; p.y:=20; hThread:=CreateThread(nil,0,@ThreadFun,p,0,ID); if hThread=0 then MessageBox(Handle,'No Thread',nil,MB_OK); Dispose(p); end;
这里的多线程执行会有问题,发现窗体上才画到9就没有了,有时一点都没有画。找了一下原因,原来指向参数记录的指针p是Button2的点击事件的局部变量,创建线程后,p就被释放掉了,而线程入口函数中引用的指针也就没了,所以就出问题了。 修改方法:1、将p设为全局变量,在Form1的OnCreate事件中New(p),在OnDestroy事件中Dispose(p)。2、在线程函数中复制一份参数的拷贝,而不是指向参数的指针。var Info:TInfo; Info:=PInfo(p)^;… type TInfo = Record Count:Integer; x:Integer; y:Integer; End; PInfo = ^TInfo; var Form1: TForm1; pf:PInfo; implementation {$R *.dfm} //线程函数 function ThreadFun(p:Pointer):DWord;stdcall; var Info:TInfo; i:Integer; begin Info:=PInfo(p)^; //这里拷贝值,而不是用指针。 for i := 0 to Info.Count-1 do begin Form1.Canvas.Lock; Form1.Canvas.TextOut(Info.x,Info.y,IntToStr(i)); Form1.Canvas.Unlock; end; Result:=0; end; //在多线程中调用循环 //界面操作不受影响 procedure TForm1.Button2Click(Sender: TObject); var hThread:THandle; ID:DWORD; begin pf.Count:=200000; pf.x:=20; pf.y:=20; hThread:=CreateThread(nil,0,@ThreadFun,pf,0,ID); if hThread=0 then MessageBox(Handle,'No Thread',nil,MB_OK); end; procedure TForm1.FormCreate(Sender: TObject); begin New(pf); end; procedure TForm1.FormDestroy(Sender: TObject); begin Dispose(pf); end;
修改以后,成功运行了!如图:
VCL多数不是线程安全的,但VCL具有让多线程同时操作单个图形的对象的能力,主要是由于TCannas中引入Lock()和UnLock()方法,所以整个Graphics单元都是线程安全的,包括TCanvas,TPen,TBrush,TFont,TBitMap,TMetafile,TPicture和TIcon。画布锁定规则只要使用画布前锁定,使用完毕后再解锁,多线程就可以互不干扰地使用画布。 像本例中如果不用Lock和UnLock,那么执行时,拖动界面,有时就会 |

浙公网安备 33010602011771号