【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.mzph.cn/news/742986.shtml

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

相关文章

活动会议线上直播,如何扩大曝光?媒体直播分流解析

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 线上直播扩大曝光与媒体直播分流解析 一、扩大曝光策略&#xff1a; 平台选择&#xff1a;选择用户基数大、活跃度高的直播平台进行直播。 预告宣传&#xff1a;提前发布直播预告&…

k8s-Istio服务网络 27

官网&#xff1a;https://istio.io/latest/zh/about/service-mesh/ Istio与k8s的区别 SpringCloud传统微服务结合k8s与Istio与k8s结合&#xff1a; Istio数据面&#xff1a;通过envoy以sidecar方式拦截svc的流量来进行治理。 Istio控制面&#xff1a;pilot list/watch APIserv…

HarmonyOS NEXT应用开发之异常处理案例

介绍 本示例介绍了通过应用事件打点hiAppEvent获取上一次应用异常信息的方法&#xff0c;主要分为应用崩溃、应用卡死以及系统查杀三种。 效果图预览 使用说明&#xff1a; 点击构建应用崩溃事件&#xff0c;3s之后应用退出&#xff0c;然后打开应用进入应用异常页面&#x…

nginx启动闪退

在nginx目录下cmd&#xff0c;nginx -t&#xff0c;找到原因是&#xff1a;“在端口80上运行NGINX时&#xff0c;因为端口80是HTTP默认端口&#xff0c;需要管理员权限才能访问” 所以修改端口号&#xff1a; 在nginx.conf文件中&#xff0c;修改listen&#xff1a;80为8080 …

【C++】类的默认成员函数(下)

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《C》 《Linux》 《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 文章目录 一、运算符重载1.1 引例1.2 概念及运用&#xff1a;1.3 牛刀小试:1.4 重载1.5 运…

Python网络基础爬虫-python基本语法

文章目录 逻辑语句if,else,elifforwhile异常处理 函数与类defpassclass 逻辑语句 熟悉C/C语言的人们可能很希望Python提供switch语句&#xff0c;但Python中并没有这个关键词&#xff0c;也没有这个语句结构。但是可以通过if-elif-elif-…这样的结构代替&#xff0c;或者使用字…

目标检测——YOLOv2算法解读

论文&#xff1a;YOLO9000: Better, Faster, Stronger 作者&#xff1a;Joseph Redmon, Ali Farhadi 链接&#xff1a;https://arxiv.org/pdf/1612.08242v1.pdf 代码&#xff1a;http://pjreddie.com/yolo9000/ YOLO系列其他文章&#xff1a; YOLOv1通俗易懂版解读SSD算法解读…

记一次生产慢sql索引优化及思考

记一次生产慢sql索引优化及思考 问题重现 夜黑风高的某一晚&#xff0c;突然收到一条运营后台数据库慢sql的报警&#xff0c;耗时竟然达到了60s。看了一下&#xff0c;还好不是很频繁&#xff0c;内心会更加从容排查问题&#xff0c;应该是特定条件下没有走到索引导致&#x…

[论文精读]Dynamic Coarse-to-Fine Learning for Oriented Tiny Object Detection

论文网址&#xff1a;[2304.08876] 用于定向微小目标检测的动态粗到细学习 (arxiv.org) 论文代码&#xff1a;https://github.com/ChaselTsui/mmrotate-dcfl 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&…

支付模块-基于消息队列发送支付通知消息

消息队列发送支付通知消息 需求分析 订单服务作为通用服务&#xff0c;在订单支付成功后需要将支付结果异步通知给其他对接的微服务&#xff0c;微服务收到支付结果根据订单的类型去更新自己的业务数据 技术方案 使用消息队列进行异步通知需要保证消息的可靠性即生产端将消息…

深入了解 大语言模型(LLM)微调方法

引言 众所周知&#xff0c;大语言模型(LLM)正在飞速发展&#xff0c;各行业都有了自己的大模型。其中&#xff0c;大模型微调技术在此过程中起到了非常关键的作用&#xff0c;它提升了模型的生成效率和适应性&#xff0c;使其能够在多样化的应用场景中发挥更大的价值。 那么&…

服务器命令

服务器命令 服务器命令top查看任务 服务器命令 top查看任务 、ps 命令 ps 命令是最基本同时也是非常强大的进程查看命令。使用该命令可以确定有哪些进程正在运行和它所运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等。总之大部分信息都是可以通过执行…

pytorch模型转onnx格式,编写符号函数实现torch算子接口和onnx算子的映射,新建简单算子--模型部署记录整理

对于深度学习模型来说&#xff0c;模型部署指让训练好的模型在特定环境中运行的过程。相比于软件部署&#xff0c;模型部署会面临更多的难题&#xff1a; 运行模型所需的环境难以配置。深度学习模型通常是由一些框架编写&#xff0c;比如 PyTorch、TensorFlow。由于框架规模、依…

掌握Go语言:深入encoding/gob包的高效数据序列化

掌握Go语言&#xff1a;深入encoding/gob包的高效数据序列化 引言理解Gob和它的使用场景Gob的概念和设计目标Gob的适用场景和优势 开始使用Gob基本的Gob编码和解码示例代码编码&#xff08;序列化&#xff09;解码&#xff08;反序列化&#xff09; Gob编码高级应用自定义类型的…

【Java语言】遍历List元素时删除集合中的元素

目录 前言 实现方式 1.普通实现 1.1 使用【for循环】 方式 1.2 使用【迭代器】方式 2.jdk1.8新增功能实现 2.1 使用【lambda表达式】方式 2.2 使用【stream流】方式 注意事项 1. 使用【for循环】 方式 2. 不能使用增强for遍历修改元素 总结 前言 分享几种从List中移…

基于 K8s 容器集群的容灾架构与方案

作者&#xff1a;庄宇 在设计系统架构时&#xff0c;我们必须假设任何组件和任何基础设施可能会在任何时间失效&#xff0c;例如&#xff1a;自然灾害&#xff0c;电力中断&#xff0c;网络中断&#xff0c;错误的系统变更等。为了应对挑战&#xff0c;我们必须设计合适的容灾…

在centos8中部署Tomcat和Jenkins

参考链接&#xff1a;tomcat安装和部署jenkins_jenkins和tomcat-CSDN博客 1、进入centos中 /usr/local 目录文件下 [rootlocalhost webapps]# cd /usr/local2、使用通过wget命令下下载tomcat或者直接在官网下载centos版本的包后移动到centos中的local路径下 3、下载tomcat按…

VUE3内置组件Transition的学习使用

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 更多nbcio-boot功能请看演示系统RuoYi-Nbcio亿事达企业管理平台 gitee源代码地址 后端代码&#xff1a;…

详解Postman使用

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

从0到1入门C++编程——12 演讲比赛流程管理系统

文章目录 一、创建类并显示菜单二、退出管理系统三、开始演讲比赛四、查看往届记录五、清空比赛记录六、案例源代码 演讲比赛流程管理系统 比赛规则&#xff1a;演讲比赛共有12个人参加&#xff0c;比赛分两轮进行&#xff0c;第一轮为淘汰赛&#xff0c;第二轮为决赛。每名选手…