转载自:万一的博客
function CreateThread( lpThreadAttributes: Pointer; //安全设置 dwStackSize: DWORD; //堆栈大小 lpStartAddress: TFNThreadStartRoutine; //入口函数 lpParameter: Pointer; //函数参数 dwCreationFlags: DWORD; //启动选项 var lpThreadId: DWORD; //输出线程ID ): THandle; stdcall; //返回线程句柄
在Windows 上建立一个线程,离不开 CreateThread函数:TThread.Create就是先调用了BeginThread(Delphi自定义的),BeginThread又调用的CreateThread
既然有建立,就该有释放,CreateThread对应的释放函数就是:ExitThread,譬如下面的代码
procedure TForm1.Button1Click(Sender: TObject); begin ExitThread(0) //此句即可退出当前程序,但是不建议这样使用 end;
代码注释:
当前程序是一个进程,进程只是一个工作环境,线程是工作者
每个进程都会有一个启动线程(或叫主线程),也就是说:我们之前大量的编码都是写给这个主线程的;
上面的ExitThread(0); 就是退出这个主线程
系统不允许一个没有线程的进程存在,所以程序就退出了
另外:ExitThread 函数的参数是一个退出码,这个退出码是给之后的其他函数使用的,这里随便给个无符号整数即可
或许你会说:这个ExitThread 挺好用的;其实不管是API 还是用 TThread类写多线程,我们很少用到它,因为
1) 假如直接使用API的 CreateThread,它执行完入口函数之后会自动退出,无需ExitThread
2) 用 TThread类建立的线程又绝不能使用 ExitThread退出;因为使用 TThread建立线程时会同时分配更多的资源(譬如你自定义的成员、还有它的祖先了(TObject)分配的资源……),如果用 ExitThread给草草退出了,这些资源将得不到释放而导致内存泄露。尽管Delphi 提供了 EndThread(其内部是调用 ExitThread),这也不需要我们手动操作(假如非要手动操作也是件很麻烦的事情,因为很多时候你不知道线程是什么时候执行完毕的)
除了CreateThread,还有一个 CreateRemoteThread,可在其他的进程中建立线程,这不应该是现在学习的重点
现在还是集中精力把 CreateThread的参数搞彻底
倒过来说吧,先谈谈 CreateThread将要返回的“线程句柄”
“句柄”类似指针,但是通过指针可读写对象,通过句柄只是使用对象
有句柄的对象一般是系统级别的对象(或者叫内核对象);之所以给我们的是句柄而不是指针,目的只有一个:“安全”
貌似通过句柄能做很多事情,但是一般把句柄提交到某个函数(一般是系统函数)后,我们也就到此为止很难了解更多了;事实上是系统并不相信我们
不管是指针还是句柄,都不过是内存中的一小块数据(一般用结构描述),微软并没有公开句柄的结构细节,猜一下它应该包括:真实的指针地址、访问权限设置、引用计数……
既然CreateThread 可以返回一个句柄,说明线程属于“内核对象”
实际上不管线程属于哪个进程,它们在系统的怀抱中是平等的;在优先级(后面详谈)相同的情况下,系统会在相同的时间间隔内来运行一下每个线程,不过这个间隔很小很小,以至于让我们误认为程序是在不间断地运行
这时候你应该有一个疑问:系统在去执行其他线程的时候,是怎么记住前一个线程的数据状态的?
有这样一个结构 TContext,它基本上是一个CPU 寄存器的集合,线程是数据就是通过这个结构切换的,我们也可以通过GetThreadContext 函数读取寄存器看看
附上这个结构 TContext(或叫: CONTEXT、_CONTEXT) 的定义:
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;
CreateThread 的最后一个参数是“线程ID”
既然可以返回句柄,为什么还要输出线程ID“现在我知道的是:
1) 线程的ID 是唯一的,而句柄可能不只一个,譬如可以用 GetCurrentThread 获取一个伪句柄、可以用 DuplicateHandle 赋值一个句柄 等等
2) ID比句柄更轻便
在主线程中 GetCurrentThreadId、MainThreadID、MainInstance 获取的都是主线程的ID