进程与线程的解析

进程:一个正在运行的程序的实例,由两部分组成:

1.一个内核对象,操作系统用它来管理进程。内核对象也是系统保存进程统计信息的地方。 
2.一个地址空间,其中包含所有可执行文件或DLL模块的代码和数据。此外,它还包含动态内存分配,比如线程堆栈和堆的分配。
   进程要做任何事情,都必须让一个线程在它的上下文中运行。该线程负责执行进程地址空间包含的代码。事实上,一个进程可以有多个线程,所有线程都在进程的地 址空间中“同时”执行代码。为此,每个线程都有它自己的一组CPU寄存器和它自己的堆栈。每个进程至少要有一个线程来执行进程地址空间包含的代码。当系统 创建一个进程的时候,会自动为进程创建第一个线程,这称为主线程。然后,这个线程再创建更多的线程,后者再创建更多的线程。。。如果没有线程要执行进程地 址空间包含的代码,进程就失去了继续存在的理由。这时,系统会自动销毁进程及其地址空间。

线程也有两个部分组成:
一个是线程的内核对象,操作系统用它管理线程。系统还用内核对象来存放线程统计信息的地方。 
一个线程栈,线程栈默认大小为1M,线程内申请的资源都放在线程栈中,用于维护线程执行时所需的所有函数参数和局部变量

内核对象又包括:1.计数器(起始值为2,线程退出减一,句柄关闭减一,该计数器为0内核对象才会消失)

        2.挂起计数器(初始值为0,计数器为0的时候该线程开始运行,每挂起一个进程,该计数器加一,每恢复一个进程,该计数器减一,且它的值只可以是非负整数)

        3.信号

进程从来不执行任何东西,它只是一个线程的容器。线程必然是在某个进程的上下文中创建的,而且会在这个进程内部“终其一生”。这意味着线程要在其进程的地址 空间内执行代码和处理数据。所以,假如一个进程上下文中有两个以上的线程运行,这些线程将共享同一个地址空间。这些线程可以执行同样的代码,可以处理相同 的数据。此外,这些线程还共享内核对象句柄,因为句柄表是针对每一个进程的,而不是针对每一个线程。

对于所有要运行的线程,操作系统会轮流为每个线程调度一些CPU时间。它会采取循环(轮询或轮流)方式,为每个线程都分配时间片(称为“量程”),从而营造出所有线程都在“并发”运行的假象。  

每个线程都有一个上下文,后者保存在线程的内核对象中。这个上下文反映了线程上一次执行时CPU寄存器的状态。大约每隔20ms,Windows都会查看 所有当前存在的线程内核对象。在这些对象中,只有一些被认为是可调度的。Windows在可调度的线程内核对象中选择一个,并将上次保存在线程上下文中的 值载入CPU寄存器。这一操作被称为上下文切换。线程执行代码,并在进程的地址空间中操作数据。又过了大约20ms,Windows将CPU寄存器存回线 程的上下文,线程不再运行。系统再次检查剩下的可调度线程内核对象,选择另一个线程的内核对象,将该线程的上下文载入CPU寄存器,然后继续。载入线程上 下文、让线程运行、保存上下文并重复的操作在系统启动的时候就开始,然后这样的操作会不断重复,直至系统关闭。

创建进程是用来占空间的,真正干活的是线程
线程的创建:

用MFC写一个简单的小例子来介绍一下工作者线程的创建:

首先我们先让进度条跑一下

 while()
{
m_process.StepIt();
Sleep();//为了让进度条更明显,可以睡一会儿,作用是让出时间片
//因为cpu是轮换时间片的,代表cpu到该线程时它放弃本次时间片,让cpu先给别人分配任务
//注意windows中的sleep的单位是毫秒,而linux中的是秒
}

我们会发现在进度条跑的时候窗口是不可以移动的,因为现在进程里干活的只有这一个线程,它在一段时间内只能干一件事,为了让跑进度条的同时也可以移动窗口,这就需要我们再创建一根线程

接下来我们用CreateThread来创建线程,参数如下

 _In_opt_   LPSECURITY_ATTRIBUTES lpThreadAttributes,//安全属性
