PoEdu - Windows阶段班 【Po学校】Lesson06 线程

win32API 线程

  • 线程 函数

    • 复习:进程启动

      • 进程创建之始,会新建一个“进程内核对象”,此对象包含有进程的许多参数,如:进程地址空间;进程是一堆数据组合,它有一定的惰性。
      • 接着会启动一个线程,线程才是CPU执行的基础单位。
    • 在进入main函数启动之前,就已经准备好了堆栈,启动好了线程。

 

 

    • 本质上:main函数也是一个线程函数,它符合线程运行的规则:线程运行前,操作系统要知道,线程从哪个函数开始运行。此时默认找到当前函数,线程运行开动。
  • 线程 堆栈

    • 入口函数是编译器帮我们创建完成的线程函数
    • 一般线程,需要CreateTread()函数创建线程,同时创建一个“线程内核对象(结构体)”,操作系统通过此结构体来管理线程。
    • 接着会在当前进程空间内,创建一块空间,将此空间当作当前线程的堆栈。

 

 

  • 线程创建所需参数

 

 

  • 第1个参数:线程继承相关属性

    • 进程之间继承关系的本质:

 

 

  • 第2个参数 堆大小的设置

    • 默认线程堆栈大小1MB

 

 

    • 当默认的1MB或者自定义的大小不够用时,会抛出栈溢出,被当前程序捕获后,再次分配更多的空间,这是一个动态分配的机制。
    • 有一种情况下,会导致栈溢出:当一次性需要的堆栈空间过大,1MB不够用的时候,第2次分配时,还是需要大于1MB空间,会抛出栈溢出错误。
      • 两种解决方式 : 1 在CreateThread()函数中,把初始保留堆栈大小设置得更大;
      • 2 把需要一次性使用的堆栈,设置得小一些,通过分批分次的方式使用初始保留堆栈空间。
  • 第3个参数 线程开始的地址,一般是某个函数的地址。

    • 线程的入口函数,必须符合以下几点:

 

 

    • 1 必须 是一个stdcall
    • 2 必须返回一个DWORD
    • 3 必须带一个LPVOD参数,这个参数可以在紧接着的第4个参数传递。
  • 第4个参数 传递给第3个参数(也就是回调函数),注意传递之间参数的生命周期

  • 第5个参数 标志位,0表示 创建成功后,直接运行; CREATE_SUSPENDED表示暂停。

  • 第6个参数 传递一个ThreadID

  • 主线程退出与其他线程退出 之间的区别:
    • 主线程退出,进程消亡,会清理所有线程。
    • 一般的线程(非主线程)退出,其子线程不会随之消亡,要在运行完成后才消亡。
    • 一般的线程与一般子线程之间抢占CPU资源。
  • 一般线程的正确使用 7步

 

 

    • 多线程中参数的传递,需要格外的注意:父进程的消亡,子进程还存在时,参数的生命周期是否有保障

    • 深入理解时间片 (实验)

      #include <windows.h>
      #include <tchar.h>
      enum ThreadSign
      {
      NO1,
      NO2,
      NO3
      };
      ThreadSign g_ThreadSign;
      DWORD WINAPI ThreadFuncNo1(LPVOID lParama)
      {
      for (int i = 1; i <=100 ; ++i)
      {
      while (g_ThreadSign != NO1)
      {
      Sleep(1);
      }
      _tprintf(TEXT("No1:%d\r\n"), i);
      g_ThreadSign = NO2;
      }
      return 0;
      }
      DWORD WINAPI ThreadFuncNo2(LPVOID lParama)
      {
      for (int i = 101; i <= 200; ++i)
      {
      while (g_ThreadSign != NO2)
      {
      Sleep(1);
      } 
      _tprintf(TEXT("No2:%d\r\n"), i);
      g_ThreadSign = NO3;
      }
      return 0;
      }
      int main()
      {
      HANDLE hThread[2];
      hThread[0] = CreateThread(nullptr, 0, ThreadFuncNo1, nullptr, 0, nullptr); 
      hThread[1] = CreateThread(nullptr, 0, ThreadFuncNo2, nullptr, 0, nullptr);
      g_ThreadSign = NO1;
      for (int i = 201; i <= 300; ++i)
      {
      while (g_ThreadSign != NO3)
      {
      Sleep(1);
      } 
      _tprintf(TEXT("No3:%d\r\n"), i);
      g_ThreadSign = NO1;
      }
      WaitForMultipleObjects(2,hThread,TRUE,INFINITE);
      for (int i = 0; i < sizeof(hThread)/sizeof(hThread[0]); ++i)
      {
      CloseHandle(hThread[i]);
      }
      
      return 0;
      }
      

       

    • 线程的退出:当一个线程结束时,会发生哪些事情?

      • 先来看 进程 的销毁:
        • 销毁临时对象
        • 释放堆栈
        • 将返回值设置为我的退出代码
        • 减少进程内核对象的使用计数
      • 实际上线程的销毁与进程的销毁相同的。当一个 线程 销毁时:
        • 销毁临时对象,调用我们的析构函数
        • 释放线程里面所有分配的堆栈
        • 将线程的入口函数的返回值设置为我们的退出代码
        • 减少线程内核对象的使用计数
    •   线程退出函数:

        • ExitThread 立即结束当前线程
          • 终止线程运行,销毁堆栈
          • 并释放以下资源:(窗口句柄 与 HOOK对象是线程的标配)
            • 1 如有创建窗口,则还释放窗口句柄;
            • 2 HOOK(勾子)对象。
          • 不调用析构函数,导致内存泄漏
        • TerminateThread 可以结束其他的线程;此函数的动作,与ExitThread()函数销毁动作类似。
        • 回头看线程启动中,调用了哪些资源?
          • 注意一点:线程没有自己的内存空间,内存空间是进程所属。线程在启动时,去进程当中申请一块内存,作为当前线程的栈;
          • 创建之初,构建一个线程内核对象,线程内核对象的结构中,包含与进程相同的参数:1 使用计数,2 退出代码(ExitCode)等。但线程会多出一个信号参数----受信状态:Signaled(信号),用以调控线程的执行优先顺序。
          • 另外在结构体之外,还有一个CONTEXT(线程上下文)参数,存储了当前CPU寄存器的状态: IP(指定寄存器:下一条指令是什么)、SP(栈寄存器:从哪个地址执行);
          • 线程在创建之初,还有两个参数是不可或缺的:
            • lParam
            • lpStartAddress 线程的入口函数地址


posted on 2017-06-15 13:40  zzdoit  阅读(341)  评论(0编辑  收藏  举报

导航