【C语言】Windows下的C语言线程编程详解

文章目录

  • 1. 头文件
    • 1.1 windows.h
    • 1.2 process.h
  • 2. 创建线程
  • 3. 线程同步
    • 3.1 线程同步方式
    • 3.1 互斥量(Mutex)
    • 3.2 事件(Event)
  • 4. 线程的结束与资源管理
  • 5.线程池(简要)

在Windows平台下,C语言提供了一套丰富的线程(Thread)编程接口,使得开发者可以轻松地实现多线程并发操作。本文将详细介绍如何在Windows环境下使用C语言创建、管理和同步线程,以及一些常见的线程操作技巧。

这里指的是使用MSVC编译,Windows下也可以使用gcc,这时可以使用pthread.h。这个放在下一篇文章中说明。

1. 头文件

头文件:

#include <windows.h>
#include <stdio.h>
#include <process.h>
  • windows.h:包含了Windows API中线程相关的函数和数据结构。
  • stdio.h:用于标准输入输出。
  • process.h:包含了线程相关的一些宏和函数声明。如果只创建一些简单的线程,可以不用这个头文件。

1.1 windows.h

一些 <windows.h> 头文件中定义的常见数据类型、结构、函数和宏的详细说明:

数据类型:

数据类型描述
BOOL逻辑值类型,可以是 TRUEFALSE
BYTE8 位无符号整数
CHAR8 位字符类型
DWORD32 位无符号整数
HANDLE通用句柄类型,可以表示线程、进程、文件等
HINSTANCE模块实例句柄,表示一个加载到内存中的 DLL 或 EXE 文件的实例
HWND窗口句柄
LPVOID指向任意类型的指针
LPCSTR指向常量字符串的指针
LPWSTR指向宽字符字符串的指针
LPTHREAD_START_ROUTINE线程函数指针,指向一个线程函数
LPSECURITY_ATTRIBUTES安全属性指针
DWORD_PTR用于指针大小的无符号整数,通常与指针一起使用
LONG_PTR用于指针大小的有符号整数,通常与指针一起使用
SIZE_T用于表示大小或计数的无符号整数

结构:

结构体描述
CRITICAL_SECTION临界区对象,用于线程同步
FILETIME文件时间结构,表示一个文件的创建、最后访问和最后修改时间
SYSTEMTIME系统时间结构,表示一个日期和时间
PROCESS_INFORMATION进程信息结构,包含有关新进程及其主线程的信息
STARTUPINFO进程的起始信息结构,用于指定新进程的主窗口的外观、标准输入输出和错误流
SECURITY_ATTRIBUTES安全属性结构,用于指定对象的安全描述符

函数

函数描述
CreateThread创建一个新的线程
CreateMutex创建一个互斥量
CreateEvent创建一个事件
CreateSemaphore创建一个信号量
WaitForSingleObject等待一个对象的信号,如线程、事件、互斥量等
WaitForMultipleObjects等待多个对象的信号
ReleaseMutex释放互斥量
SetEvent设置事件,使得等待该事件的线程可以继续执行
ResetEvent重置事件的信号状态
EnterCriticalSection进入临界区,获取临界区的控制权
LeaveCriticalSection离开临界区,释放对临界区的控制权
CloseHandle关闭一个内核对象的句柄
GetCurrentThreadId获取当前线程的线程ID
GetCurrentProcessId获取当前进程的进程ID
GetLastError获取最后一个发生错误的错误代码
Sleep让当前线程休眠指定的时间
TerminateThread终止一个线程
ExitThread退出当前线程
SetThreadPriority设置线程的优先级
GetThreadPriority获取线程的优先级
CreateProcess创建一个新的进程
GetSystemTime获取当前的系统时间
GetLocalTime获取当前的本地时间
GetProcessTimes获取进程的创建时间、用户模式和内核模式执行时间等信息
GetThreadTimes获取线程的创建时间、用户模式和内核模式执行时间等信息
GetModuleFileName获取模块的文件名
GetModuleHandle获取模块的句柄
GetProcAddress获取动态链接库中的函数地址
LoadLibrary加载一个动态链接库
FreeLibrary释放一个动态链接库
MessageBox显示一个消息框
MoveFile移动文件或重命名文件
DeleteFile删除文件
FindFirstFile查找一个文件
FindNextFile继续查找下一个文件
FindClose关闭一个查找句柄

宏:

