一、前言
線程是比進(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ū)等,以確保線程安全和程序的正確性。
二、實(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)閉所有線程的句柄,這是必要的資源清理步驟,以避免資源泄漏。
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ù)回傳給客戶端。
由于CreateThread
函數(shù)創(chuàng)建的線程默認(rèn)是守護(hù)線程(非前臺(tái)線程),因此主線程結(jié)束時(shí),子線程也將被終止。在上面的代碼中,CloseHandle
函數(shù)被用來(lái)關(guān)閉線程句柄,但這并不意味著線程立即結(jié)束,它只是釋放了主線程對(duì)線程句柄的引用。