Windows下线程的创建与使用(win32-API)
一、前言
线程是比进程更轻量级的执行单元,允许在一个进程中并发执行多个控制流。每一个线程都有自己的程序计数器、寄存器集和栈空间,但它们共享所属进程的全局数据和资源。这种共享内存模型使线程间的通信比进程间通信更为高效,同时也带来了潜在的同步问题,如死锁和竞态条件,需要通过适当的同步机制来解决。
在程序设计中,线程能够显著提高程序的响应速度和资源利用率,特别是在处理CPU密集型或IO密集型任务时。例如,一个图形用户界面(GUI)应用程序可以使用一个线程处理用户输入,而另一个线程执行耗时的计算或网络请求,这样可以避免UI冻结,保持良好的用户体验。线程还常用于实现并行算法,加快大数据处理、图像渲染等任务的执行速度。
在Windows环境下,C语言可以通过调用Win32 API中的CreateThread函数来创建和管理线程。CreateThread函数允许你指定线程的入口点(即线程函数)、线程的优先级、堆栈大小等参数。

以下是一个使用CreateThread函数创建线程的简单示例:
#include <windows.h>
#include <stdio.h>// 线程函数
DWORD WINAPI ThreadFunction(LPVOID lpParam)
{int id = *(int *)lpParam;printf("Hello from thread %d\n", id);return 0;
}int main()
{HANDLE hThread;DWORD threadID;int threadParameter = 1;// 创建线程hThread = CreateThread(NULL, // 默认的安全属性0, // 使用默认堆栈大小ThreadFunction, // 线程函数&threadParameter, // 传递给线程函数的参数0, // 创建标志,0表示立即启动&threadID); // 返回线程IDif (hThread == NULL){printf("Error creating thread. Error code: %d\n", GetLastError());return 1;}// 等待线程结束WaitForSingleObject(hThread, INFINITE);// 关闭线程句柄CloseHandle(hThread);return 0;
}
在这个示例中,CreateThread函数接收多个参数,包括一个线程函数指针、一个指向线程参数的指针、线程的创建标志等。当线程创建成功后,CreateThread函数返回一个句柄,这个句柄可以用于后续的线程控制操作,如等待线程结束、终止线程或查询线程状态。
通过这种方式,C语言程序员可以在Windows平台上利用多线程编程,有效地提高程序性能和响应能力,同时解决复杂的问题域。多线程编程同时也带来了同步和死锁等问题,需要开发者采用合适的同步机制,如互斥量、信号量、临界区等,以确保线程安全和程序的正确性。