描述
MAX_PATH文件路径最大长度
INFINITE用于指示无限等待的超时值
TRUE, FALSE逻辑值 TRUEFALSE
WAIT_OBJECT_0等待对象的信号状态值,表示对象已经收到信号
WAIT_TIMEOUT等待超时的信号状态值
WAIT_FAILED等待失败的信号状态值
IN_CLASSA, IN_CLASSB, IN_CLASSC, IN_CLASSD, IN_CLASSE用于定义 IP 地址类别的常量

WINAPI是一个宏,用于标记 Windows API 函数的调用约定。在Windows平台上,使用WINAPI宏声明的函数使用的是stdcall调用约定,这是一种在函数调用时处理函数参数和堆栈的标准方式。

  • 参数传递顺序:参数是从右向左依次入栈的,即右边的参数先入栈,左边的参数后入栈。被调用函数会按照相反的顺序弹出这些参数。
  • 堆栈清理:被调用的函数会负责清理调用堆栈。这意味着在调用函数后,调用者不需要负责清理堆栈。

为什么使用__stdcall?

  • 约定性:使用标准调用约定能够确保在不同的函数之间有一个一致的接口和调用规则。
  • 兼容性:许多 Windows API 函数都使用__stdcall调用约定。如果您编写的函数也使用相同的约定,可以更方便地与这些函数进行交互。
  • 性能:由于清理堆栈是由被调用函数负责的,因此在某些情况下,__stdcall可以比其他调用约定更高效。

函数前面加上WINAPI通常是为了方便移植。


1.2 process.h

下面是 <process.h> 头文件中定义的一些常见数据类型、结构、函数和宏的详细说明:

数据类型:

数据类型描述
_pid_t进程 ID 的数据类型,通常是整数类型
_pipe_t管道的句柄类型
_fmode_t文件模式的数据类型,用于设置文件打开模式
_wfinddata_t用于 _wfindfirst()_wfindnext() 函数的数据结构
_wfinddatai64_t_wfindfirsti64()_wfindnexti64() 函数的数据结构的 64 位版本
_wchdir_t用于 _wchdir() 函数的数据类型
_wexecle_t用于 _wexecle() 函数的数据类型
_wexecve_t用于 _wexecve() 函数的数据类型
_wexecvpe_t用于 _wexecvpe() 函数的数据类型
_wsearchenv_t用于 _wsearchenv() 函数的数据类型
_wsplitpath_t用于 _wsplitpath() 函数的数据类型

结构:

结构体描述
_finddata_t用于 _findfirst()_findnext() 函数的数据结构
_finddatai64_t_findfirsti64()_findnexti64() 函数的数据结构的 64 位版本
_startupinfo进程的起始信息结构,用于指定新进程的主窗口的外观、标准输入输出和错误流
_PROCESS_INFORMATION进程信息结构,包含有关新进程及其主线程的信息

函数:

函数描述
_execl()用指定的参数列表执行一个新的程序
_execle()用指定的参数列表执行一个新的程序,并指定环境变量
_execlp()用指定的参数列表执行一个新的程序(带路径)
_execlpe()用指定的参数列表执行一个新的程序(带路径和环境变量)
_execv()用指定的参数列表执行一个新的程序
_execve()用指定的参数列表执行一个新的程序,并指定环境变量
_execvp()用指定的参数列表执行一个新的程序(带路径)
_execvpe()用指定的参数列表执行一个新的程序(带路径和环境变量)
_getpid()获取当前进程的进程 ID
_getwch()从控制台获取一个宽字符
_getws()从控制台读取一个宽字符字符串
_pipe()创建一个管道,用于父子进程之间的通信
_spawnl()用指定的参数列表创建一个新的进程
_spawnle()用指定的参数列表创建一个新的进程,并指定环境变量
_spawnlp()用指定的参数列表创建一个新的进程(带路径)
_spawnlpe()用指定的参数列表创建一个新的进程(带路径和环境变量)
_spawnv()用指定的参数列表创建一个新的进程
_spawnve()用指定的参数列表创建一个新的进程,并指定环境变量
_spawnvp()用指定的参数列表创建一个新的进程(带路径)
_spawnvpe()用指定的参数列表创建一个新的进程(带路径和环境变量)
_wexecl()用指定的参数列表执行一个新的程序(宽字符)

2. 创建线程

在Windows下,可以使用CreateThread函数来创建线程。其原型如下:

HANDLE CreateThread(LPSECURITY_ATTRIBUTES   lpThreadAttributes,SIZE_T                  dwStackSize,LPTHREAD_START_ROUTINE  lpStartAddress,LPVOID                  lpParameter,DWORD                   dwCreationFlags,LPDWORD                 lpThreadId
);

