线程基础及要点
来源:互联网 发布:生意参谋有哪些数据 编辑:程序博客网 时间:2024/05/20 12:48
线程的组成如下图所示:
可见,线程由两部分组成:
1、内核对象
操作系统用它来管理线程。还用内核对象来存放线程统计信息。
2、线程栈
用于维护线程执行时所需要的所有函数参数和局部变量。
每个线程都必须有一个入口点函数,这是线程的起点,形式如下:
DWORD WINAPI ThreadFunc(PVOID pvParam)
{
DWORD dwResult=0;
.....
return (dwResult);
}
1、线程函数的名称,可以任意命名,不一定要是ThreadFunc。如果应用程序中有多个线程,则需为他们指定不同名称的线程函数。
2、线程函数的参数,可以传递任意类型的数据的指针。只需在传递时强制转换为PVOID,在线程中调用时,再强制转换回来即可。
3、线程函数必须返回一个值,如上例中的dwResult,这将成为线程的退出代码。
4、线程函数中,应尽可能使用局部变量和函数参数(在线程栈上创建)。如果使用全局变量或静态变量,则其他线程也有可能破坏该变量中的值。
线程创建
如需创建线程,则可以在一个正在运行的线程中调用CreateThread.其原型为:
HANDLE WINAPI CreateThread(
__in LPSECURITY_ATTRIBUTES lpThreadAttributes,//安全性
__in SIZE_T dwStackSize,//线程栈大小上限
__in LPTHREAD_START_ROUTINE lpStartAddress,//线程函数入口地址
__in LPVOID lpParameter,//传入线程函数的参数
__in DWORD dwCreationFlags,//可设定线程创建完毕后,立即运行或挂起
__out LPDWORD lpThreadId,//如需返回线程ID,则可在此传入一个DWORD指针。否则,传入NULL
);
调用CreateThread时,系统会创建一个线程内核对象。并从进程地址空间中分配内存给线程栈。
因为新线程与负责创建的那个线程在同一个上下文中运行,则新线程可以访问进程内核对象的所有句柄、进程中的所有内存、同一进程中其他线程的栈。这样一来,线程间通信就非常容易。
终止线程
有四种方式
1、线程函数返回
被推荐的方式。因为这种自然的返回,可以让应用程序的清理工作都得以执行。
2、线程自己调用ExitThread
可强制线程终止运行,并导致系统清理该线程的所有资源。但是线程中的c/c++资源(如c++类对象)不会被销毁。所有更好的做法是让线程自然返回。
3、线程调用TerminateThread终止其他线程
不被推荐的方式。TerminateThread可以杀死任何线程。因为被杀死的线程不会收到被“杀死”的通知。所以线程无法正确清理,而且不能阻止自己被终止运行。
使用TerminateThread后,被杀死的线程不会销毁这个线程的堆栈。
此外,DLL通常会在线程终止运行时收到通知并处理(如果此DLL有DllMain函数的话),如果使用TerminateThread结束线程,则DLL不会收到通知。其结果就是不能执行正常的清理工作。
4、包含线程的进程终止
如调用ExitProcess或TerminateProcess,则进程中所有的线程都会退出。当然,这种效果相当于对所有线程调用TerminateThread。
线程终于运行时,会发生下面的事
1、线程退出代码,由STILL_ACTIVE变成传给ExitThread或TerminateThread的代码(线程函数的返回值)
2、线程内核对象的状态,变为触发状态。图中的“已通知(Signaled)=TRUE”
3、如果线程是进程中的最后一个活动线程,则系统认为进程也终止了
4、线程内核对象的使用计数递减1
线程内幕
结合本文开始的图示。
对CreateThread函数的调用,导致了系统创建一个线程内核对象。
1、最初使用计数(Usage count)为2
注意:如果要销毁线程内核对象,就必须线程终止,计数减1;并且用closeHandle关闭线程句柄,计数减1变为0。
2、暂停计数(Suspend count)被设置为1
3、退出代码(Exit code)设置为STILL_ACTIVE(0x103)
4、设置为未触发状态(signaled=FALSE)
内核对象创建完毕后,则系统就为线程栈分配内存。首先被压入栈的是线程函数的参数 pvParam的值,之后便压入线程函数的地址(CreateThread参数中的lpStartAddress)。
每一个线程内核对象,都有一个上下文结构(context),上下文结构保存了线程上一次执行时CPU寄存器状态(以便windows下次调度此线程时,将上下文载入CPU寄存器继续执行)。其中最重要的两个寄存器便是IP(指令指针寄存器)和SP(栈指针寄存器)。
内核初始化时,SP设置为lpStartAddress在线程栈中的地址。IP被设置为RtlUserThreadStart函数的地址,此函数是从NTDLL.dll中导出的。参见本文开始的图例。
VOID RtlUserThreadStart(PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam) {
__try {
ExitThread((pfnStartAddr)(pvParam));
}
__except(UnhandledExceptionFilter(GetExceptionInformation())) {
ExitProcess(GetExceptionCode());
}
// 注意: 我们永远不会到达这里.
}
因为新线程的指令指针寄存器被设置为RtlUserThreadStart的地址,所以,这里才是线程的真正起点。当RtlUserThreadStart调用线程函数时,它会将线程函数地址压入堆栈,当处理完成后,则可调用 ExitThread 或 ExitProcess来终止线程或整个进程。由此可见,线程永远不可能退出RtlUserThreadStart。
- 线程基础及要点
- java面试要点---基础部分CoreJava,基础及语法
- 简单的线程池技术写法及要点
- JAVA基础:GUI事件处理及布局要点
- 浅谈服务器单I/O线程+工作者线程池模型架构及实现要点
- 浅谈服务器单I/O线程+工作者线程池模型架构及实现要点
- 进程/线程要点
- 进程/线程要点
- 线程总结要点
- 多线程、并发及线程的基础问题
- Android基础之多线程及服务
- 线程基础:多任务处理(14)——Fork/Join框架(要点1)
- 线程基础:多任务处理(15)——Fork/Join框架(要点2)
- 线程基础:多任务处理——Fork/Join框架(要点1)
- 线程基础:多任务处理——Fork/Join框架(要点2)
- java基础之线程--线程概述及创建方法
- java 基础学习之线程--多线程并发及线程锁
- Visual Basic 基础要点
- 苹果机上使用onenote2007
- WCF RIA 服务 (二)- 解决方案结构
- jQuery
- 何时用ExecuteDataSet / ExecuteReader() / ExecuteScalar
- 数据库分区
- 线程基础及要点
- 网络基础之一OSI七层模型篇
- 分页存储过程
- seo之HTML源代码优化
- WCF RIA 服务 (三)- 创建一个RIA Services Solution
- 动态生成列举例
- IMPACT软件使用(转载)
- frameset
- ARM Linux相关