• 方案介紹
  • 附件下載
  • 相關推薦
申請入駐 產(chǎn)業(yè)圖譜

Windows下線程的創(chuàng)建與使用(win32-API)

6小時前
140
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

更多詳細資料請聯(lián)系.docx

共1個文件

一、前言

線程是比進程更輕量級的執(zhí)行單元,允許在一個進程中并發(fā)執(zhí)行多個控制流。每一個線程都有自己的程序計數(shù)器、寄存器集和棧空間,但它們共享所屬進程的全局數(shù)據(jù)和資源。這種共享內(nèi)存模型使線程間的通信比進程間通信更為高效,同時也帶來了潛在的同步問題,如死鎖和競態(tài)條件,需要通過適當?shù)耐綑C制來解決。

在程序設計中,線程能夠顯著提高程序的響應速度和資源利用率,特別是在處理CPU密集型或IO密集型任務時。例如,一個圖形用戶界面(GUI應用程序可以使用一個線程處理用戶輸入,而另一個線程執(zhí)行耗時的計算或網(wǎng)絡請求,這樣可以避免UI凍結,保持良好的用戶體驗。線程還常用于實現(xiàn)并行算法,加快大數(shù)據(jù)處理、圖像渲染等任務的執(zhí)行速度。

在Windows環(huán)境下,C語言可以通過調(diào)用Win32 API中的CreateThread函數(shù)來創(chuàng)建和管理線程。CreateThread函數(shù)允許你指定線程的入口點(即線程函數(shù))、線程的優(yōu)先級、堆棧大小等參數(shù)。

以下是一個使用CreateThread函數(shù)創(chuàng)建線程的簡單示例:

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

// 線程函數(shù)
DWORD WINAPI ThreadFunction(LPVOID lpParam)
{
    int id = *(int *)lpParam;
    printf("Hello from thread %dn", id);
    return 0;
}

int main()
{
    HANDLE hThread;
    DWORD threadID;
    int threadParameter = 1;

    // 創(chuàng)建線程
    hThread = CreateThread(
        NULL,                   // 默認的安全屬性
        0,                      // 使用默認堆棧大小
        ThreadFunction,         // 線程函數(shù)
        &threadParameter,       // 傳遞給線程函數(shù)的參數(shù)
        0,                      // 創(chuàng)建標志,0表示立即啟動
        &threadID);             // 返回線程ID

    if (hThread == NULL)
    {
        printf("Error creating thread. Error code: %dn", GetLastError());
        return 1;
    }

    // 等待線程結束
    WaitForSingleObject(hThread, INFINITE);

    // 關閉線程句柄
    CloseHandle(hThread);

    return 0;
}

在這個示例中,CreateThread函數(shù)接收多個參數(shù),包括一個線程函數(shù)指針、一個指向線程參數(shù)的指針、線程的創(chuàng)建標志等。當線程創(chuàng)建成功后,CreateThread函數(shù)返回一個句柄,這個句柄可以用于后續(xù)的線程控制操作,如等待線程結束、終止線程或查詢線程狀態(tài)。

通過這種方式,C語言程序員可以在Windows平臺上利用多線程編程,有效地提高程序性能和響應能力,同時解決復雜的問題域。多線程編程同時也帶來了同步和死鎖等問題,需要開發(fā)者采用合適的同步機制,如互斥量、信號量、臨界區(qū)等,以確保線程安全和程序的正確性。

image-20240715132740378

二、實操案例

2.1 CreateThread函數(shù)

CreateThread函數(shù)是Windows API中用于創(chuàng)建新線程的核心函數(shù)。在C或C++語言中,可以從一個現(xiàn)有的進程中啟動一個新的執(zhí)行流。

下面詳細介紹了CreateThread函數(shù)的原型和每個參數(shù)的意義:

HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES lpThreadAttributes, // 線程安全性屬性
  SIZE_T                dwStackSize,        // 線程堆棧大小
  LPTHREAD_START_ROUTINE lpStartAddress,    // 線程函數(shù)的入口點
  LPVOID                lpParameter,        // 傳遞給線程函數(shù)的參數(shù)
  DWORD                 dwCreationFlags,    // 創(chuàng)建線程的標志
  LPDWORD               lpThreadId          // 輸出參數(shù),接收線程ID
);
  • lpThreadAttributes: 是一個指向SECURITY_ATTRIBUTES結構的指針,用于指定線程的安全屬性,比如權限和安全描述符。如果你不需要特別的安全設置,通??梢詡鬟fNULL
  • dwStackSize: 是一個SIZE_T類型的值,用來指定新線程的堆棧大?。ㄒ宰止?jié)為單位)。如果設置為0,則使用系統(tǒng)的默認堆棧大小。
  • lpStartAddress: 是一個LPTHREAD_START_ROUTINE類型的指針,指向線程的起始函數(shù)。這是一個回調(diào)函數(shù),當線程開始執(zhí)行時會被調(diào)用。這個函數(shù)的原型通常如下:
    DWORD WINAPI ThreadFunction(LPVOID lpParameter);
    

    其中lpParameter是在CreateThread調(diào)用中傳遞的參數(shù)。

  • lpParameter: 是一個LPVOID類型的指針,可以用來向線程函數(shù)傳遞參數(shù)。這個參數(shù)會被直接傳遞給lpStartAddress所指向的函數(shù)。
  • dwCreationFlags: 是一個DWORD類型的值,用于指定線程創(chuàng)建的標志。常見的標志包括:
    • 0: 立即開始執(zhí)行線程。
    • CREATE_SUSPENDED: 創(chuàng)建線程但不立即執(zhí)行它。線程處于掛起狀態(tài),可以通過ResumeThread函數(shù)恢復執(zhí)行。
  • lpThreadId: 是一個指向DWORD類型的指針,CreateThread成功創(chuàng)建線程后,會將線程的唯一標識符(ID)寫入這個指針所指向的位置。這個ID可以用于后續(xù)的線程管理和控制。