_In_ SIZE_T dwStackSize,//栈大小,若设置为0 ,新线程的大小默认为1M,
_In_ LPTHREAD_START_ROUTINE lpStartAddress,//线程函数,该指针代表线程函数的起始地址
_In_opt_ LPVOID lpParameter,//线程函数参数
_In_ DWORD dwCreationFlags,//创建线程的标志,0位创建后线程就跑起来,CREATE_SUSPENDED为创建后状态为挂起状态
_Out_opt_ LPDWORD lpThreadId
线程函数如下:
 DWORD WINAPI ThreadProc(
_In_ LPVOID lpParameter//LPVOID代表void *
 );

WINAP代表的是调用约定,转到定义为

 #define WINAPI      __stdcall//上面的这个是c++的默认的调用约定,参数调用从右向左,函数本身去清理空间
#define WINAPIV __cdecl//下面的是c的默认调用约定,参数调用从右向左,调用者清理空间

整体代码实现为:

 DWORD WINAPI ThreadProc(LPVOID lpParameter)//当前函数时全局函数,没有this指针,类成员m_process无法直接使用,所以江this指针作为参数传进来
{
CtestThreadDlg *pthis=(CtestThreadDlg*)lpParameter;//传进来的this指针是作为void *类型传进来的,此处要强转
while()
{
pthis->m_process.StepIt();
Sleep();
}
    return 0;
}
void CtestThreadDlg::OnBnClickedButton1()
{
HANDLE h_thread= CreateThread( NULL,//安全属性
,//栈大小为1M
&ThreadProc,
this,
,//创建起来就跑
NULL//线程id
);
}

那么如果上面的线程函数中的第五个参数我们设置为CREATE_SUSPENDED让它的初始态是挂起的呢,为了解除挂起状态,我们可以用到函数ResumeThread(h_thread);

ResumeThread(h_thread);//解除挂起状态

既然有恢复函数,当然也有挂起函数

SuspendThread(h_thread);//让线程变成挂起状态

那么下面的这个小例子我们来判断一下线程是否可以运行?

 SuspendThread(h_thread);
SuspendThread(h_thread);
ResumeThread(h_thread);

答案是不能,我们在前面已经说了在线程里面有一个挂起计数器,当挂起一次,计数器加一,恢复一次计数器减一,当挂起计数器为0的时候线程开始运行。要保证挂几次就恢复几次。

那么看看下面的这个小例子呢,线程是否可以运行?答案是不能,挂起计数器只能是非负数,即使你先恢复两次线程,挂起计数器仍然是0.

 ResumeThread(h_thread);
ResumeThread(h_thread);
SuspendThread(h_thread);
SuspendThread(h_thread);

接下来我们要为我们的进度条加一些功能,加上暂停和停止的功能,暂停部分代码为:

 void CtestThreadDlg::OnBnClickedButton1()
{
if(!h_thread)//如果没有线程,则创建线程,否则恢复线程
{
h_thread=CreateThread( NULL,//安全属性
,//栈大小为1M
&ThreadProc,
this,
,//创建起来就跑
NULL//线程id
);
  if(NULL==h_thread)
  {
   MessageBox("failed!\n");
   }
}
ResumeThread(h_thread);
}
void CtestThreadDlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
SuspendThread(h_thread);
}//这段代码其实有个小问题就是你要是按多次暂停的话,要按多次开始才可以继续运行线程,可以加一个判断让线程只挂起一次,我懒得写。。。。。

接下来我们要重点实现的是停止部分的功能。

当线程退出时,线程栈也不在了,但是内核对象不一定在不在,为什么说不一定呢,这个要看句柄是否已经关闭,若已经关闭,则内核对象不在,否则还在。

内核对象不在了,线程一定不在了。
停止的代码如下:

 DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
