当前位置: 首页 > news >正文

Windows运行机理——线程的机制(2)

1. 线程的内存泄漏的主要原因

 

在很多参考书上,都说不要用CreateThread 创建线程、并用CloseHandle来关闭这个线程,因为这样做会导致内存泄漏,而应该用_beginthread来创建线程,_endthread来销毁线程。其实,真正的原因并非如此。看如下一段代码:


None.gif HANDLE CreateThread(
None.gif
None.gif
//  线程安全属性
None.gif

None.gif        LPSECURITY_ATTRIBUTES lpThreadAttributes,   
None.gif
None.gif
//  堆栈大小
None.gif

None.gif        DWORD dwStackSize,    
None.gif
None.gif
//  线程函数
None.gif

None.gif         LPTHREAD_START_ROUTINE lpStartAddress,  
None.gif
None.gif          
// 线程参数
None.gif

None.gif         LPVOID lpParameter,    
None.gif
None.gif
//  线程创建属性
None.gif

None.gif        DWORD dwCreationFlags,                            
None.gif
None.gif
//  线程ID
None.gif

None.gifLPDWORD lpThreadId                             
None.gif
None.gif    );
None.gif
None.gif
线程中止运行后,线程对象仍然在系统中,必须通过 CloseHandle 函数来关闭该线程对象。
CloseHandle函数的原型是:
None.gif BOOL CloseHandle(
None.gif
None.gif    HANDLE hObject   
//  对象句柄
None.gif

None.gif);
None.gif
None.gif

CloseHandle可以关闭多种类型的对象,比如文件对象等,这里使用这个函数来关闭线程对象。调用时,hObject为待关闭的线程对象的句柄。

说用这种方法时内存在泄漏,其实不完全正确。那为什么会引起内存的泄漏呢?因为当线程的函数用到了C的标准库的时候,很容易导致冲突,所以在创建VC的工程时,系统提示是用单线程还是用多线程的库,因为在C的内部有很多的全局变量。例如,出错号、文件句柄等全局变量

因为在C的库中有全局变量,这样用C的库时,如果程序中使用了标准的C的库时,就很容易导致运行不正常,会引起很多的冲突。所以,微软和Borland都对C的库进行了一些改进。但是这个改进的一个条件就是,如果一个线程已经开始创建了,就应该创建一个结构来包含这些全局变量,接着把这些全局变量放入线程的上下文中和这个线程相关起来。这样,全局变量就会依赖于这个线程,不会引起冲突。

这样做就会有一个问题,什么时候这个线程开始创建呢?标准的WindowsAPI是不知道的,因为它是静态的库。这些库都是放在VCLIB的目录内的,而线程函数是操作系统的函数。所以,VCBC在创建线程时,都会用_beginThread来创建线程,再用_endThread来结束线程。这样,它们在创建线程的时候,就会知道什么时候创建了线程,并把全局变量放入某一结构中,让它和线程能关联起来。这样就不会发生冲突了。

很显然,要完成这个功能,首先需要分配结构表把全局变量包含起来。这个过程是在_beginThread时做的,而释放在_endTread内完成。

所以,当用_beginThread来创建,而用CloseHandle来关闭线程时,这时复制的全局结构就不会被释放了,这就有了内存的泄漏。这就是很多资料所说的内存泄漏问题的真正的原因

其实,可以不用_beginThread_endThread这一对函数。如果用CreateThread函数创建,用CloseHandle关闭,那么,与C有关的库就会用全局的,它们会引起冲突。所以,比较好的方法就是在线程内不用标准的C的库(可以使用Windows API的库函数)。这样就不会有什么问题,也就不会引起冲突。例如,字符串的操作函数、文件操作等。

当某个程序创建一个线程后,会产生一个线程的句柄,线程的句柄主要用来控制整个线程的运行,例如停止、挂起或设置线程的优先级等操作。一般来说,当线程启用后,就会用线程的CloseHandle来关闭线程。但在微软的示例程序中,有一个例子创建以后,就马上调用CloseHandle关闭线程的运行。这样做在Windows 98下没什么问题,但在Windows NT下,内核就会出现错误。这是为什么呢?

这是因为虽然线程有关的结构已经释放了,但线程还在运行中,所以程序就会出现错误。那怎么做才能确保正常运行呢?

其实,要正常运行,可以让线程完全结束以后,再调用CloseHandle来释放资源

怎样知道线程完全结束呢?在Windows API中有一类等待线程的命令:

 
None.gif DWORD WaitForSingleObject(
None.gif
None.gif  HANDLE hHandle,              
//  handle to object to wait for
None.gif

None.gif  DWORD dwMilliseconds   
//  time-out interval in milliseconds
None.gif

None.gif);
None.gif
None.gif 
None.gif
None.gifDWORD WaitForMultipleObjects(
None.gif
None.gif  DWORD nCount,           
//  number of handles in the handle array
None.gif

None.gif  CONST HANDLE 
* lpHandles,   //  pointer to the object-handle array
None.gif

None.gif  BOOL fWaitAll,                
//  wait flag
None.gif

None.gif  DWORD dwMilliseconds   
//  time-out interval in milliseconds
None.gif

None.gif);
None.gif
None.gif

