文章目录[隐藏]
封装多线程模块-线程启动
• 1、CreateThread
• 2、线程_启动_句柄()
• 3、线程_启动_逻辑()
• 4、线程句柄
• 5、线程ID
• 6、易语言SHCreateThread
SHCreateThread function | Microsoft Docs https://docs.microsoft.com/zh-cn/windows/desktop/api/shlwapi/nf-shlwapi-shcreatethread
.版本 2 .参数 线程地址, 子程序指针, , pfnThreadProc .参数 数据, 整数型, , pData,Any---0
SHCreateThread
指向包含初始化数据的可选应用程序定义数据结构的指针。它被传递给pfnThreadProc和pfnCallback(可选)指向的函数。这个值可以是NULL。
.参数 标志, 整数型, , dwFlags------0
控制函数行为的标志。一个或多个CTF常数。
.参数 返回信息, 整数型, , pfnCallback—0
SHCreateThread将等待pfnCallback所指向的函数返回,然后再返回给它的调用者。pfnCallback指向的函数的返回值被忽略。
这个函数,这是shlwapi.dll 的一个导出函数,这个函数看字面也能理解是个创建多线程的函数,而且。。。他是基于对象操作的多线程函数,相对比较稳定
API- Create Thread
CreateThread是一种微软在Windows API中提供了建立新的线程的函数,该函数在主线程的基础上创建一个新线程。线程终止运行后,线程对象仍然在系统中,必须通过CloseHandle函数来关闭该线程对象。
当某个程序创建一个线程后,会产生一个线程的句柄,线程的句柄主要用来控制整个线程的运行,例如停止、挂起或设置线程的优先级等操
第一个参数表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。是指向SECURITY_ATTRIBUTES型态的结构的指针。在Windows 98中忽略该参数。在Windows NT中,它被设为NULL。//线程安全属性
第二个参数表示线程栈空间大小。传入0表示使用默认大小(1MB)。// 堆栈大小
第三个参数表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。// 线程函数
第四个参数是传给线程函数ThreadProc的参数。这样主线程和从属线程就可以共享数据。 //线程参数
第五个参数指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,直到调用ResumeThread()。// 线程创建属性
第六个参数将返回线程的ID号,传入NULL表示不需要返回该线程ID号。// 线程ID
函数返回值:
成功返回新线程的句柄,失败返回0。
http://www.511yj.com/eyuyan-mk-xcqd.html
CreateThread使用(六个参数介绍)
function CreateThread( lpThreadAttributes: Pointer; {安全设置} dwStackSize: DWORD; {堆栈大小} lpStartAddress: TFNThreadStartRoutine; {入口函数} lpParameter: Pointer; {函数参数} dwCreationFlags: DWORD; {启动选项} var lpThreadId: DWORD {输出线程 ID } ): THandle; stdcall; {返回线程句柄}
CreateThread function | Microsoft Docs https://docs.microsoft.com/zh-cn/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createthread
1、返回值:返回线程句柄<
"句柄" 类似指针, 但通过指针可读写对象, 通过句柄只是使用对象;
有句柄的对象一般都是系统级别的对象(或叫内核对象); 之所以给我们的是句柄而不是指针, 目的只有一个: "安全";
貌似通过句柄能做很多事情, 但一般把句柄提交到某个函数(一般是系统函数)后, 我们也就到此为止很难了解更多了; 事实上是系统并不相信我们.不管是指针还是句柄, 都不过是内存中的一小块数据(一般用结构描述), 微软并没有公开句柄的结构细节, 猜一下它应该包括: 真实的指针地址、访问权限设置、引用计数等等.既然 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;
2、参数6:输出线程ID
CreateThread 的最后一个参数是 "线程的 ID";
既然可以返回句柄, 为什么还要输出这个 ID? 现在我知道的是:
1、线程的 ID 是唯一的; 而句柄可能不只一个, 譬如可以用 GetCurrentThread 获取一个伪句柄、可以用 DuplicateHandle 复制一个句柄等等.
2、ID 比句柄更轻便.
在主线程中 GetCurrentThreadId、MainThreadID获取的都是主线程的 ID.
MainInstance: Indicates the instance handle for the main executable.
Use MainInstance to obtain the instance handle for the main executable of an application. This is useful in applications that use runtime libraries or packages, when you need the handle for the executable rather than for the library.
3、参数5:启动选项
CreateThread 的倒数第二个参数 dwCreationFlags(启动选项) 有两个可选值:
0: 线程建立后立即执行入口函数;
CREATE_SUSPENDED: 线程建立后会挂起等待.
ResumeThread 恢复线程的运行; SuspendThread 挂起线程.
这两个函数的参数都是线程句柄, 返回值是执行前的挂起计数.
什么是挂起计数?
SuspendThread 会给这个数 +1; ResumeThread 会给这个数 -1; 但这个数最小是 0.
当这个数 = 0 时, 线程会运行; > 0 时会挂起.
如果被 SuspendThread 多次, 同样需要 ResumeThread 多次才能恢复线程的运行.
ResumeThread 和 SuspendThread 分别对应 TThread 的 Resume 和 Suspend 方法, 很好理解.
4、参数4:函数参数
线程入口函数的参数是个无类型指针(Pointer), 用它可以指定任何数据; 本例是把鼠标点击窗体的坐标传递给线程的入口函数, 每次点击窗体都会创建一个线程.
5、参数3:入口函数指针
到了入口函数了, 学到这个地方, 我查了一个入口函数的标准定义, 这个函数的标准返回值应该是 DWORD, 不过这函数在 Delphi 的 System 单元定义的是: TThreadFunc = function(Parameter: Pointer): Integer; 我以后会尽量使用 DWORD 做入口函数的返回值.
这个返回值有什么用呢?
等线程退出后, 我们用 GetExitCodeThread 函数获取的退出码就是这个返回值!
如果线程没有退出, GetExitCodeThread 获取的退出码将是一个常量 STILL_ACTIVE (259); 这样我们就可以通过退出码来判断线程是否已退出.
还有一个问题: 前面也提到过, 线程函数不能是某个类的方法! 假如我们非要线程去执行类中的一个方法能否实现呢?
尽管可以用 Addr(类名.方法名) 或 MethodAddress('published 区的方法名') 获取类中方法的地址, 但都不能当做线程的入口函数, 原因可能是因为类中的方法的地址是在实例化为对象时动态分配的.
后来换了个思路, 其实很简单: 在线程函数中再调用方法不就得了, 估计 TThread 也应该是这样.
CreateThread 第三个参数是函数指针, 新线程建立后将立即执行该函数, 函数执行完毕, 系统将销毁此线程从而结束多线程的故事.
6、参数2:堆栈大小
栈是私有的但堆是公用的
CreateThread 的第二个参数是分配给线程的堆栈大小.
这首先这可以让我们知道: 每个线程都有自己独立的堆栈(也拥有自己的消息队列).
什么是堆栈? 其实堆是堆、栈是栈, 有时 "栈" 也被叫做 "堆栈".
它们都是进程中的内存区域, 主要是存取方式不同(栈:先进后出; 堆:先进先出);
"栈"(或叫堆栈)适合存取临时而轻便的变量, 主要用来储存局部变量; 譬如 for i := 0 to 99 do 中的 i 就只能存于栈中, 你把一个全局的变量用于 for 循环计数是不可以的.
现在我们知道了线程有自己的 "栈", 并且在建立线程时可以分配栈的大小.
前面所有的例子中, 这个值都是 0, 这表示使用系统默认的大小, 默认和主线程栈的大小一样, 如果不够用会自动增长;
那主线程的栈有多大? 这个值是可以设定的: Project -> Options -> Delphi Compiler -> Linking(如图)
栈是私有的但堆是公用的, 如果不同的线程都来使用一个全局变量有点乱套;
为解决这个问题 Delphi 为我们提供了一个类似 var 的 ThreadVar 关键字, 线程在使用 ThreadVar 声明的全局变量时会在各自的栈中留一个副本, 这样就解决了冲突. 不过还是尽量使用局部变量, 或者在继承 TThread 时使用类的成员变量, 因为 ThreadVar 的效率不好, 据说比局部变量能慢 10 倍.
7、参数1:安全设置
CreateThread 的第一个参数 lpThreadAttributes 是指向 TSecurityAttributes 结构的指针, 一般都是置为 nil, 这表示没有访问限制; 该结构的定义是:
//TSecurityAttributes(又名: SECURITY_ATTRIBUTES、_SECURITY_ATTRIBUTES)
_SECURITY_ATTRIBUTES = record
nLength: DWORD; {结构大小}
lpSecurityDescriptor: Pointer; {默认 nil; 这是另一个结构 TSecurityDescriptor 的指针}
bInheritHandle: BOOL; {默认 False, 表示不可继承}
end;
//TSecurityDescriptor(又名: SECURITY_DESCRIPTOR、_SECURITY_DESCRIPTOR)
_SECURITY_DESCRIPTOR = record
Revision: Byte;
Sbz1: Byte;
Control: SECURITY_DESCRIPTOR_CONTROL;
Owner: PSID;
Group: PSID;
Sacl: PACL;
Dacl: PACL;
end;
够复杂的, 但我们在多线程编程时不需要去设置它们, 大都是使用默认设置(也就是赋值为 nil).我觉得有必要在此刻了解的是: 建立系统内核对象时一般都有这个属性(TSecurityAttributes);
在接下来多线程的课题中要使用一些内核对象, 不如先盘点一下, 到时碰到这个属性时给个 nil 即可, 不必再费神.
{建立事件}
function CreateEvent(
lpEventAttributes: PSecurityAttributes; {!}
bManualReset: BOOL;
bInitialState: BOOL;
lpName: PWideChar
): THandle; stdcall;
{建立互斥}
function CreateMutex(
lpMutexAttributes: PSecurityAttributes; {!}
bInitialOwner: BOOL;
lpName: PWideChar
): THandle; stdcall;
{建立信号}
function CreateSemaphore(
lpSemaphoreAttributes: PSecurityAttributes; {!}
lInitialCount: Longint;
lMaximumCount: Longint;
lpName: PWideChar
): THandle; stdcall;
{建立等待计时器}
function CreateWaitableTimer(
lpTimerAttributes: PSecurityAttributes; {!}
bManualReset: BOOL;
lpTimerName: PWideChar
): THandle; stdcall;上面的四个系统内核对象(事件、互斥、信号、计时器)都是线程同步的手段, 从这也能看出处理线程同步的复杂性; 不过这还不是全部, Windows Vista 开始又增加了 Condition variables(条件变量)、Slim Reader-Writer Locks(读写锁)等同步手段.不过最简单、最轻便(速度最快)的同步手段还是 CriticalSection(临界区), 但它不属于系统内核对象, 当然也就没有句柄、没有 TSecurityAttributes 这个安全属性, 这也导致它不能跨进程使用; 不过写多线程时一般不用跨进程啊, 所以 CriticalSection 应该是最常用的同步手段.
原文链接:http://www.cnblogs.com/del/category/174761.html
教程源码:
.版本 2
.支持库 EThread
.子程序 _按钮1_被单击
编辑框4.内容 = 到文本 (启动线程 (&子程序1, , e_hwnd))
编辑框1.内容 = 到文本 (e_hwnd)
.子程序 子程序1
.局部变量 i
.判断循环首 (e_是否启动 = 假)
i = i + 1
列表框1.加入项目 (到文本 (i), )
标签1.标题 = 到文本 (列表框1.取项目数 ())
延时 (1)
处理事件 ()
.判断循环尾 ()
.子程序 _按钮2_被单击
a_hwnd = 线程_启动_句柄 (&子程序2, , a_thid)
编辑框2.内容 = 到文本 (a_hwnd)
编辑框3.内容 = 到文本 (a_thid)
.子程序 子程序2
.局部变量 n
.判断循环首 (a_是否启动 = 假)
n = n + 1
列表框2.加入项目 (到文本 (n), )
标签2.标题 = 到文本 (列表框2.取项目数 ())
延时 (1)
处理事件 ()
.判断循环尾 ()
.子程序 _按钮3_被单击
e_是否启动 = 真
线程_关闭句柄 (e_hwnd)
.子程序 _按钮4_被单击
a_是否启动 = 真
线程_关闭句柄 (a_hwnd)
模块源码:
.版本 2
.程序集 集_多线程
.子程序 线程_启动_句柄, 整数型, 公开, 成功返回线程句柄,失败返回0
.参数 要启动的子程序, 子程序指针, , 要启动的子程序
.参数 要传去子程序的参数, 整数型, 可空, 往子程序传递一个整数型参数
.参数 线程ID, 整数型, 参考 可空, 整数型的变量,用于存储新创建的线程ID
返回 (CreateThread (0, 0, 要启动的子程序, 要传去子程序的参数, 0, 线程ID))
.子程序 线程_关闭句柄, 逻辑型, 公开, 关闭一个线程,关闭后线程继续运行,但是无法对这个线程操作
.参数 线程句柄, 整数型
返回 (CloseHandle (线程句柄))
优酷全部教程播单:http://i.youku.com/i/UNTU3NDYyODg0/playlists
交流群:521068947
本人QQ: 272586593