zoukankan      html  css  js  c++  java
  • windows下进程与线程剖析

    进程与线程的解析

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

    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(1)
    2     {
    3         m_process.StepIt();
    4         Sleep(100);//为了让进度条更明显,可以睡一会儿,作用是让出时间片
    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 DWORD WINAPI ThreadProc(
    2   _In_  LPVOID lpParameter//LPVOID代表void *
    3 );

     

    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(1)
     5     {
     6         pthis->m_process.StepIt();
     7         Sleep(100);
     8     }
        return 0;
    9 } 10 void CtestThreadDlg::OnBnClickedButton1() 11 { 12 HANDLE h_thread= CreateThread( NULL,//安全属性 13 0,//栈大小为1M 14 &ThreadProc, 15 this, 16 0,//创建起来就跑 17 NULL//线程id 18 ); 19 }

     

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

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

     

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

    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);

     

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

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

     

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

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

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

     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(100);
     8     }
     9     return 0;
    10 }
    11 void CtestThreadDlg::OnBnClickedButton1()
    12 {
    13     if(!h_thread)//如果没有线程,则创建线程,否则恢复线程
    14     {
    15         m_flag=true;
    16         h_thread=CreateThread(   NULL,//安全属性
    17                                 0,//栈大小为1M
    18                                 &ThreadProc,
    19                                 this,
    20                                 0,//创建起来就跑
    21                                 NULL//线程id        
    22                             );
    23             if(NULL==h_thread)
    24                 MessageBox("failed
    ");
    25     }
    26     ResumeThread(h_thread); 
    27 }
    35 void CtestThreadDlg::OnBnClickedButton3()
    36 {
    37     //1.正常退出
    38     m_flag=false;//这个情况是适用于正常情况下的退出,在这种情况下若是按暂停后再按下停止无法正常杀死进程(异常退出),于是要采取强制退出
    39     //2.能正常退出的正常退出,实在不行再强制杀死
    40     if(WaitForSingleObject(h_thread,100)==WAIT_TIMEOUT)//代表没收到线程退出的信号
    41     {                                   //WAIT_OBJECT_0代表收到了信号
    42         TerminateThread(h_thread,-1);//可以杀死任何线程
    43         
    44     }
    45     m_process.SetPos(0);//让停止后的进度条归零
    46     if(h_thread)
    47     {
    48         CloseHandle(h_thread);
    49         h_thread=NULL;
    50     }
    51     
    52 }
    线程可以通过以下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++对象撤消函数没有被调用,数据没有转至磁盘等等。

     




      

     

  • 相关阅读:
    vue-生命周期图示 注解
    vue-组件嵌套之——父组件向子组件传值
    vue-框架模板的源代码注释
    vue-小demo、小效果 合集(更新中...)
    Gulp-自动化编译sass和pug文件
    JS
    Node.js- sublime搭建node的编译环境
    sublime--package control的配置与插件安装
    git-常用命令一览表
    java面试题:jvm
  • 原文地址:https://www.cnblogs.com/curo0119/p/8343598.html
Copyright © 2011-2022 走看看