函数参数说明:

  • lpThreadAttributes:指向 SECURITY_ATTRIBUTES 结构的指针,用于指定新线程的安全属性。通常为 NULL,表示使用默认的安全属性。

  • dwStackSize:指定新线程的堆栈大小,以字节为单位。如果为 0,则新线程使用与创建线程的进程相同的堆栈大小。

  • lpStartAddress:指向线程函数的指针,表示新线程从哪个函数开始执行。线程函数的原型应为 DWORD WINAPI ThreadFunc(LPVOID lpParam),其中 lpParam 参数可以接收 lpParameter 参数的值。

  • lpParameter:传递给线程函数的参数,可以是任意类型的指针。该参数将被传递给 lpStartAddress 指向的线程函数。

  • dwCreationFlags:指定线程的创建标志。

  • lpThreadId:用于接收新线程的线程 ID。如果为 NULL,则不返回线程 ID。

dwCreationFlags:

这些标志可以单独使用,也可以使用按位 OR 操作符 | 组合使用,以实现更复杂的创建标志设置。若为0,则表示不设置任何标志,即使用默认的创建标志。在这种情况下,新线程会立即开始执行,不会挂起,也不会设置其他特殊的创建选项。

标志描述
CREATE_SUSPENDED创建后线程处于挂起状态,需要调用 ResumeThread 才能开始执行
STACK_SIZE_PARAM_IS_A_RESERVATIONdwStackSize 参数指定的是堆栈的保留大小,而不是真实的堆栈大小
CREATE_NEW_CONSOLE为新进程创建一个新的控制台窗口
CREATE_UNICODE_ENVIRONMENT使用 Unicode 环境变量
DETACHED_PROCESS新进程将不与其父进程有关联,父进程退出时不会影响子进程
CREATE_NO_WINDOW新进程不会创建窗口
CREATE_DEFAULT_ERROR_MODE新进程将使用默认的错误模式
CREATE_NEW_PROCESS_GROUP将新进程创建为一个新的进程组,使其成为新会话的首要进程

返回值说明:

  • 如果函数调用成功,将返回新线程的句柄,可以使用这个句柄操作新线程。
  • 如果函数调用失败,返回值为 NULL。可以使用 GetLastError() 获取具体的错误信息。

下面是一个简单的例子,演示如何创建一个线程并执行线程函数:

#include<windows.h>
#include<stdio.h>// 定义一个线程函数,打印参数值
DWORD WINAPI ThreadFunc(LPVOID lpParam) {int* p = (int*)lpParam;printf("Thread is running with parameter: %d\n",*p);return 0;
}int main() {// 创建一个句柄HANDLE hTheard;// 线程id变量DWORD dwThreadId;int param = 123;hTheard = CreateThread(NULL, // 安全性0,    // 线程堆栈大小ThreadFunc,  // 线程函数指针&param,       // 线程函数参数0,            // 线程创建标志,默认,立即执行&dwThreadId); // 接收线程id// 检查是否创建成功if (hTheard == NULL) {DWORD err = GetLastError();printf("Failed to create Thread with code %d.\n",err);return 1;}// 等待线程结束WaitForSingleObject(hTheard,INFINITE);// 关闭线程CloseHandle(hTheard);return 0;
}

3. 线程同步

线程同步是指多个线程之间协调工作,以确保它们正确地访问共享资源并按照预期顺序执行。在多线程编程中,当多个线程同时访问共享资源时,可能会出现以下问题:

  1. 竞态条件(Race Condition):多个线程竞争同时对共享资源进行读写操作,导致结果不确定或不正确。
  2. 死锁(Deadlock):多个线程相互等待对方释放资源而无法继续执行。
  3. 活锁(Livelock):线程之间互相响应对方的动作而无法继续执行,类似于死锁但线程还在运行。
  4. 饿死(Starvation):某个线程由于优先级低或者其他原因,一直无法获取到所需的资源,无法继续执行。

为了解决这些问题,需要使用线程同步机制来确保线程之间的正确协作。线程同步的目的是确保:

  • 各个线程按照规定的顺序访问共享资源,避免竞态条件。
  • 线程之间相互协作、通信,确保工作按照预期顺利进行。
  • 避免死锁和活锁等线程间互相等待的情况发生。

3.1 线程同步方式

常用线程同步方式:

