9.1 運用API創建多線程

来源:https://www.cnblogs.com/LyShark/archive/2023/10/01/17738611.html
-Advertisement-
Play Games

在Windows平臺下創建多線程有兩種方式,讀者可以使用`CreateThread`函數,或者使用`beginthreadex`函數均可,兩者雖然都可以用於創建多線程環境,但還是存在一些差異的,首先`CreateThread`函數它是`Win32 API`的一部分,而`_beginthreadex`... ...


在Windows平臺下創建多線程有兩種方式,讀者可以使用CreateThread函數,或者使用beginthreadex函數均可,兩者雖然都可以用於創建多線程環境,但還是存在一些差異的,首先CreateThread函數它是Win32 API的一部分,而_beginthreadexC/C++運行庫的一部分,在參數返回值類型方面,CreateThread返回線程句柄,而_beginthreadex返回線程ID,當然這兩者在使用上並沒有太大的差異,但為了代碼更加通用筆者推薦使用後者,因為後者與平臺無關性更容易實現跨平臺需求。

9.1.1 CreateThread

CreateThread 函數是Windows API提供的用於創建線程的函數。它接受一些參數,如線程的入口函數、線程的堆棧大小等,可以創建一個新的線程並返回線程句柄。開發者可以使用該句柄控制該線程的運行狀態。需要註意,在使用CreateThread創建線程時,線程入口函數的返回值是線程的退出碼,而不是線程執行的結果值。

CreateThread 函數原型如下:

HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES   lpThreadAttributes,
  SIZE_T                  dwStackSize,
  LPTHREAD_START_ROUTINE  lpStartAddress,
  LPVOID                  lpParameter,
  DWORD                   dwCreationFlags,
  LPDWORD                 lpThreadId
);

參數說明:

  • lpThreadAttributes:指向SECURITY_ATTRIBUTES結構體的指針,指定線程安全描述符和訪問許可權。通常設為NULL,表示使用預設值。
  • dwStackSize:指定線程堆棧的大小,以位元組為單位。如果dwStackSize為0,則使用預設的堆棧大小。(註:在32位程式下,該值的預設大小為1MB;在64位程式下,該值的預設大小為4MB)
  • lpStartAddress:指向線程函數的指針,這個函數就是線程執行的入口點。當線程啟動時,系統就會調用這個函數。
  • lpParameter:指定傳遞給線程函數的參數,可以為NULL。
  • dwCreationFlags:指定線程的創建標誌。通常設為0,表示使用預設值。
  • lpThreadId:指向一個DWORD變數的指針,表示返回的線程ID號。可以為NULL。

CreateThread 函數將創建一個新的線程,並返回線程句柄。開發者可以使用該句柄控制該線程的運行狀態,如掛起、恢復、終止等。線程創建成功後,執行線程函數進行相應的業務處理。需要註意的是,在使用CreateThread創建線程時,線程入口函數的返回值是線程的退出碼,而不是線程執行的結果值。

#include <windows.h>
#include <iostream>

using namespace std;

DWORD WINAPI Func(LPVOID lpParamter)
{
  for (int x = 0; x < 10; x++)
  {
    cout << "thread function" << endl;
    Sleep(200);
  }
  return 0;
}

int main(int argc,char * argv[])
{
  HANDLE hThread = CreateThread(NULL, 0, Func, NULL, 0, NULL);
  CloseHandle(hThread);

  for (int x = 0; x < 10; x++)
  {
    cout << "main thread" << endl;
    Sleep(400);
  }

  system("pause");
  return 0;
}

如上所示代碼中我們線上程函數Func()內沒有進行任何的加鎖操作,那麼也就會出現資源的爭奪現象,這些會被搶奪的資源就被稱為是臨界資源,我們可以通過設置臨界鎖來實現同一時刻內保持一個線程操作資源。

EnterCriticalSection 是Windows API提供的線程同步函數之一,用於進入一個臨界區並且鎖定該區域,以確保同一時間只有一個線程訪問臨界區代碼。

EnterCriticalSection函數的函數原型如下:

void EnterCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
);

參數說明:

  • lpCriticalSection:指向CRITICAL_SECTION結構體的指針,表示要進入的臨界區。

EnterCriticalSection 函數將等待,直到指定的臨界區對象可用並且已經鎖定,然後,當前線程將進入臨界區。臨界區中的代碼將在當前線程完成之前,不允許被任何其他線程執行。當線程完成臨界區的工作時,應該調用LeaveCriticalSection函數釋放臨界區。否則,其他線程將無法進入臨界區,導致死鎖。

