《Windows核心编程》第二部分(线程)

(3)线程

多线程的优点:可以让用户同时体验应用程序的多种服务。
多线程的缺陷:在多线程访问一共享资源的时候会发生冲突。
和进程一样,线程也由两部分组成。
(1)内核对象:一种数据结构,管理和存储线程的有关信息。
(2)线程堆栈:维护线程运行过程中需要的内存空间。
同一个进程中的线程共享进程的地址空间,共享进程句柄表,共享其他线程的线程堆栈。
主线程的入口点函数是_tmain或_tWinmain。如果在进程中创建新线程必须提供自己的入口点函数。创建的线程必须有一个进入点函数,如下:

DWORD ThreadProc(PVOID pvParam){
    int temp=(int)pvParam;
    //其中写我们需要线程执行的任务
    return 0;
}

线程函数只有一个参数,这个参数可以由我们自己定义。在结束时必须返回一个值,传递给ExitThread,作为函数的退出代码。

在线程函数中如果没有特殊需求,尽量使用作用域只在函数内有效的变量,定义静态变量所有线程都可以引用有可能出现线程间的同步问题。

线程创建函数。

HANDLE CreateThread(  
    //指明内核对象的安全属性
    PSECURITY_ATTRIBUTE psa,  
    //cbStack参数指定线程栈的大小
    //主进程的cbStack在进程创建时会自动通过调用CreateThread创建一个主进程,
    //可以使用链接程序/STACK开关来控制这个值。/STACK:[reserve],[commit]
    //reserve设置系统应该为线程堆栈保留的地址空间量,默认1MB。
    //commit设置默认保证用于堆栈存储的物理存储器的容量,默认1页。
    //主动调用CreateThread创建非主线程,如果值不为0就将所有的存储器保留并分配给线程
    //保留空间是/STACK的值和cbStack中较大的一个。
    //如果传入0则将链接程序嵌入.exe文件的/STACK确定。
    //保留空间用于限制堆栈的上限,防止无限递归。
    DWORD cbStack,  
    //pfnStartAddr参数指定线程函数的地址
    PTHREAD_START_ROUTINE pfnStartAddr,  
    //pvParam是传给线程函数的参数
    PVOID pvParam,  
    //dwCreateFlags参数控制线程的创建后的行为
    DWORD dwCreateFlag,  
    //pdwThreadID参数存储线程的ID
    PDWORD pdwThreadID
); 

线程的结束方式:
1:线程函数返回。
这一种方法是最安全的方法,线程函数自己结束会通过调用ExitThread来调用C++对象的析构函数来释放空间。
2:线程调用ExitThread杀死自己。
此种方式也会释放所有的操作系统的资源,只是所有的C++对象的析构函数不会被调用。
3:其他线程调用TerminateThread。
此方式会把线程直接杀死,可以杀死任何线程,不限于自己,其所占的资源不会被清理,只能等待进程结束清理空间时会清理。TerminateThread为异步方法,必须调用WaitForSingleObject或类似的函数来监听。
4:进程终止导致线程终止。
线程终止运行时,线程所拥有的所有用户对象句柄会被释放。但是也是没有正常调用C/C++的析构函数,数据没转至磁盘

线程终止运行时的操作:
1:线程拥有的所有用户对象被释放
2:退出代码从STILL_ACTIVE改为传给ExitThread或TerminateThread的代码
3:线程对象变为已通知
4:如果是进程中最后一个线程则进程终止
5:线程内核对象引用减一

//获取线程退出代码函数,来判断线程是否已经终止运行。
//也可以使用WaitForSingleObject来监听线程内核对象。
BOOL GetExitCodeThread(HANDLE hThread,PDWORD pdwExitCode);

0_1527567905433_ad6bdb35-fb51-4768-8417-6f751a6da199-image.png
使用计数:CreateThread函数创建线程内核对象,该对象的最初使用计数为2。一个是该函数返回的句柄对该内核对象的引用,一个是线程本身也占有一个引用。

线程上下文:线程私有的一组寄存器用于保存保存当前线程上一次执行时CPU寄存器的状态,保存在线程内核对象的CONTEXT结构中,线程初始化时CONTEXT结构中的堆栈指针SP设为pfnStartAddr即线程执行函数,指针寄存器IP设置为BaseThreadStart函数,即线程开始是从BaseThreadStart函数开始的。

暂停计数:在线程初始化完后根据是否传递CREATE_SUSPENDED标志来判断是否暂停,如果没有传递则置为0。(初始为1)

已通知标志:初始为未通知状态。

注释:
主线程初始化时调用的是BaseProcessStart函数,该函数和BaseThreadStart函数类似,唯一差别是没有引用pvParam参数。

为了让C/C++创建新线程时有自己的独立的数据块,防止多个进程之间的影响,我们必须调用C/C++多线程运行库函数 _beginthreadex 函数来创建进程。该函数只有多线程库有。

_beginThreadex函数的参数列表跟CreateThread一样,但是参数名称和类型并不完全一样。
在_beginthreadex内部,申请了_tiddata内存块,在C/C++运行堆栈中独立分配的,用于存储局部的数据。
0_1527569383684_1412ead2-003f-426e-9dc3-d13eb5562280-image.png
线程函数的地址和参数保存在_tiddata内存块中。
整体流程:
_beginthreadex
1:每个线程获得由C/C++运行库分配的tiddata内存结构。
2:线程函数保存在tiddata中
3:内部调用CreateThread
4:调用CreateThread时通知通过调用_threadstartex来执行线程,传递的参数是tiddata地址
5:执行完返回句柄
_threadstartex
1:新线程从BasethreadStart函数执行,然后转移到_threadstartex
2:tiddata地址作为唯一参数
3:TlsSetValue,是个操作系统函数,用于将一个值与调用线程关联起来。称为线程存储器TLS
4:SEH帧放置在需要的线程函数周围,用来处理与运行库相关的事情
5:调用必要的函数,传递必要的参数(tiddata的地址)
6:返回值为线程的退出代码,返回至_endthreadex
_endthreadex
1:调用TlsGetValue函数获得线程tiddata地址
2:释放该数据块,调用ExitThread销毁线程

常见用CreateThread函数创建线程在大部分情况下也是可以的,在C/C++运行库函数需要用到_tiddata内存块时如果没有会主动创建和关联,只是在后面通过ExitThread退出时内存块不会被释放。另一个问题是结构化异常没有就绪,当使用C/C++运行库的signal函数时将会导致进程终止。

建议使用_beginthreadex和_endthreadex配合使用。
千万不能与CreateThread或ExitThread、TerminateThread混用。

GetCurrentProcess和GetCurrentThread可以获得伪句柄前者返回-1后者返回-2,不能跨进程访问因为-1,-2在句柄表中本就是不存在的。可以通过DuplicateHandle来实现复制句柄,并在不使用时要用CloseHandle关闭句柄。

避免使用_beginthread和_endthread来创建线程,限制较多,_endthread会自动关闭句柄,造成不方便。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值