CreateThread函數(shù)的返回值是一個HANDLE類型的值,這是新創(chuàng)建線程的句柄。這個句柄可以用于后續(xù)的線程控制操作,比如WaitForSingleObject(等待線程結束)、TerminateThread(終止線程)或ResumeThread(恢復掛起的線程)。

一旦線程完成執(zhí)行,或被終止,線程對象仍然存在,直到CloseHandle函數(shù)被調(diào)用來釋放它。因此,在使用CreateThread創(chuàng)建線程后,記得在適當?shù)臅r候調(diào)用CloseHandle來清理資源。

2.2 案例1:創(chuàng)建多個線程同時運行

開發(fā)環(huán)境:在Windows下安裝一個VS即可。我當前采用的版本是VS2020。

在C語言中使用多線程,尤其是使用Windows API進行多線程編程,涉及創(chuàng)建和管理多個線程來并發(fā)執(zhí)行任務。

下面代碼,演示了如何在C語言中創(chuàng)建多個線程,并讓它們同時運行,每個線程執(zhí)行簡單的打印操作。此代碼將創(chuàng)建五個線程,每個線程都會打印一條消息。

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

// 線程函數(shù)
DWORD WINAPI PrintMessage(LPVOID lpParam)
{
    int id = (int)lpParam;
    printf("Hello from thread ID: %dn", id);
    return 0;
}

int main()
{
    HANDLE hThreads[5]; // 數(shù)組用于保存所有線程的句柄
    DWORD threadIDs[5]; // 數(shù)組用于保存所有線程的ID

    // 創(chuàng)建五個線程
    for (int i = 0; i < 5; i++)
    {
        hThreads[i] = CreateThread(
            NULL,                   // 默認安全屬性
            0,                      // 使用默認堆棧大小
            PrintMessage,           // 線程函數(shù)
            (LPVOID)(i + 1),        // 傳遞給線程函數(shù)的參數(shù)
            0,                      // 創(chuàng)建標志,0表示立即啟動
            &threadIDs[i]);         // 返回線程ID
        if (hThreads[i] == NULL)
        {
            printf("Failed to create thread %d.n", i);
            return 1;
        }
    }

    // 等待所有線程結束
    for (int i = 0; i < 5; i++)
    {
        WaitForSingleObject(hThreads[i], INFINITE);
    }

    // 關閉所有線程句柄
    for (int i = 0; i < 5; i++)
    {
        CloseHandle(hThreads[i]);
    }

    return 0;
}

在這段代碼中,PrintMessage函數(shù)是每個線程將要執(zhí)行的任務。它接收一個LPVOID類型的參數(shù),這個參數(shù)是在CreateThread函數(shù)中傳遞的。在這個例子中,我們傳遞了一個整數(shù)i+1作為參數(shù),這使得每個線程都有一個唯一的ID。

main函數(shù)中,我們使用一個循環(huán)來創(chuàng)建五個線程。每個線程的句柄被存儲在hThreads數(shù)組中,而每個線程的ID則存儲在threadIDs數(shù)組中。CreateThread函數(shù)的最后一個參數(shù)&threadIDs[i]是一個指向數(shù)組元素的指針,用于接收新創(chuàng)建線程的ID。

在所有線程創(chuàng)建完畢后,再次使用一個循環(huán)來等待所有線程結束。WaitForSingleObject函數(shù)用于阻塞當前線程,直到指定的線程結束。由于我們使用INFINITE作為超時值,這意味著WaitForSingleObject將一直等待,直到指定的線程確實結束。