二、实操案例
2.1 CreateThread函数
CreateThread函数是Windows API中用于创建新线程的核心函数。在C或C++语言中,可以从一个现有的进程中启动一个新的执行流。
下面详细介绍了CreateThread函数的原型和每个参数的意义:
HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程安全性属性SIZE_T dwStackSize, // 线程堆栈大小LPTHREAD_START_ROUTINE lpStartAddress, // 线程函数的入口点LPVOID lpParameter, // 传递给线程函数的参数DWORD dwCreationFlags, // 创建线程的标志LPDWORD lpThreadId // 输出参数,接收线程ID
);
-
lpThreadAttributes: 是一个指向
SECURITY_ATTRIBUTES结构的指针,用于指定线程的安全属性,比如权限和安全描述符。如果你不需要特别的安全设置,通常可以传递NULL。 -
dwStackSize: 是一个
SIZE_T类型的值,用来指定新线程的堆栈大小(以字节为单位)。如果设置为0,则使用系统的默认堆栈大小。 -
lpStartAddress: 是一个
LPTHREAD_START_ROUTINE类型的指针,指向线程的起始函数。这是一个回调函数,当线程开始执行时会被调用。这个函数的原型通常如下:DWORD WINAPI ThreadFunction(LPVOID lpParameter);其中
lpParameter是在CreateThread调用中传递的参数。 -
lpParameter: 是一个
LPVOID类型的指针,可以用来向线程函数传递参数。这个参数会被直接传递给lpStartAddress所指向的函数。 -
dwCreationFlags: 是一个
DWORD类型的值,用于指定线程创建的标志。常见的标志包括:0: 立即开始执行线程。CREATE_SUSPENDED: 创建线程但不立即执行它。线程处于挂起状态,可以通过ResumeThread函数恢复执行。
-
lpThreadId: 是一个指向
DWORD类型的指针,CreateThread成功创建线程后,会将线程的唯一标识符(ID)写入这个指针所指向的位置。这个ID可以用于后续的线程管理和控制。
CreateThread函数的返回值是一个HANDLE类型的值,这是新创建线程的句柄。这个句柄可以用于后续的线程控制操作,比如WaitForSingleObject(等待线程结束)、TerminateThread(终止线程)或ResumeThread(恢复挂起的线程)。
一旦线程完成执行,或被终止,线程对象仍然存在,直到CloseHandle函数被调用来释放它。因此,在使用CreateThread创建线程后,记得在适当的时候调用CloseHandle来清理资源。
2.2 案例1:创建多个线程同时运行
开发环境:在Windows下安装一个VS即可。我当前采用的版本是VS2020。
在C语言中使用多线程,尤其是使用Windows API进行多线程编程,涉及创建和管理多个线程来并发执行任务。
下面代码,演示了如何在C语言中创建多个线程,并让它们同时运行,每个线程执行简单的打印操作。此代码将创建五个线程,每个线程都会打印一条消息。
#include <windows.h>
#include <stdio.h>// 线程函数
DWORD WINAPI PrintMessage(LPVOID lpParam)
{int id = (int)lpParam;printf("Hello from thread ID: %d\n", id);return 0;
}int main()
{HANDLE hThreads[5]; // 数组用于保存所有线程的句柄DWORD threadIDs[5]; // 数组用于保存所有线程的ID// 创建五个线程for (int i = 0; i < 5; i++){hThreads[i] = CreateThread(NULL, // 默认安全属性0, // 使用默认堆栈大小PrintMessage, // 线程函数(LPVOID)(i + 1), // 传递给线程函数的参数0, // 创建标志,0表示立即启动&threadIDs[i]); // 返回线程IDif (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函数是每个线程将要执行的任务。它接收一个LPVOID类型的参数,这个参数是在CreateThread函数中传递的。在这个例子中,我们传递了一个整数i+1作为参数,这使得每个线程都有一个唯一的ID。
main函数中,我们使用一个循环来创建五个线程。每个线程的句柄被存储在hThreads数组中,而每个线程的ID则存储在threadIDs数组中。CreateThread函数的最后一个参数&threadIDs[i]是一个指向数组元素的指针,用于接收新创建线程的ID。
在所有线程创建完毕后,再次使用一个循环来等待所有线程结束。WaitForSingleObject函数用于阻塞当前线程,直到指定的线程结束。由于我们使用INFINITE作为超时值,这意味着WaitForSingleObject将一直等待,直到指定的线程确实结束。
最后,使用另一个循环来关闭所有线程的句柄,这是必要的资源清理步骤,以避免资源泄漏。

2.3 案例2:多线程处理并发处理网络请求
开发环境:在Windows下安装一个VS即可。我当前采用的版本是VS2020。
创建一个使用子线程并发处理客户端连接的TCP服务器是一个典型的多线程编程场景。以下是一个使用C语言和Windows Socket API(Winsock)的示例代码,展示了如何创建一个TCP服务器,该服务器在接收到客户端连接时,为每个客户端创建一个子线程来处理通信。
以下是一个示例:
#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// 子线程函数,用于处理客户端连接
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] = '\0';printf("Received from client: %s\n", buffer);send(sock, buffer, bytesReceived, 0);}if (bytesReceived == SOCKET_ERROR){printf("recv failed with error: %d\n", WSAGetLastError());}else if (bytesReceived == 0){printf("Client disconnected\n");}closesocket(sock);return 0;
}int main(int argc, char* argv[])
{WSADATA wsaData;SOCKET serverSocket;struct addrinfo hints, *result, *ptr;int iResult;HANDLE hThread;// 初始化WinsockiResult = WSAStartup(MAKEWORD(2, 2), &wsaData);if (iResult != 0){printf("WSAStartup failed: %d\n", 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: %d\n", iResult);WSACleanup();return 1;}// 创建服务器套接字ptr = result;serverSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);if (serverSocket == INVALID_SOCKET){printf("socket failed with error: %ld\n", 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: %d\n", WSAGetLastError());freeaddrinfo(result);closesocket(serverSocket);WSACleanup();return 1;}freeaddrinfo(result);// 开始监听iResult = listen(serverSocket, SOMAXCONN);if (iResult == SOCKET_ERROR){printf("listen failed with error: %d\n", 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: %d\n", WSAGetLastError());break;}// 创建子线程来处理客户端连接hThread = CreateThread(NULL, 0, ClientHandler, (LPVOID)clientSocket, 0, NULL);if (hThread == NULL){printf("CreateThread failed with error: %d\n", GetLastError());closesocket(clientSocket);continue;}CloseHandle(hThread);}// 清理closesocket(serverSocket);WSACleanup();return 0;
}
这段代码初始化Winsock,创建一个监听特定端口的TCP服务器。每当有客户端连接时,服务器就创建一个新的线程来处理该客户端的通信。在子线程中,ClientHandler函数接收来自客户端的数据,将其打印出来,并将同样的数据回传给客户端。

