Windows下线程的创建与使用(win32-API)

news2024/9/21 20:54:46

一、前言

线程是比进程更轻量级的执行单元,允许在一个进程中并发执行多个控制流。每一个线程都有自己的程序计数器、寄存器集和栈空间,但它们共享所属进程的全局数据和资源。这种共享内存模型使线程间的通信比进程间通信更为高效,同时也带来了潜在的同步问题,如死锁和竞态条件,需要通过适当的同步机制来解决。

在程序设计中,线程能够显著提高程序的响应速度和资源利用率,特别是在处理CPU密集型或IO密集型任务时。例如,一个图形用户界面(GUI)应用程序可以使用一个线程处理用户输入,而另一个线程执行耗时的计算或网络请求,这样可以避免UI冻结,保持良好的用户体验。线程还常用于实现并行算法,加快大数据处理、图像渲染等任务的执行速度。

在Windows环境下,C语言可以通过调用Win32 API中的CreateThread函数来创建和管理线程。CreateThread函数允许你指定线程的入口点(即线程函数)、线程的优先级、堆栈大小等参数。

image-20240715145140163

以下是一个使用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);             // 返回线程ID

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

    // 等待线程结束
    WaitForSingleObject(hThread, INFINITE);

    // 关闭线程句柄
    CloseHandle(hThread);

    return 0;
}

在这个示例中,CreateThread函数接收多个参数,包括一个线程函数指针、一个指向线程参数的指针、线程的创建标志等。当线程创建成功后,CreateThread函数返回一个句柄,这个句柄可以用于后续的线程控制操作,如等待线程结束、终止线程或查询线程状态。

通过这种方式,C语言程序员可以在Windows平台上利用多线程编程,有效地提高程序性能和响应能力,同时解决复杂的问题域。多线程编程同时也带来了同步和死锁等问题,需要开发者采用合适的同步机制,如互斥量、信号量、临界区等,以确保线程安全和程序的正确性。

image-20240715132740378

二、实操案例

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]);         // 返回线程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函数是每个线程将要执行的任务。它接收一个LPVOID类型的参数,这个参数是在CreateThread函数中传递的。在这个例子中,我们传递了一个整数i+1作为参数,这使得每个线程都有一个唯一的ID。

main函数中,我们使用一个循环来创建五个线程。每个线程的句柄被存储在hThreads数组中,而每个线程的ID则存储在threadIDs数组中。CreateThread函数的最后一个参数&threadIDs[i]是一个指向数组元素的指针,用于接收新创建线程的ID。

在所有线程创建完毕后,再次使用一个循环来等待所有线程结束。WaitForSingleObject函数用于阻塞当前线程,直到指定的线程结束。由于我们使用INFINITE作为超时值,这意味着WaitForSingleObject将一直等待,直到指定的线程确实结束。

最后,使用另一个循环来关闭所有线程的句柄,这是必要的资源清理步骤,以避免资源泄漏。

image-20240715132714198

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;

    // 初始化Winsock
    iResult = 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函数接收来自客户端的数据,将其打印出来,并将同样的数据回传给客户端。

image-20240715132548776

由于CreateThread函数创建的线程默认是守护线程(非前台线程),因此主线程结束时,子线程也将被终止。在上面的代码中,CloseHandle函数被用来关闭线程句柄,但这并不意味着线程立即结束,它只是释放了主线程对线程句柄的引用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2057893.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2-71 基于matlab的小波分析在心电信号去噪中的应用

基于matlab的小波分析在心电信号去噪中的应用&#xff0c;主要针对心电信号中的肌电干扰/基线漂移/工频干扰进行的算法研究&#xff0c;输出了三类去噪结果。程序已调通&#xff0c;可直接运行。 2-71 基线漂移去噪 工频干扰去噪 - 小红书 (xiaohongshu.com)

android apk 加固后的地图加载异常及重新签名

1.首先根据需求将打包生成后的APK进行加固&#xff0c;可以使用360、阿里、腾讯加固等。 2.加固后的APK无法直接安装&#xff0c;需要重新进行签名。 3.首先找到sdk的位置&#xff0c;进入build-tools目录。 4.根据gradle文件选择版本目录。 5.将加固后的APK放至该目录下。在…

QT-小游戏翻金币

QT-小游戏翻金币 一、演示效果二、使用步骤三、下载链接 一、演示效果 二、使用步骤 #include "chooselevelscene.h" #include <QMenuBar> #include <QPainter> #include "mypushbutton.h" #include <QDebug> #include <QTimer> …

Ajax-3

一.图片上传 1.获取图片文件对象 2.使用FormData携带图片文件 const fd new FormData() fd.append(参数名, 值) 3.提交表单数据到服务器&#xff0c;使用图片url网址 二.AJAX原理—XMLHttpRequest 定义&#xff1a;XMLHttpReques&#xff08;XHR&#xff09;对象用于与服务器…

P38-数据存储1

百度2015年系统工程师笔试题 编程题 编程题 编程题 编程题

20240820飞凌的OK3588-C的核心板在Linux R4下使用poweroff关机

20240820飞凌的OK3588-C的核心板在Linux R4下使用poweroff关机 2024/8/20 14:03 经过测试&#xff0c;poweroff有效&#xff0c;关机之后&#xff0c;12V/0.024A12*0.0240.288W shutdown无效。 reboot -p无效。 rootok3588:/# rootok3588:/# shutdown -h now sh: shutdown: c…

