进程与线程的解析

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

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. 用rewrite把旧域名直接跳转到新域名的nginx配置

    用rewrite把旧域名直接跳转到新域名的nginx配置 把下面代码保存到daziran.com.conf 放在nginx配置目录下 /etc/nginx/conf.d/ #把旧域名zdz8207直接 ...

  2. linux查看是否有某个运行的进程命令

    linux查看是否有某个运行的进程命令:例如,查询是否包含 “my_post” 关键字的进程 ps aux | grep my_post ps aux | grep  my_post | grep - ...

  3. 解读 Q_D, Q_Q 指针

    见 qglog.h文件定义: #define Q_D(Class) Class##Private * const d = d_func()    #define Q_Q(Class) Class * ...

  4. Eclipse中把Java工程修改成web工程

    Eclipse中把Java工程修改成web工程 点击项目:右击:选择properties--输入project facets,将“Dynamic Web Module”打勾即可:

  5. 利用构建缓存机制缩短Docker镜像构建时间

    在使用Docker部署PHP或者node.js应用时,常用的方法是将代码和环境镜像打包成一个镜像然后运行,一些云厂商提供了非常便捷的操作,只需要把我们的代码提交到VCS上,然后它们就会帮我们拉取代码并 ...

  6. TimeUnit简析

    TimeUnit是java.util.concurrent包下面的一个类,主要有两种功能: 1.提供可读性更好的线程暂停操作,通常用来替换Thread.sleep() 2.提供便捷方法用于把时间转换成 ...

  7. STM32.SPI(25Q16)

    1.首先认识下W25Q16DVSIG, SOP8 SPI FLASH 16MBIT  2MB(4096个字节) (里面可以放字库,图片,也可以程序掉电不丢失数据放里面) 例程讲解: ① 1.用到SPI ...

  8. python函数作用域LEGB

    我们的在学习Python函数的时候,经常会遇到很多定义域的问题,全部变量,内部变量,内部嵌入的函数,等等,Python是如何查找的呢?以及Python又是按照什么顺序来查找的呢?这里做一个顺序的说明 ...

  9. MVC5 一套Action的登录控制流程

    流程: 用拦截器控制每一个页面请求和ajax请求,根据请求体的cookie里面是否有token判断是否登录,还必须判断该token在redis里面的缓存是否存在 组成部分: 拦截器: using Sy ...

  10. IDEA,RubyMine等JetBrains系列软件通用破解教程

    此教程不光适用于IDEA,还可以在RubyMine等JetBrains系列软件使用,亲测可用. (1)下载安装你需要的JetBrains系列软件,安装完最好不要打开,直接finish,断开网络. (2 ...