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 線程的入口函數地址
相關文章
相關標籤/搜索