最后,使用另一個循環(huán)來關閉所有線程的句柄,這是必要的資源清理步驟,以避免資源泄漏。

image-20240715132714198

2.3 案例2:多線程處理并發(fā)處理網(wǎng)絡請求

開發(fā)環(huán)境:在Windows下安裝一個VS即可。我當前采用的版本是VS2020。

創(chuàng)建一個使用子線程并發(fā)處理客戶端連接的TCP服務器是一個典型的多線程編程場景。以下是一個使用C語言和Windows Socket API(Winsock)的示例代碼,展示了如何創(chuàng)建一個TCP服務器,該服務器在接收到客戶端連接時,為每個客戶端創(chuàng)建一個子線程來處理通信。

以下是一個示例:

#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <stdio.h>
#include <string.h>

#pragma comment(lib, "ws2_32.lib")

#define SERVER_PORT 27015
#define BUFFER_SIZE 1024

// 子線程函數(shù),用于處理客戶端連接
DWORD WINAPI ClientHandler(LPVOID clientSocket)
{
    SOCKET sock = (SOCKET)clientSocket;
    char buffer[BUFFER_SIZE];
    int bytesReceived;

    while ((bytesReceived = recv(sock, buffer, BUFFER_SIZE, 0)) > 0)
    {
        buffer[bytesReceived] = '?';
        printf("Received from client: %sn", buffer);
        send(sock, buffer, bytesReceived, 0);
    }

    if (bytesReceived == SOCKET_ERROR)
    {
        printf("recv failed with error: %dn", WSAGetLastError());
    }
    else if (bytesReceived == 0)
    {
        printf("Client disconnectedn");
    }

    closesocket(sock);
    return 0;
}

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKET serverSocket;
    struct addrinfo hints, *result, *ptr;
    int iResult;
    HANDLE hThread;

    // 初始化Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0)
    {
        printf("WSAStartup failed: %dn", iResult);
        return 1;
    }

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE;

    // 解析服務器地址和端口
    iResult = getaddrinfo(NULL, "27015", &hints, &result);
    if (iResult != 0)
    {
        printf("getaddrinfo failed: %dn", iResult);
        WSACleanup();
        return 1;
    }

    // 創(chuàng)建服務器套接字
    ptr = result;
    serverSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
    if (serverSocket == INVALID_SOCKET)
    {
        printf("socket failed with error: %ldn", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }

    // 綁定套接字
    iResult = bind(serverSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
    if (iResult == SOCKET_ERROR)
    {
        printf("bind failed with error: %dn", WSAGetLastError());
        freeaddrinfo(result);
        closesocket(serverSocket);
        WSACleanup();
        return 1;
    }

    freeaddrinfo(result);

    // 開始監(jiān)聽
    iResult = listen(serverSocket, SOMAXCONN);
    if (iResult == SOCKET_ERROR)
    {
        printf("listen failed with error: %dn", WSAGetLastError());
        closesocket(serverSocket);
        WSACleanup();
        return 1;
    }

    printf("Server is ready to accept connections...n");

    while (1)
    {
        SOCKET clientSocket = accept(serverSocket, NULL, NULL);
        if (clientSocket == INVALID_SOCKET)
        {
            printf("accept failed: %dn", WSAGetLastError());
            break;
        }

        // 創(chuàng)建子線程來處理客戶端連接
        hThread = CreateThread(NULL, 0, ClientHandler, (LPVOID)clientSocket, 0, NULL);
        if (hThread == NULL)
        {
            printf("CreateThread failed with error: %dn", GetLastError());
            closesocket(clientSocket);
            continue;
        }

        CloseHandle(hThread);
    }

    // 清理
    closesocket(serverSocket);
    WSACleanup();

    return 0;
}

這段代碼初始化Winsock,創(chuàng)建一個監(jiān)聽特定端口的TCP服務器。每當有客戶端連接時,服務器就創(chuàng)建一個新的線程來處理該客戶端的通信。在子線程中,ClientHandler函數(shù)接收來自客戶端的數(shù)據(jù),將其打印出來,并將同樣的數(shù)據(jù)回傳給客戶端。

image-20240715132548776

由于CreateThread函數(shù)創(chuàng)建的線程默認是守護線程(非前臺線程),因此主線程結束時,子線程也將被終止。在上面的代碼中,CloseHandle函數(shù)被用來關閉線程句柄,但這并不意味著線程立即結束,它只是釋放了主線程對線程句柄的引用。

  • 更多詳細資料請聯(lián)系.docx
    下載

相關推薦