同步方式描述常用场景
互斥量(Mutex)用于保护临界区资源,防止多个线程同时访问对共享资源的独占访问,如文件操作、数据库操作等
临界区(Critical Section)用于保护临界资源的轻量级同步对象,只适用于同一进程内的线程同步对临界资源的保护,需要高效的同步机制
信号量(Semaphore)控制对资源的访问,允许多个线程同时访问同一资源有限资源的控制,如线程池、连接池等
事件(Event)用于线程之间的通信和同步,允许一个或多个线程等待事件的状态改变线程间的信号通知和同步等
条件变量(Condition Variable)用于线程等待某个条件成立时再继续执行生产者-消费者模型中,消费者等待生产者产生数据
读写锁(Read-Write Lock)允许多个线程同时读取共享数据,但只允许一个线程写入数据对于读操作频繁、写操作较少的情况
自旋锁(Spin Lock)一种忙等待的同步机制,用于临界区很小,不希望线程切换的情况对于临界区非常短小,不希望线程切换带来的开销
事件计数器(Event Counters)表示一个或多个事件的发生次数,用于线程之间的通信控制多个事件的发生次数,线程等待特定数量的事件发生
  1. 互斥量(Mutex)

    • 互斥量是一种同步对象,用于保护临界区资源,防止多个线程同时访问。
    • 通过互斥量,只有拥有互斥量的线程可以进入临界区。
  2. 临界区(Critical Section)

    • 临界区是一种轻量级的同步对象,类似于互斥量,用于保护临界资源。
    • 临界区通常比互斥量更快速,但只能用于同一进程内的线程同步。
  3. 信号量(Semaphore)

    • 信号量是一种计数器,控制对资源的访问,允许多个线程同时访问同一资源。
    • 信号量的值表示可用资源的数量,当资源被占用时,信号量减少;当资源释放时,信号量增加。
  4. 事件(Event)

    • 事件用于线程之间的通信和同步,允许一个或多个线程等待某个事件的状态改变。
    • 事件有两种状态:有信号(signaled)和无信号(nonsignaled)。
  5. 条件变量(Condition Variable)

    • 条件变量用于线程等待某个条件成立时再继续执行。
    • 一般和互斥量结合使用,当条件不满足时,线程进入等待状态并释放互斥量;当条件满足时,线程被唤醒继续执行。
  6. 读写锁(Read-Write Lock)

    • 读写锁允许多个线程同时读取共享数据,但只允许一个线程写入数据。
    • 读锁可以多个线程同时持有,写锁只能被一个线程持有。
  7. 自旋锁(Spin Lock)

    • 自旋锁是一种忙等待的同步机制,当某个线程尝试获得锁时,如果锁被其他线程持有,则该线程会一直循环等待直到锁被释放。
    • 自旋锁适用于临界区很小,不希望线程切换的情况。
  8. 事件计数器(Event Counters)

    • 事件计数器用于线程之间的通信,表示一个或多个事件的发生次数。
    • 可以等待事件计数器的值达到某个特定值,然后继续执行。

本文暂且只介绍互斥量和事件。

3.1 互斥量(Mutex)

在 Windows 中,可以使用 CreateMutex() 函数来创建互斥量(Mutex)。互斥量是一种同步对象,用于控制多个线程对共享资源的访问。只有一个线程可以拥有一个互斥量,当一个线程拥有互斥量时,其他线程需要等待这个互斥量被释放才能访问被保护的资源。

原型:

HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,BOOL                  bInitialOwner,LPCTSTR               lpName
);
  • lpMutexAttributes: 一个指向 SECURITY_ATTRIBUTES 结构的指针,决定了新创建的互斥量的安全描述符。通常情况下可以设为 NULL
  • bInitialOwner: 如果为 TRUE,表示调用线程拥有互斥量;如果为 FALSE,表示互斥量是未拥有的。通常情况下可以设为 FALSE,除非你确实需要在创建时让某个线程拥有这个互斥量。
  • lpName: 互斥量的名称,可以为 NULL。如果互斥量是局部的,可以设为 NULL;如果要在多个进程中共享互斥量,可以给互斥量取一个名字。

释放信号量使用:ReleaseMutex()。获取:WaitForSingleObject()
在这里插入图片描述

互斥量用于保护临界区,确保同时只有一个线程可以访问共享资源。示例代码如下:

