进程与线程的解析

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

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

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

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

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

        3.信号

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

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

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

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

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

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

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

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

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

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

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

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

整体代码实现为:

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

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

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

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

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

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

  1. SuspendThread(h_thread);
  2. SuspendThread(h_thread);
  3. ResumeThread(h_thread);

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

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

  1. ResumeThread(h_thread);
  2. ResumeThread(h_thread);
  3. SuspendThread(h_thread);
  4. SuspendThread(h_thread);

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

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

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

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

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

  1. DWORD WINAPI ThreadProc(LPVOID lpParameter)
  2. {
  3. CtestThreadDlg *pthis=(CtestThreadDlg*)lpParameter;
  4. while(pthis->m_flag)//设置一个标志,初始化为false,在线程创建时将其置为true,再次置为false,时候,线程退出
  5. {
  6. pthis->m_process.StepIt();
  7. Sleep();
  8. }
  9. return ;
  10. }
  11. void CtestThreadDlg::OnBnClickedButton1()
  12. {
  13. if(!h_thread)//如果没有线程,则创建线程,否则恢复线程
  14. {
  15. m_flag=true;
  16. h_thread=CreateThread( NULL,//安全属性
  17. ,//栈大小为1M
  18. &ThreadProc,
  19. this,
  20. ,//创建起来就跑
  21. NULL//线程id
  22. );
  23. if(NULL==h_thread)
  24. MessageBox("failed\n");
  25. }
  26. ResumeThread(h_thread);
  27. }
  28. void CtestThreadDlg::OnBnClickedButton3()
  29. {
  30. //1.正常退出
  31. m_flag=false;//这个情况是适用于正常情况下的退出,在这种情况下若是按暂停后再按下停止无法正常杀死进程(异常退出),于是要采取强制退出
  32. //2.能正常退出的正常退出,实在不行再强制杀死
  33. if(WaitForSingleObject(h_thread,)==WAIT_TIMEOUT)//代表没收到线程退出的信号
  34. { //WAIT_OBJECT_0代表收到了信号
  35. TerminateThread(h_thread,-);//可以杀死任何线程
  36.  
  37. }
  38. m_process.SetPos();//让停止后的进度条归零
  39. if(h_thread)
  40. {
  41. CloseHandle(h_thread);
  42. h_thread=NULL;
  43. }
  44.  
  45. }
  1. 线程可以通过以下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. linux 中的定时任务crontab使用方法

    linux 中的定时任务crontab使用方法: 切换到root用户,sudo su root (可以设置成不需要输入密码) sudo su - (需要输入当前帐号的密码才能进入.) crontab ...

  2. 【JavaScript】数组随机排序 之 Fisher–Yates 洗牌算法

    Fisher–Yates随机置乱算法也被称做高纳德置乱算法,通俗说就是生成一个有限集合的随机排列.Fisher-Yates随机置乱算法是无偏的,所以每个排列都是等可能的,当前使用的Fisher-Yat ...

  3. STM32.BOOT

    BOOT0 和 BOOT1STM32 三种启动模式对应的存储介质均是芯片内置的,它们是:1)用户闪存 = 芯片内置的?Flash.2)SRAM = 芯片内置的 RAM 区,就是内存啦.3)系统存储器 ...

  4. Python3基础 str while+iter+next 字符串的遍历

             Python : 3.7.0          OS : Ubuntu 18.04.1 LTS         IDE : PyCharm 2018.2.4       Conda ...

  5. perl入门知识(3)

    引用       在很多场合下使用引用传值,能在很大程度上提高代码的运行效率.       定义一个引用在变量名前加”\”就可以了,如:       $ra=\$a;       $rb=\@b;   ...

  6. C#学习笔记(十八):数据结构和泛型

    数据结构 只有这四种 a.集合:数据之间没有特定的关系 b.线性结构:数据之间有一对一的前后联系 c.树形结构:数据之间有一对多的关系,一个父节点有多个子节点,一个子节点只能有一个父节点 d.图状结构 ...

  7. js函数事件对象

    每个函数都有4个默认对象 arguments 保存着实际传入的参数,集合列表 return 有两个功能,打断函数和返回函数值 this 谁调用的函数,this就是谁 event 事件对象 事件 box ...

  8. MySQL查询优化之性能提升一个数量级

    这段时间一直在用kettle做数据抽取和报表,写SQL便是家常便饭了,200行+SQL经常要写.甚至写过最长的一个SQL500多行将近600行.这么长的SQL估计大部分人连看的意愿都没有,读起来也比较 ...

  9. WiscKey: Separating Keys from Values in SSD-Conscious Storage [读后整理]

    WiscKey: Separating Keys from Values in SSD-Conscious Storage WiscKey是一个基于LSM的KV存储引擎,特点是:针对SSD的顺序和随机 ...

  10. Spring 事物机制总结

    Spring两种事物处理机制,一是声明式事务,二是编程式事务 声明式事物 1)Spring的声明式事务管理在底层是建立在AOP的基础之上的.其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加 ...