EnterCriticalSection 函數是比較底層的線程同步函數,需要開發者自行創建臨界區,維護臨界區的狀態併進行加鎖解鎖的操作,使用時需要註意對臨界區中的操作進行適當的封裝和處理。同時,EnterCriticalSection函數也是比較高效的線程同步方式,對於需要頻繁訪問臨界資源的場景,可以通過使用臨界區來提高程式的性能。

#include <Windows.h>
#include <iostream>

int Global_One = 0;

// 全局定義臨界區對象
CRITICAL_SECTION g_cs;

// 定義一個線程函數
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
  // 加鎖防止線程數據衝突
  EnterCriticalSection(&g_cs);
  for (int x = 0; x < 10; x++)
  {
    Global_One++;
    Sleep(1);
  }

  // 執行完修改以後,需要釋放鎖
  LeaveCriticalSection(&g_cs);
  return 0;
}

int main(int argc, char * argv[])
{
  // 初始化臨界區
  InitializeCriticalSection(&g_cs);
  HANDLE hThread[10] = { 0 };

  for (int x = 0; x < 10; x++)
  {
    // 迴圈創建線程
    hThread[x] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
  }

  // 等待多個線程執行結束
  WaitForMultipleObjects(10, hThread, TRUE, INFINITE);

  // 最後迴圈釋放資源
  for (int x = 0; x < 10; x++)
  {
    CloseHandle(hThread[x]);
  }

  printf("全局變數值: %d \n", Global_One);

  // 釋放鎖
  DeleteCriticalSection(&g_cs);

  system("pause");
  return 0;
}

9.1.2 BeginThreadex

BeginThreadex 是C/C++運行庫提供的用於創建線程的函數。它也接受一些參數,如線程的入口函數、線程的堆棧大小等,與CreateThread不同的是,_beginthreadex函數返回的是線程的ID,而不是線程句柄。開發者可以使用該ID在運行時控制該線程的運行狀態。此外,_beginthreadex函數通常與_endthreadex配對使用,供線程退出時使用。

beginthreadex 函數的函數原型如下:

uintptr_t _beginthreadex(
  void*                 security,
  unsigned             stack_size,
  unsigned(__stdcall*  start_address)(void*),
  void*                 arglist,
  unsigned             initflag,
  unsigned*            thrdaddr
);

參數說明:

  • security:與Windows安全機制相關,用於指定線程的安全屬性,一般填NULL即可。
  • stack_size:指定線程的堆棧大小,以位元組為單位。如果stack_size為0,則使用預設的堆棧大小。
  • start_address:線程函數的入口點。
  • arglist:傳遞給線程函數的參數。
  • initflag:線程標誌,0表示啟動線程後立即運行,CREATE_SUSPENDED表示啟動線程後暫停運行。
  • thrdaddr:指向unsigned變數的指針,表示返回的線程ID號。可以為NULL。

CreateThread相比,_beginthreadex函數返回線程ID而非線程句柄,使用時需要註意區分。與CreateThread不同的是,_beginthreadex函數接受傳遞給線程函數的參數放在arglist中,方便傳遞多個參數。線程使用完需要調用_endthreadex函數來關閉線程。當使用了_beginthreadex創建的線程退出時,會調用_endthreadex來結束線程,這裡的返回值會被當做線程的退出碼。

#include <windows.h>
#include <iostream>
#include <process.h>

using namespace std;

unsigned WINAPI Func(void *arg)
{
  for (int x = 0; x < 10; x++)
  {
    cout << "thread function" << endl;
    Sleep(200);
  }
  return 0;
}

int main(int argc, char * argv[])
{
  HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, Func, NULL, 0, NULL);
  CloseHandle(hThread);
  for (int x = 0; x < 10; x++)
  {
    cout << "main thread" << endl;
    Sleep(400);
  }

  system("pause");
  return 0;
}

由於CreateThread()函數是Windows提供的API介面,在C/C++語言另有一個創建線程的函數_beginthreadex()該函數在創建新線程時會分配並初始化一個_tiddata塊,這個塊用來存放一些需要線程獨享的數據,從而保證了線程資源不會發生衝突的情況,代碼只需要稍微在上面基礎上改進即可。

當然該函數同樣需要設置線程臨界區而設置方式與CreateThread中所展示的完全一致。

#include <stdio.h>
#include <process.h>
#include <windows.h>

// 全局資源
long g_nNum = 0;

// 子線程個數
const int THREAD_NUM = 10;

CRITICAL_SECTION  g_csThreadCode;