可以用以上两函数,等待线程的结束。如果线程结束,函数就会返回。否则就一直等待,直到指定的时间结束。

还有一种线程根本不会退出,它一直运行着循环的线程。我们就要用中止线程的方法来结束线程的运行,强制把它关闭。强制关闭后,再用CloseHandle来释放结构。


2. 进程管理

 

Win16中,一个正在运行的程序被称为一个任务(task),16位的KERNEL把每一个Win16任务的信息保持在一个叫任务数据库(TDB)的段内,任务数据库的选择器被认为是一个HTASK,通过它可获知正在执行任务的API

Windows 95中,针对32位程序做了什么改进呢?它把一个运行的程序称为一个进程而不是一个任务,每一个进程运行在自己的地址空间内。它们可以看到自己的内存和操作系统,而看不到其他的进程或其他进程的空间。使进程相互之间保持分离的基本原因是防止有问题的进程影响其他进程。<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

Win32程序中,给WinMainhPrevInstance参数总是为0。不管其他程序是否运行,一般情况下,一个进程自认为系统中只有该程序在运行。当然,如果你确实需要与另外的进程通信(或是去操作另一个进程),也是很容易的,这在编写代码之前就要考虑到。

每一个Windows 95进程在系统中被分配一个单一值。这个值为进程ID,一个程序可以通过GetCurrentProcessID函数获取自己的进程ID。这个进程ID非常近似于一个Win16 HTASKNT中的进程ID分配给系统数据结构,因为典型的进程ID值是数字的,所以Windows 95中的进程ID的值比较高,并且是随机的。一个进程ID可以通过转换获取一个指示器,该指示器指向KERNEL32.DLL,用于跟踪进程的进程数据库结构。

转载于:https://www.cnblogs.com/rookieport/archive/2005/06/09/170802.html

相关文章:

  • 这两天用web标准写了个首页样式,放出pp
  • OpenSolaris的精神本质
  • ActiveX分类
  • 通过gem安装rails
  • 辞职了……
  • 从sql2000导入数据到sql2005的问题
  • SqlDataAdapter的几种常用方法
  • 推荐好书《JOEL说软件》
  • WorkFlow 实施记录(1)
  • 客户端效果总结
  • Movie
  • C#.Net一百零一夜(第一夜)
  • 华为成功破解磁悬浮列车WCDMA无缝覆盖难题
  • 数据结构-翻牌游戏
  • TreeView初始化,返回节点值的方法(转)--收藏
  • python3.6+scrapy+mysql 爬虫实战
  • Druid 在有赞的实践
  • Nodejs和JavaWeb协助开发
  • node入门
  • React Native移动开发实战-3-实现页面间的数据传递
  • React-redux的原理以及使用
  • swift基础之_对象 实例方法 对象方法。
  • VirtualBox 安装过程中出现 Running VMs found 错误的解决过程
  • 反思总结然后整装待发
  • 排序算法之--选择排序
  • 前端每日实战:70# 视频演示如何用纯 CSS 创作一只徘徊的果冻怪兽
  • 视频flv转mp4最快的几种方法(就是不用格式工厂)
  • 云大使推广中的常见热门问题
  • 阿里云重庆大学大数据训练营落地分享
  • ​LeetCode解法汇总518. 零钱兑换 II
  • ​软考-高级-系统架构设计师教程(清华第2版)【第20章 系统架构设计师论文写作要点(P717~728)-思维导图】​
  • #Linux(权限管理)
  • #Z2294. 打印树的直径
  • %3cscript放入php,跟bWAPP学WEB安全(PHP代码)--XSS跨站脚本攻击
  • ( 10 )MySQL中的外键
  • (九)One-Wire总线-DS18B20
  • (三) prometheus + grafana + alertmanager 配置Redis监控
  • (一)SpringBoot3---尚硅谷总结
  • (原)记一次CentOS7 磁盘空间大小异常的解决过程
  • (原+转)Ubuntu16.04软件中心闪退及wifi消失
  • .bat批处理(三):变量声明、设置、拼接、截取
  • .NET delegate 委托 、 Event 事件
  • .NET/C# 使窗口永不激活(No Activate 永不获得焦点)
  • .NET/C# 使用 ConditionalWeakTable 附加字段(CLR 版本的附加属性,也可用用来当作弱引用字典 WeakDictionary)
  • .NET面试题解析(11)-SQL语言基础及数据库基本原理
  • .Net小白的大学四年,内含面经
  • .set 数据导入matlab,设置变量导入选项 - MATLAB setvaropts - MathWorks 中国
  • /dev下添加设备节点的方法步骤(通过device_create)
  • @hook扩展分析
  • @Not - Empty-Null-Blank
  • @ResponseBody
  • @staticmethod和@classmethod的作用与区别
  • [Android]常见的数据传递方式
  • [Angular] 笔记 6:ngStyle
  • [C++从入门到精通] 14.虚函数、纯虚函数和虚析构(virtual)