#include<Windows.h>
#include<stdio.h>HANDLE hMutex;
int sharedata = 0;DWORD WINAPI threadFunc(LPVOID lpParam) {for (int i = 0; i < 5; i++) {// 获取互斥量WaitForSingleObject(hMutex,INFINITE);sharedata++;// 释放互斥量ReleaseMutex(hMutex);}return 0;
}int main() {hMutex = CreateMutex(NULL,FALSE,NULL);if (hMutex == NULL) {printf("Create Mutex error.\n");return 1;}HANDLE hThraed1, hThread2;hThraed1 = CreateThread(NULL, 0, threadFunc, NULL, 0, NULL);hThread2 = CreateThread(NULL, 0, threadFunc, NULL, 0, NULL);if (hThraed1 == NULL || hThread2 == NULL) {printf("Create thread failed.\n");return 1;}WaitForSingleObject(hThraed1,1);WaitForSingleObject(hThread2,INFINITE);CloseHandle(hThraed1);CloseHandle(hThread2);CloseHandle(hMutex);printf("The final value of sharedata is :%d\n",sharedata);return 0;
}

注:
WaitForSingleObject() 是 Windows API 中用于等待一个对象的函数。在多线程编程中,它通常用于等待线程结束、等待信号量、事件等。这个函数的原型如下:

DWORD WaitForSingleObject(HANDLE hHandle,    // 要等待的对象的句柄DWORD dwMilliseconds  // 等待的时间(毫秒),单位为毫秒,INFINITE 表示无限等待
);

3.2 事件(Event)

CreateEvent 函数是 Windows API 中用于创建事件对象的函数。事件对象可以用于线程间的同步和通信,允许一个线程等待其他线程的某个事件发生后再继续执行。

HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,BOOL                  bManualReset,BOOL                  bInitialState,LPCTSTR               lpName
);

参数:

  • lpEventAttributes:指向 SECURITY_ATTRIBUTES 结构的指针,用于指定事件对象的安全属性。一般情况下设为 NULL 即可。
  • bManualReset:指定事件是手动重置还是自动重置。如果为 TRUE,表示事件为手动重置,需要显式调用 ResetEvent 函数来重置事件;如果为 FALSE,表示事件为自动重置,当一个等待的线程被释放后,事件自动重置为未触发状态。一般情况下,我们常用自动重置。
  • bInitialState:指定事件的初始状态。如果为 TRUE,表示初始状态为有信号(事件已触发);如果为 FALSE,表示初始状态为无信号(事件未触发)。
  • lpName:指定事件对象的名字。如果为 NULL,则创建一个匿名事件。

返回值:

  • 如果函数成功,返回一个事件对象的句柄 HANDLE
  • 如果函数失败,返回 NULL。可以调用 GetLastError() 函数获取错误信息。

示例代码如下:

#include<windows.h>
#include<stdio.h>HANDLE hEvent;
int sharedData = 0;DWORD WINAPI Thread1Func(LPVOID lpParam) {WaitForSingleObject(hEvent, INFINITE);sharedData = 1;printf("Thread 1 sets sharedData to 1\n");return 0;
}DWORD WINAPI Thread2Func(LPVOID lpParam) {Sleep(100);  // 等待100毫秒SetEvent(hEvent);printf("Thread 2 signals the event\n");return 0;
}int main() {hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);if (hEvent == NULL)return 1;HANDLE hThread1, hThread2;hThread1 = CreateThread(NULL, 0, Thread1Func, NULL, 0, NULL);hThread2 = CreateThread(NULL, 0, Thread2Func, NULL, 0, NULL);if (hThread1==NULL||hThread2==NULL)return 1;WaitForSingleObject(hThread1, INFINITE);WaitForSingleObject(hThread2, INFINITE);CloseHandle(hThread1);CloseHandle(hThread2);CloseHandle(hEvent);printf("Final sharedData value: %d\n", sharedData);return 0;
}

4. 线程的结束与资源管理

结束方式:

  1. return:最简单的方法是让线程函数执行完毕并返回。
  2. ExitThread():在任何时候,线程都可以调用ExitThread()来终止自己。
  3. TerminateThread():可以用来强制终止一个线程,但应该慎用,因为它可能导致资源泄漏或者使程序处于不一致的状态。
DWORD WINAPI ThreadFunc(LPVOID lpParam) {// 线程执行的代码return 0;// ExitThread(0);
}int main() {HANDLE hThread;DWORD dwThreadId;hThread = CreateThread(NULL, 0, ThreadFunc, NULL, 0, &dwThreadId);// 主线程等待一段时间后强制终止线程// Sleep(2000);// TerminateThread(myThread, 0);// 等待线程结束WaitForSingleObject(hThread, INFINITE);CloseHandle(hThread);return 0;
}