CtestThreadDlg *pthis=(CtestThreadDlg*)lpParameter;
while(pthis->m_flag)//设置一个标志,初始化为false,在线程创建时将其置为true,再次置为false,时候,线程退出
{
pthis->m_process.StepIt();
Sleep();
}
return ;
}
void CtestThreadDlg::OnBnClickedButton1()
{
if(!h_thread)//如果没有线程,则创建线程,否则恢复线程
{
m_flag=true;
h_thread=CreateThread( NULL,//安全属性
,//栈大小为1M
&ThreadProc,
this,
,//创建起来就跑
NULL//线程id
);
if(NULL==h_thread)
MessageBox("failed\n");
}
ResumeThread(h_thread);
}
void CtestThreadDlg::OnBnClickedButton3()
{
//1.正常退出
m_flag=false;//这个情况是适用于正常情况下的退出,在这种情况下若是按暂停后再按下停止无法正常杀死进程(异常退出),于是要采取强制退出
//2.能正常退出的正常退出,实在不行再强制杀死
if(WaitForSingleObject(h_thread,)==WAIT_TIMEOUT)//代表没收到线程退出的信号
{ //WAIT_OBJECT_0代表收到了信号
TerminateThread(h_thread,-);//可以杀死任何线程 }
m_process.SetPos();//让停止后的进度条归零
if(h_thread)
{
CloseHandle(h_thread);
h_thread=NULL;
} }
线程可以通过以下4种方法来终止运行。
1.线程函数返回(这是强烈推荐的)。
始终都应该将线程设计成这样的形式,即当想要线程终止运行时,它们就能够返回。这是
确保所有线程资源被正确地清除的唯一办法。
如果线程能够返回,就可以确保下列事项的实现:
a) 在线程函数中创建的所有C + +对象均将通过它们的撤消函数正确地撤消。
b)操作系统将正确地释放线程堆栈使用的内存。
c)系统将线程的退出代码(在线程的内核对象中维护)设置为线程函数的返回值。
d)系统将递减线程内核对象的使用计数。
2.TerminateThread(h_thread,-1);//可以杀死任何线程(避免使用)
注意TerminateThread 函数是异步运行的函数,也就是说,它告诉系统你想要线程终止运行,但是,当函数返回时,不能保证线程被撤消。
如果需要确切地知道该线程已经终止运行,必须调用WaitForSingleObject或者类似的函数,传递线程的句柄。
设计良好的应用程序从来不使用这个函数,因为被终止运行的线程收不到它被撤消的通知。线程不能正确地清除,并且不能防止自己被撤消。
当线程终止运行时, DLL通常接收通知。如果使用Terminate Thread 强迫线程终止,DLL就不接收通知,这能阻止适当的清除
3.ExitThread(-1);//杀死调用它的线程(避免使用)
可以让线程调用ExitThread 函数,以便强制线程终止运行:
该函数将终止线程的运行,并导致操作系统清除该线程使用的所有操作系统资源。但是,C++资源(如C++类对象)将不被撤消。由于这个原因,
最好从线程函数返回,而不是通过调用ExitThread 来返回。
4.包含线程的进程终止运行(避免使用)
由于整个进程已经被关闭,进程使用的所有资源肯定已被清除。这当然包括所有线程的堆栈。这两个函数会导致进程中的剩余线程被强制撤消,就像从每个剩余的线程调用
TerminateThread 一样。显然,这意味着正确的应用程序清除没有发生,即C++对象撤消函数没有被调用,数据没有转至磁盘等等。