unsigned int __stdcall ThreadFunction(void *ptr)
{
  int nThreadNum = *(int *)ptr;

  // 進入線程鎖
  EnterCriticalSection(&g_csThreadCode);
  g_nNum++;
  printf("線程編號: %d --> 全局資源值: %d --> 子線程ID: %d \n", nThreadNum, g_nNum, GetCurrentThreadId());

  // 離開線程鎖
  LeaveCriticalSection(&g_csThreadCode);
  return 0;
}

int main(int argc,char * argv[])
{
  unsigned int ThreadCount = 0;
  HANDLE handle[THREAD_NUM];

  InitializeCriticalSection(&g_csThreadCode);

  for (int each = 0; each < THREAD_NUM; each++)
  {
    handle[each] = (HANDLE)_beginthreadex(NULL, 0, ThreadFunction, &each, 0, &ThreadCount);
    printf("線程ID: %d \n", ThreadCount);
  }

  WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);

  DeleteCriticalSection(&g_csThreadCode);

  system("pause");
  return 0;
}

總的來說,_beginthreadexCreateThread更加高級,封裝了許多細節,使用起來更方便,特別是對於傳遞多個參數的情況下,可以更簡單地傳參。

本文作者: 王瑞
本文鏈接: https://www.lyshark.com/post/922df2e6.html
版權聲明: 本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!

文章作者:lyshark (王瑞)
文章出處:https://www.cnblogs.com/LyShark/p/17738611.html
本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 【中秋國慶不斷更】HarmonyOS對通知類消息的管理與發佈通知(上) 一、 通知概述 通知簡介 應用可以通過通知介面發送通知消息,終端用戶可以通過通知欄查看通知內容,也可以點擊通知來打開應用。 通知常見的使用場景: ​ ● 顯示接收到的短消息、即時消息等。 ​ ● 顯示應用的推送消息,如廣告、版本 ...
  • WebSocket是一種在單個TCP連接上進行全雙工通信的協議。WebSocket使得客戶端和伺服器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。在WebSocket API中,瀏覽器和伺服器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,併進行雙向數據傳輸。 ...
  • @Styles和@Extend僅僅應用於靜態頁面的樣式復用,stateStyles可以依據組件的內部狀態的不同,快速設置不同樣式。這就是我們本章要介紹的內容stateStyles(又稱為:多態樣式)。 概述 stateStyles是屬性方法,可以根據UI內部狀態來設置樣式,類似於css偽類,但語法不 ...
  • Sentinel 簡介 Sentinel 是阿裡中間件團隊開源的,面向分散式服務架構的高可用流量防護組件,主要以流量為切入點,從限流、流量整形、熔斷降級、系統負載保護、熱點防護等多個維度來幫助開發者保障微服務的穩定性 Sentinel 提供了兩個服務組件: Sentinel 用來實現微服務系統中服務 ...
  • 開心一刻 昨晚,老婆輔導女兒寫作業 有一道形容媽媽的題,女兒寫下了:我媽媽像一個暴躁的老虎 老婆拿起題冊輕輕敲了下女兒,生氣到:有這麼形容你媽的嗎 女兒:你看你現在 老婆:我有那麼暴躁嗎,你就不能說我媽媽像一個公主,溫柔大方漂亮? 女兒:題目讓我造句,沒讓我造謠! 我:哈哈哈哈! 郵件發送 基於 J ...
  • 在Go語言中,結構體是核心的數據組織工具,提供了靈活的手段來處理複雜數據。本文深入探討了結構體的定義、類型、字面量表示和使用方法,旨在為讀者呈現Go結構體的全面視角。通過結構體,開發者可以實現更加模塊化、高效的代碼設計。這篇文章旨在為您提供關於結構體的深入理解,助您更好地利用Go語言的強大功能。 關 ...
  • 堆疊面積圖和麵積圖都是用於展示數據隨時間變化趨勢的統計圖表,但它們的特點有所不同。面積圖的特點在於它能夠直觀地展示數量之間的關係,而且不需要標註數據點,可以輕鬆地觀察數據的變化趨勢。而堆疊面積圖則更適合展示多個數據系列之間的變化趨勢,它們一層層的堆疊起來,每個數據系列的起始點是上一個數據系列的結束點 ...
  • 進行SSM(Spring+SpringMVC+MyBatis)集成的主要原因是為了提高開發效率和代碼可維護性。SSM是一套非常流行的Java Web開發框架,它集成了Spring框架、SpringMVC框架和MyBatis框架,各自發揮優勢,形成了一個完整的開發框架。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...