Maven-06.依赖管理-依赖传递

一.依赖传递 什么是依赖传递&#xff1a;projectA依赖于JAR包和projectB&#xff0c;而JAR包又依赖于黄色的JAR包。而projectB依赖于projectC和其他JAR包。因此projectA依赖于projectB,projectC和图中的所有JAR包。这就是依赖的传递性。其中蓝绿色部分成为直接依赖。在当前项目…

ant design pro 的环境变量的使用

如上图所示&#xff0c;定义好环境变量后&#xff0c;整个应用的名字就发生的变化。 规则 环境变量的名称要以 UMI_APP 开头 如何使用这个环境变量 直接用 process.env.UMI_APP_APP_NAME 来调它。 ant design pro 如何去保存颜色ant design pro v6 如何做好角色管理ant desi…

【2025校招】4399 NLP算法工程师笔试题

目录 1. 第一题2. 第二题3. 第三题 ⏰ 时间&#xff1a;2024/08/19 &#x1f504; 输入输出&#xff1a;ACM格式 ⏳ 时长&#xff1a;2h 本试卷分为单选&#xff0c;自我评价题&#xff0c;编程题 单选和自我评价这里不再介绍&#xff0c;4399的编程题一如既往地抽象&#xff…

JavaScript(25)——BOM、延迟函数、JS执行机制

BOM BOM是浏览器对象模型 window对象是一个全局对象&#xff0c;也就是JavaScript中的顶级对象所有通过var定义的全局作用域中的变量&#xff0c;函数都会变成window对象的属性和方法window对象下的属性和方法调用的时候可以省略window 延时函数 let a setTimeout(回调函数…

Python(Falsk) + React Golang(Gin) + Vue 全栈开发的最佳实践

前面分别讲了 Python(Falsk) 、 React 、 Golang(Gin) 、 Vue(Element)&#xff0c;现在整体的给大家汇报一下&#xff0c;这个是简单搭建的demo&#xff0c;后面的添砖加瓦需要自己动手咯&#xff0c;有不明白的可以参考一下小编前面的文章&#xff0c;也许会给大家有答疑解惑…

QTday04

1.思维导图 2. . #include "widget.h" #include "ui_widget.h" #include "widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget), speecher(new QTextToSpeech(this)) //给语音播报者实例化空间{ui->setupUi(th…

ansible搭建+ansible常用模块

ansible搭建 管理机安装ansible,被管理节点必须打开ssh服务 1.管理机安装ansible yum -y install ansible 2.查看版本 ansible --version ansible 2.9.27 3.查找配置文件 find /etc/ -name "*ansible*" /etc/ansible /etc/ansible/ansible.cfg 4.三台被管理机…

轻松捕捉屏幕精彩,2024年录屏神器大盘点

随着技术的不断进步和用户需求的日益多样化&#xff0c;市场上的录屏软件更是层出不穷&#xff0c;各有千秋。今天&#xff0c;就让我们一同盘点那些和win10录屏快捷键一样可以快捷控制的专业录屏软件&#xff0c;探索它们如何助力我们更加高效地捕捉屏幕上的每一个精彩瞬间。 …

数据结构-链表-第二天

结合leetcode学习c 链表比数组更易增加和删除数据&#xff0c;但访问速度更慢 定义 链表&#xff08;linked list&#xff09;是一种线性数据结构&#xff0c;其中的每个元素都是一个节点对象&#xff0c;各个节点通过“引用”相连接。 引用记录了下一个节点的内存地址&#…

「字符串」前缀函数|KMP匹配:规范化next数组 / LeetCode 28(C++)

概述 为什么大家总觉得KMP难&#xff1f;难的根本就不是这个算法本身。 在互联网上你可以见到八十种KMP算法的next数组定义和模式串回滚策略&#xff0c;把一切都懂得特别混乱。很多时候初学者的难点根本不在于这个算法本身&#xff0c;而是它令人痛苦的百花齐放的定义。 有…

[C++] map、set的 红黑树 封装(一)

标题&#xff1a;[C] map、set的 红黑树 封装 水墨不写bug &#xff08;图片来源于网络&#xff09; 目录 一、红黑树与AVL树的比较&#xff08;为什么容器选择红黑树&#xff09; 二、map、set的封装 1.模板参数 2.红黑树迭代器设计 正文开始&#xff1a; 一、红黑树与AV…

RK3588J正式发布Ubuntu桌面系统,丝滑又便捷!

本文主要介绍瑞芯微RK3588J的Ubuntu系统桌面演示&#xff0c;开发环境如下&#xff1a; U-Boot&#xff1a;U-Boot-2017.09 Kernel&#xff1a;Linux-5.10.160 Ubuntu&#xff1a;Ubuntu20.04.6 LinuxSDK&#xff1a; rk3588-linux5.10-sdk-[版本号] &#xff08;基于rk3…

【GH】【EXCEL】P7: Control

XL Label XL Dropdown XL CHECK BOX XL Button XL Scroller XL Spinner XL ListBox

RocketMQ源码分析 - 环境搭建

RocketMQ源码分析 - 环境搭建 环境搭建源码拉取导入IDEA调试1) 启动NameServer2) 启动Broker3) 发送消息4) 消费消息 环境搭建 依赖工具 JDK&#xff1a;1.8MavenIntellij IDEA 源码拉取 从官方仓库 https://github.com/apache/rocketmq clone或者download源码。 源码目录…