由于CreateThread函数创建的线程默认是守护线程(非前台线程),因此主线程结束时,子线程也将被终止。在上面的代码中,CloseHandle函数被用来关闭线程句柄,但这并不意味着线程立即结束,它只是释放了主线程对线程句柄的引用。
相关文章:
Windows下线程的创建与使用(win32-API)
一、前言 线程是比进程更轻量级的执行单元,允许在一个进程中并发执行多个控制流。每一个线程都有自己的程序计数器、寄存器集和栈空间,但它们共享所属进程的全局数据和资源。这种共享内存模型使线程间的通信比进程间通信更为高效,同时也带来…...
华为OD机试(C卷,100分)- 游戏分组
题目描述 部门准备举办一场王者荣耀表演赛,有 10 名游戏爱好者参与,分为两队,每队 5 人。 每位参与者都有一个评分,代表着他的游戏水平。为了表演赛尽可能精彩,我们需要把 10 名参赛者分为示例尽量相近的两队。 一队的实力可以表示为这一队 5 名队员的评分总和。 现在给你…...
centos7.9系统按cloudpods
1. 简介: Cloudpods 是一款简单、可靠的企业IaaS资源管理软件。帮助未云化企业全面云化IDC物理资源,提升企业IT管理效率。 Cloudpods 帮助客户在一个地方管理所有云计算资源。统一管理异构IT基础设施资源,极大简化多云架构复杂度和难度&…...
android apk 加固后的地图加载异常及重新签名
1.首先根据需求将打包生成后的APK进行加固,可以使用360、阿里、腾讯加固等。 2.加固后的APK无法直接安装,需要重新进行签名。 3.首先找到sdk的位置,进入build-tools目录。 4.根据gradle文件选择版本目录。 5.将加固后的APK放至该目录下。在…...
手把手搭建私人在线备份系统
对于打工人来说,什么文件最重要? 那就是——打不开的文件最重要! 那么,如何才能避免这样的事情发生呢?这时候就需要使出我们的大杀器——文件备份! 文件备份怎么搞才最合适呢? 是使用移动硬盘&a…...
数据分析实操案例分享:如何对人事数据进行BI分析?
在数据驱动时代,数据分析已经成为企业和个人获取竞争优势的关键技能。特别是在人力资源管理领域,数据分析的应用正变得越来越重要。通过对在职和离职数据的深入分析,企业不仅能够洞察员工的动态,揭示员工流动的模式、预测人才需求…...
谷粒商城实战笔记-228-商城业务-认证服务-自定义SpringSession完成子域session共享
文章目录 一,228-商城业务-认证服务-自定义SpringSession完成子域session共享1. cookieSerializer()2. springSessionDefaultRedisSerializer() 一,228-商城业务-认证服务-自定义SpringSession完成子域session共享 前面弄清楚了分布式服务中的两个问题&…...
Elasticsearch核心
一、几个核心概念 1、节点:一个节点(Node)就是一个es进程,一个服务器可以部署多个节点 查询节点以及节点信息: http://127.0.0.1:9200/_cat/nodes?v 2、角色,是指节点在集群中担任什么角色:…...
Python.NET:打开Python与.NET世界互通的大门
Python.NET 是一个强大的工具,它为 Python 程序员提供了一种与 .NET 公共语言运行时 (CLR) 无缝集成的途径。它就像一座桥梁,将 Python 的灵活性与 .NET 的强大功能连接起来,为开发者提供了前所未有的自由和可能性。 1. Python.NET 的核心价值…...
uniapp - plugins的组件配置使用
点击进入到uniapp中mp-weixin的配置中 点击进入小程序的plugin的配置 在项目中,我们可引用插件的使用,例如一些快递100,点餐插件的业务引入 添加插件 在使用插件前,首先要在小程序管理后台的“设置-第三方服务-插件管理”中添加…...
Microsoft Edge WebView2 截图
使用工具可以保存可见区域 CallDevToolsProtocolMethodAsync("Page.captureScreenshot", Params) always returns only visible part of html page including scrollbars. I tried all possible combinations of "fromSurface" and "captureBeyondVi…...
[word] 复杂文本如何仅全选word中的表格 (简单跟做即可)
问题描述 在word文档中,有各种形式的文本,有纯文本,有表格,有图片或者更多其它形式参杂在一起,本篇记录解决如何只全选中文档中的所有表格形式的部分,从而方便对表格进行批量修改和操作 环境说明 word版…...
Aop切面编程
学习视频 一、定义模型:订单保存模型,订单更新模型,业务层,日志模型 订单保存模型 /*** author durunwu* date 2024-08-20-21:04*/ Data public class SaveOrder {private Long id; }订单更新模型 /*** author durunwu* date …...
目标检测 | yolov9 原理和介绍
相关系列: 目标检测 | yolov1 原理和介绍 目标检测 | yolov2/yolo9000 原理和介绍 目标检测 | yolov3 原理和介绍 目标检测 | yolov4 原理和介绍 目标检测 | yolov5 原理和介绍 目标检测 | yolov6 原理和介绍 目标检测 | yolov7 原理和介绍 目标检测 | yolov8 原理和…...
如何在不格式化的情况下解锁Android智能手机密码
如果您忘记了密码,您的 Android 移动设备会将您锁定。发生这种情况时,通常可以通过执行恢复出厂设置来重新获得对设备的访问权限。可悲的是,这将导致所有数据的丢失。下面列出了在不丢失任何个人数据的情况下解锁锁定的Android 手机的有效方法…...
ts语法、nvm的使用以及github访问速度
TS基础语法 let aa:string "123" let bb:number 123 let cc:boolean true let dd:undefined undefined let ee:null null let list:Array<string> ["1", 2, 3] let list2:string[] ["1", 2, 3]interface Ibj {name: string,age: n…...
缓存实现方式
缓存是一个常见的话题,因为它对于提高应用程序性能至关重要。缓存是一种存储数据的临时地方,以便快速访问数据,减少对原始数据源(如数据库或文件系统)的访问次数,从而提高应用程序的响应速度和吞吐量。 Jav…...
鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射
关于中断部分系列篇将用三篇详细说明整个过程. 中断概念篇 中断概念很多,比如中断控制器,中断源,中断向量,中断共享,中断处理程序等等.本篇做一次整理.先了解透概念才好理解中断过程.用海公公打比方说明白中断各个概念…...
回归预测|基于雪消融优化相关向量机的数据回归预测Matlab程序SAO-RVM 多特征输入单输出 SAO-RVM
回归预测|基于雪消融优化相关向量机的数据回归预测Matlab程序SAO-RVM 多特征输入单输出 SAO-RVM 文章目录 前言回归预测|基于雪消融优化相关向量机的数据回归预测Matlab程序SAO-RVM 多特征输入单输出 SAO-RVM 一、SAO-RVM模型1. 基本模型原理2. 贝叶斯框架3. 模型优化流程4. 总…...
如何在HTML中创建链接?什么是CSS定位?什么是CSS优化?
HTML使用标签 <a> 来设置超文本链接。 超链接可以是一个字,一个词,或者一组词,也可以是一幅图像,您可以点击这些内容来跳转到新的文档或者当前文档中的某个部分。 当您把鼠标指针移动到网页中的某个链接上时,箭…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
