首页 > 编程知识 正文

createmutex函数(CreateThread 的参数详解)

时间:2023-05-06 09:38:30 阅读:121245 作者:227

function CreateThread (

lpThreadAttributes: Pointer; {安全设定}

dw堆栈大小: dword; {堆栈大小}

lpstartaddress : tfnthreadstartroutine; {入口函数}

lpParameter: Pointer; {函数参数}

dwCreationFlags: DWORD; {启动选项}

var lpThreadId: DWORD {输出线程ID }

: MHZ; stdcall; {返回线程句柄}

1、返回值:返回线程句柄

“手柄”类似于指针,但可以使用指针读写对象。 句柄只需要使用对象

具有手柄的对象通常是系统级对象(或内核对象)。 给出方向盘而不是指针的目的只有: '安全';

用句柄似乎可以做很多事情,但是把句柄提交给某个函数(一般是系统函数)的话,我们就很难知道到现在为止了。其实系统不相信我们。

不管是指针还是句柄,都只是内存中的小数据。 一般是用结构记述的。 微软没有公开方向盘结构的详细情况。 推测应该包含:的实际指针地址、访问权限的设定、参照数等。

因为CreateThread可以返回句柄,所以表示线程属于“内核对象”。

无论线程实际属于哪个进程,它们在系统的怀里都是平等的; 如果优先级(后面将详细介绍)相同,则系统会以相同的时间间隔运行每个线程,但这种间隔很小,您可能会误以为程序在不间断地运行。

此时,您应该怀疑:系统在运行其他线程时是如何记住上一个线程的数据状态的。

有这样一种结构的TContext。 这基本上是CPU寄存器的集合,线程在这种结构中切换数据。 可以尝试用GetThreadContext函数读取寄存器。

附加此结构TContext (或: CONTEXT,_CONTEXT )的定义:

PContext=^TContext;

_CONTEXT=record

上下文标志: dword;

Dr0: DWORD;

Dr1: DWORD;

Dr2: DWORD;

Dr3: DWORD;

Dr6: DWORD;

Dr7: DWORD;

浮动保存: 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;

结束;

2、参数6 :输出线程ID

CreateThread的最后一个参数是“线程的ID”;

既然可以返回句柄,为什么还要输出这个ID? 我现在知道的是:

1、线程ID唯一的句柄可能有多个。 例如,可以在GetCurrentThread中获取伪句柄,可以在DuplicateHandle中复制句柄。

2、ID比方向盘轻。

在主线程中,GetCurrentThreadId和MainThreadID都获取主线程的ID。

主实例: indicatestheinstancehandleforthemainexecutable。

usemaininstancetoobtaintheinstancehandleforthemainexecutableofanapplication.thisisusefulinapplicationsthatuseruntimelimelibrarierier

3、参数5 :启动选项

CreateThread倒数第二个参数dwCreationFlags“启动选项”有两个选项值:

0:线程建立后立即运行条目函数;

建立CREATE_SUSPENDED:线程后,它将挂起并等待。

恢复恢复恢复线程的执行;

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

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。