windows下进程与线程剖析的更多相关文章

  1. windows 下进程与线程的遍历

    原文:http://www.cnblogs.com/Apersia/p/6579376.html 在Windows下进程与线程的遍历有好几种方法. 进程与线程的遍历可以使用<TlHelp.h&g ...

  2. windows下进程与线程

    windows下进程与线程 Windows是一个单用户多任务的操作系统,同一时间可有多个进程在执行.进程是应用程序的运行实例,可以理解为应用程序的一次动态执行:而线程是CPU调度的单位,是进程的一个执 ...

  3. [笔记]linux下和windows下的 创建线程函数

    linux下和windows下的 创建线程函数 #ifdef __GNUC__ //Linux #include <pthread.h> #define CreateThreadEx(ti ...

  4. windows 下进程池的操作

    在Windows上创建进程是一件很容易的事,但是在管理上就不那么方便了,主要体现在下面几个方面: 1. 各个进程的地址空间是独立的,想要在进程间共享资源比较麻烦 2. 进程间可能相互依赖,在进程间需要 ...

  5. windows下进程间通信与线程间通信

    进程间通信: 1.文件映射(Memory-Mapped Files) 文件映射(Memory-Mapped Files)能使进程把文件内容当作进程地址区间一块内存那样来对待.因此,进程不必使用文件I/ ...

  6. [转] linux 下 进程和线程的区别

    1.进程与线程 进程是程序执行时的一个实例,即它是程序已经执行到课中程度的数据结构的汇集.从内核的观点看,进程的目的就是担当分配系统资源(CPU时间.内存等)的基本单位. 线程是进程的一个执行流,是C ...

  7. Linux下进程与线程的区别及查询方法

    在平时工作中,经常会听到应用程序的进程和线程的概念,那么它们两个之间究竟有什么关系或不同呢?一.深入理解进程和线程的区别 1)两者概念 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进 ...

  8. 【windows下进程searchfilterhost.exe分析】

    searchfilterhost.exe [进程信息] 进程文件: searchfilterhost.exe 进程名称: n/a 英文描述: searchfilterhost.exe is a pro ...

  9. linux 下 进程和线程的区别

    1.进程与线程 进程是程序执行时的一个实例,即它是程序已经执行到课中程度的数据结构的汇集.从内核的观点看,进程的目的就是担当分配系统资源(CPU时间.内存等)的基本单位. 线程是进程的一个执行流,是C ...

随机推荐

  1. php array 根据value获取key,in_array()判断是否在数组内实例

    php array 根据value获取key,in_array()判断是否在数组内实例 <?php header("Content-type: text/html; charset=u ...

  2. mybatis项目启动报错 The content of element type "resultMap" must match "(constructor?,id*,result*,association*,collection*,discriminator?)".

    启动项目报错 2018-02-26 17:09:51,535 ERROR [org.springframework.web.context.ContextLoader] - Context initi ...

  3. 保护Hadoop集群三大方法

    自今年以来,不少恶意软件开始频繁向Hadoop集群服务器下手,受影响最大的莫过于连接到互联网且没有启用安全防护的Hadoop集群. 大约在两年前,开源数据库解决方案MongoDB以及Hadoop曾遭受 ...

  4. Python入门之面向对象编程(二)python类的详解

    本文通过创建几个类来覆盖python中类的基础知识,主要有如下几个类 Animal :各种属性.方法以及属性的修改 Dog :将方法转化为属性并操作的方法 Cat :私人属性讲解,方法的继承与覆盖 T ...

  5. 2018-2019-1 20189218《Linux内核原理与分析》第四周作业

    构造简单的Linux内核 显然用实验楼配好的环境做这个实验太简单了,按照没有困难制造困难也要上的原则,在自己的64位虚拟机上做这个实验. 按照课本(视频)上的步骤一直做下去,到编译生成init时出现了 ...

  6. VC++ 进度条更新方案

    在实际开发中,如果有耗时操作,一般会在工作线程处理数据,然后处理完成后把时间传递到UI线程进行显示,切记不要在工作线程对UI进行操作. 场景: 1. 很多程序需要根据处理业务的进度来更新进度条,进度条 ...

  7. IOS学习基础

    http://www.jikexueyuan.com/path/ios/ 界面优化 iOS界面绘图API.控件等知识. 1,绘制图片 2,画板实例 3, 1,UIView的setNeedsDispla ...

  8. C# 将 Stream 写入文件

    public void StreamToFile(Stream stream,string fileName) { // 把 Stream 转换成 byte[] byte[] bytes = new ...

  9. xshell的Solarized Dark配色方案

    之前在ubuntu, kali, mint, air下都使用这一款配色方案,后来在网上看到有人在xshell中使用,配色方案有分享,就是一起无法导入 原来这个东西在你现有的连接无法直接导入,需要重新打 ...

  10. P4303 [AHOI2006]基因匹配 未完成

    题目 luogu 暴力60pts部分 显然如果没有出现次数==5的条件 显然是\(N_{2}\)的求lcs的模板 但是加点条件就完全不同了 思路 这个题短小精悍,不想数据结构那么傻逼无脑 我们考虑一下 ...