还可以使用PostThreadMessage()函数向指定线程发送一个消息,让线程在处理这个消息时结束自己。但要确保线程有消息循环,通常在GUI线程中使用较多。

#include <stdio.h>
#include <windows.h>#define WM_QUIT_THREAD (WM_USER + 1)DWORD WINAPI myThreadFunction(LPVOID lpParam) {printf("Thread is running...\n");MSG msg;while (GetMessage(&msg, NULL, 0, 0)) {if (msg.message == WM_QUIT_THREAD) {break;}TranslateMessage(&msg);DispatchMessage(&msg);}printf("Thread is ending...\n");return 0;
}int main() {HANDLE myThread = CreateThread(NULL, 0, myThreadFunction, NULL, 0, NULL);// 主线程等待一段时间后向线程发送消息结束它Sleep(5000);PostThreadMessage(GetThreadId(myThread), WM_QUIT_THREAD, 0, 0);// 等待线程结束WaitForSingleObject(myThread, INFINITE);printf("Thread has ended.\n");CloseHandle(myThread);return 0;
}

5.线程池(简要)

线程池(Thread Pool)是一种线程管理的机制,它包含了多个预先创建好的线程,这些线程可以被重复使用来执行多个任务,而不需要每次都创建新的线程。线程池在多线程编程中被广泛应用,它的主要目的是提高线程的利用率和减少线程创建和销毁的开销。

原理和优势:

  1. 重用线程:线程池中的线程被预先创建并保持在池中,可以被反复使用来处理不同的任务,而不需要每次都创建新的线程。这样可以避免线程的频繁创建和销毁,提高了系统的性能和效率。
  2. 减少资源开销:线程的创建和销毁会消耗系统的资源,包括内存和CPU时间。线程池可以减少这些开销,因为线程一旦创建就可以被重复利用,不需要频繁地分配和回收资源。
  3. 控制并发数量:通过设置线程池的大小,可以限制系统中并发执行的线程数量,避免因为过多的线程而导致资源竞争和性能下降的问题。
  4. 提高响应速度:线程池中的线程可以立即执行任务,不需要等待新线程的创建,从而减少了任务开始执行的延迟,提高了系统的响应速度。

工作流程:

  • 初始化线程池:预先创建一定数量的线程,并将它们放入线程池中。
  • 任务提交:当有任务需要执行时,将任务提交给线程池。
  • 任务执行:线程池中的空闲线程会从任务队列中取出任务并执行。
  • 任务完成:任务执行完毕后,线程会返回线程池,并等待下一个任务。
  • 线程池销毁:当线程池不再需要时,可以销毁线程池中的线程。
    在这里插入图片描述

使用场景:

  • 服务器编程:在服务器程序中,需要处理大量的客户端请求。使用线程池可以有效地管理这些请求,提高服务器的并发能力。
  • 多任务处理:在计算密集型或IO密集型的任务中,可以使用线程池来管理和执行这些任务,提高系统的效率。
  • 图像处理:对大量图片进行处理时,可以使用线程池来并行处理这些图片,加快处理速度。

示例:

