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

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

13小時(shí)前
161
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

更多詳細(xì)資料請(qǐng)聯(lián)系.docx

共1個(gè)文件

一、前言

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

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

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

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

#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,                   // 默認(rèn)的安全屬性
        0,                      // 使用默認(rèn)堆棧大小
        ThreadFunction,         // 線程函數(shù)
        &threadParameter,       // 傳遞給線程函數(shù)的參數(shù)
        0,                      // 創(chuàng)建標(biāo)志,0表示立即啟動(dòng)
        &threadID);             // 返回線程ID

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

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

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

    return 0;
}

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

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

image-20240715132740378

二、實(shí)操案例

2.1 CreateThread函數(shù)

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

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

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

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

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

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

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

2.2 案例1:創(chuàng)建多個(gè)線程同時(shí)運(yùn)行

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

在C語(yǔ)言中使用多線程,尤其是使用Windows API進(jìn)行多線程編程,涉及創(chuàng)建和管理多個(gè)線程來(lái)并發(fā)執(zhí)行任務(wù)。

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

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

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

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

    return 0;
}

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

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

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

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

image-20240715132714198

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

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

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

以下是一個(gè)示例:

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

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

    // 創(chuàng)建服務(wù)器套接字
    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);

    // 開(kāi)始監(jiān)聽(tīng)
    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)建子線程來(lái)處理客戶端連接
        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)建一個(gè)監(jiān)聽(tīng)特定端口的TCP服務(wù)器。每當(dāng)有客戶端連接時(shí),服務(wù)器就創(chuàng)建一個(gè)新的線程來(lái)處理該客戶端的通信。在子線程中,ClientHandler函數(shù)接收來(lái)自客戶端的數(shù)據(jù),將其打印出來(lái),并將同樣的數(shù)據(jù)回傳給客戶端。

image-20240715132548776

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

  • 更多詳細(xì)資料請(qǐng)聯(lián)系.docx
    下載

相關(guān)推薦