#include <Windows.h>
#include <stdio.h>#define MAX_THREADS 4
#define TASKS 8// 任务结构
typedef struct {int task_id;
} Task;// 线程池结构
typedef struct {HANDLE threads[MAX_THREADS]; // 线程句柄数组CRITICAL_SECTION lock;       // 临界区对象,用于线程安全操作HANDLE events[MAX_THREADS];  // 事件对象数组,用于线程间的同步Task* task_queue[TASKS];     // 任务队列int task_count;              // 任务队列中的任务数量int active_threads;          // 活动线程的数量
} ThreadPool;// 工作线程函数
DWORD WINAPI Worker(LPVOID arg) {ThreadPool* pool = (ThreadPool*)arg;while (1) {WaitForSingleObject(pool->events[pool->active_threads], INFINITE); // 等待事件触发EnterCriticalSection(&pool->lock); // 进入临界区if (pool->task_count == 0) {       // 检查任务队列是否为空LeaveCriticalSection(&pool->lock);break;}Task* task = pool->task_queue[--pool->task_count]; // 从任务队列取出一个任务LeaveCriticalSection(&pool->lock);                  // 离开临界区printf("Thread %ld processing task %d\n", GetCurrentThreadId(), task->task_id);// 模拟任务执行时间Sleep(1000);free(task); // 释放任务内存SetEvent(pool->events[pool->active_threads]); // 触发下一个线程的事件}return 0;
}// 初始化线程池
void ThreadPoolInit(ThreadPool* pool) {pool->task_count = 0;pool->active_threads = MAX_THREADS;InitializeCriticalSection(&pool->lock); // 初始化临界区对象for (int i = 0; i < MAX_THREADS; ++i) {pool->events[i] = CreateEvent(NULL, FALSE, TRUE, NULL); // 创建自动重置的有信号事件pool->threads[i] = CreateThread(NULL, 0, Worker, (LPVOID)pool, 0, NULL); // 创建工作线程}
}// 提交任务到线程池
void SubmitTask(ThreadPool* pool, Task* task) {EnterCriticalSection(&pool->lock); // 进入临界区pool->task_queue[pool->task_count++] = task;    // 将任务加入到任务队列SetEvent(pool->events[--pool->active_threads]); // 触发一个空闲线程的事件LeaveCriticalSection(&pool->lock); // 离开临界区
}// 销毁线程池
void DestroyThreadPool(ThreadPool* pool) {for (int i = 0; i < MAX_THREADS; ++i) {WaitForSingleObject(pool->threads[i], INFINITE); // 等待工作线程结束CloseHandle(pool->threads[i]); // 关闭线程句柄CloseHandle(pool->events[i]);  // 关闭事件对象句柄}DeleteCriticalSection(&pool->lock); // 删除临界区对象
}// 主函数
int main() {ThreadPool pool;ThreadPoolInit(&pool); // 初始化线程池// 创建一些任务并提交到线程池for (int i = 0; i < TASKS; ++i) {Task* task = (Task*)malloc(sizeof(Task));task->task_id = i + 1;SubmitTask(&pool, task);}// 等待一段时间,让任务执行Sleep(5000);// 销毁线程池DestroyThreadPool(&pool);return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何“使用Docker快速安装Jenkins,在CentOS7”?

1、运行 docker run -d --namejenkins -p 8080:8080 jenkins/jenkins 2、查看日志 &#xff0c;使用 "docker logs -f jenkins",可以持续刷新日志 docker logs jenkins 3、通过命令查看密码 docker exec -it jenkins cat /var/jenkins_home/secrets/initialAdminP…

VUE内盘期货配资软件源码国际外盘二合一

开发一个Vue内盘期货配资软件源码&#xff0c;同时兼容国际外盘二合一的功能&#xff0c;是一个复杂且专业的任务&#xff0c;涉及前端Vue.js框架的使用、后端服务器处理、数据库管理、实时交易接口对接等多个方面。下面是一些关于开发此类软件的基本指导和考虑因素&#xff1a…

FFmpeg工作流程及视频文件分析

FFmpeg工作流程: 解封装(Demuxing)--->解码(Decoding)--->编码(Encoding)--->封装(Muxing) FFmpeg转码工作流程: 读取输入流--->音视频解封装--->解码音视频帧--->编码音视频帧--->音视频封装--->输出目标流 可简单理解为如下流程: 读文件-->解…

程序员的三重境界:码农,高级码农、程序员!

见字如面&#xff0c;我是军哥&#xff01; 掐指一算&#xff0c;我在 IT 行业摸爬滚打 19 年了&#xff0c;见过的程序员至少大好几千&#xff0c;然后真正能称上程序员不到 10% &#xff0c;绝大部分都是高级码农而已。 今天和你聊聊程序员的三个境界的差异&#xff0c;文章不…

【BFS二叉树】113路径总和II

113路径总和 II 给你二叉树的根节点 root 和一个整数目标和 targetSum &#xff0c;找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 思路&#xff1a; 题目最终输出的是路径&#xff0c;因此用BFS遍历的时候&#xff0c;需要记录走到每个节点的路径&#xff1…

排序算法之快速排序算法介绍

目录 快速排序介绍 时间复杂度和稳定性 代码实现 C语言实现 c实现 java实现 快速排序介绍 快速排序(Quick Sort)使用分治法策略。 它的基本思想是&#xff1a;选择一个基准数&#xff0c;通过一趟排序将要排序的数据分割成独立的两部分&#xff1b;其中一部分的所有数据…

报错:Nginx 部署后刷新页面 404 问题

文章目录 问题分析解决 问题 在部署完项目后 刷新页面&#xff0c;页面进入了404 分析 加载单页应用后路由改变均由浏览器处理&#xff0c;而刷新时将会请求当前的链接&#xff0c;而Nginx无法找到对应的页面 关键代码try_files,剩下俩如果其他地方配置了则可以省略。 在这…

Python (用户登录、身份归属地查询添加异常处理、绘制多角星、电影信息提取)

任务一&#xff1a;用户登录 登录系统通常分为普通用户与管理员权限&#xff0c;在用户登录系统时&#xff0c;可以根据自身权限进行选择登录。本任务要求实现一个用户登录的程序&#xff0c;该程序分为管理员用户与普通用户&#xff0c;其中管理员账号密码在程序中设定&#…

云原生消息流系统 Apache RocketMQ 在腾讯云的大规模生产实践

导语 随着云计算技术的日益成熟&#xff0c;云原生应用已逐渐成为企业数字化转型的核心驱动力。在这一大背景下&#xff0c;高效、稳定、可扩展的消息流系统显得尤为重要。腾讯云高级开发工程师李伟先生&#xff0c;凭借其深厚的技术功底和丰富的实战经验&#xff0c;为我们带…

Linux:导出环境变量命令export

相关阅读 Linuxhttps://blog.csdn.net/weixin_45791458/category_12234591.html?spm1001.2014.3001.5482 Linux中的内建命令export命令用于创建一个环境变量&#xff0c;或将一个普通变量导出为环境变量&#xff0c;并且在这个过程中&#xff0c;可以给该环境变量赋值。 下面…

2024春秋蓝桥杯reverse——crackme01

尝试了下输入没有任何反应 查看——32位——IDA打开 我之前没怎么写过win32&#xff0c;所以我开始在string里面找flag,wrong,right什么的字符&#xff0c;都不行 然后我又在函数里面找main&#xff0c;也什么收获的没有,OK废话完了 在win32里面 关于弹窗的函数&#xff1a;…

C++Qt学习——添加资源文件

目录 1、创建好了文件之后&#xff0c;在左边空白处按下CtrlN&#xff0c;创建Qt 以及Qt Resource File 2、写入名称&#xff0c;点击下一步 3、可以发现已经创建好啦。 4、点击Add Prefix 5、写上前缀&#xff0c;最好加上斜杠 6、选择提前放好的图片或者icon 7、发…

【C语言】字符串函数上

&#x1f451;个人主页&#xff1a;啊Q闻 &#x1f387;收录专栏&#xff1a;《C语言》 &#x1f389;道阻且长&#xff0c;行则将至 前言 这篇博客是字符串函数上篇&#xff0c;主要是关于长度不受限制的字符串函数&#xff08;strlen,strcpy,strcat,strcm…

详解Postman使用

简介&#xff1a; 1.简介 PostMan&#xff0c;一款接口调试工具。 特点&#xff1a; 可以保留接口请求的历史记录 可以使用测试集Collections有效管理组织接口 可以在团队之间同步接口数据 1.简介 PostMan&#xff0c;一款接口调试工具。 特点&#xff1a; 可以保留接口请求…

解决vue2+elementUI的下拉框出现自动校验的问题

问题&#xff1a; 总结原因是因为新增的时候&#xff0c;传了空值进去 可以这样子解决 this.formData.value && this.$set(this.model, this.formData.key, this.formData.value)这种是只有值存在的时候才会给他赋值&#xff0c;但是这只解决单选下拉框&#xff0c;…

数据结构:7、队列

一、队列的概念与结构 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff0c;队列具有先进先出FIFO(First In First Out) 入队列&#xff1a;进行插入操作的一端称为队尾 出队列&#xff1a;进行删除操作的一端称为队头…

Android Studio下运行java main 方法

方法一 修改项目的.idea中的gradle.xml文件&#xff0c;在GradleProjectSettings标签下添加一行代码 <option name"delegatedBuild" value"false" />方法二 main方法上右键选择Run ‘xxx’ with Coverage

【LeetCode: 2864. 最大二进制奇数 + 模拟 + 位运算】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

Elastic Stack--05--聚合、映射mapping

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.聚合(aggregations)基本概念桶&#xff08;bucket&#xff09;度量&#xff08;metrics&#xff09; 案例 11. 接下来按price字段进行分组&#xff1a;2. 若想对所…

UE4案例记录

UE4案例记录&#xff08;制作3D角色显示在UI中&#xff09; 制作3D角色显示在UI中 转载自youtube视频 https://www.youtube.com/channel/UCC8f6SxKJElVvaRb7nF4Axg 新建项目 创建一个Actor 场景组件->摄像机组件->场景捕获组件2D&#xff0c;之后添加一个骨骼网格体…