共享内存
概述
- 核心在于让多个进程能够访问同一块物理内存区域,从而实现快速的数据共享
- 原理
- 将同一段物理内存映射到多个进程各自的虚拟地址空间
- 通信方式
- 进程直接读写该内存区域,无需内核在用户态和内核态间拷贝数据
- 高性能原因
- 避免了数据在用户态和内核态之间的拷贝,是速度最快的
IPC方式之一
- 避免了数据在用户态和内核态之间的拷贝,是速度最快的
- 关于同步
- 不提供内置同步机制,需结合信号量等机制确保数据一致性
同步
- 互斥量
- 事件
- 信号量
示例
server
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
#include <iostream> #include <windows.h> int main() { // 创建文件映射对象(共享内存) HANDLE hMapFile = CreateFileMapping( INVALID_HANDLE_VALUE, // 使用系统页文件 NULL, // 默认安全属性 PAGE_READWRITE, // 可读可写 0, // 内存大小高32位 4096, // 内存大小低32位(4KB) L"Local\\MySharedMemory" // 共享内存名称 ); if (hMapFile == NULL) { std::cout << "创建共享内存失败: " << GetLastError() << std::endl; return 1; } // 将共享内存映射到进程地址空间 char* pBuf = (char*)MapViewOfFile( hMapFile, // 共享内存句柄 FILE_MAP_ALL_ACCESS, // 可读写访问 0, 0, // 偏移量 4096 // 映射大小 ); if (pBuf == NULL) { std::cout << "映射共享内存失败: " << GetLastError() << std::endl; CloseHandle(hMapFile); return 1; } // 向共享内存写入数据 const char* message = "Hello from Server Process!"; strcpy_s(pBuf, 4096, message); std::cout << "数据已写入共享内存,按任意键退出..." << std::endl; std::cin.get(); // 清理资源 UnmapViewOfFile(pBuf); CloseHandle(hMapFile); return 0; } |
client
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
#include <iostream> #include <windows.h> int main() { // 打开已存在的共享内存 HANDLE hMapFile = OpenFileMapping( FILE_MAP_ALL_ACCESS, // 可读写访问 FALSE, // 句柄不可继承 L"Local\\MySharedMemory" // 共享内存名称 ); if (hMapFile == NULL) { std::cout << "打开共享内存失败: " << GetLastError() << std::endl; return 1; } // 将共享内存映射到进程地址空间 char* pBuf = (char*)MapViewOfFile( hMapFile, // 共享内存句柄 FILE_MAP_ALL_ACCESS, // 可读写访问 0, 0, // 偏移量 4096 // 映射大小 ); if (pBuf == NULL) { std::cout << "映射共享内存失败: " << GetLastError() << std::endl; CloseHandle(hMapFile); return 1; } // 从共享内存读取数据 std::cout << "从共享内存读取: " << pBuf << std::endl; std::cout << "按任意键退出..." << std::endl; std::cin.get(); // 清理资源 UnmapViewOfFile(pBuf); CloseHandle(hMapFile); return 0; } |
文件内存映射
概述
- 能将磁盘上的文件直接映射到进程的虚拟地址空间,从而可以像访问内存一样直接读写文件
原理
- 建立映射关系
- 调用
MapViewOfFile时,操作系统并非立即将整个文件内容加载到物理内存(RAM)中 - 它只是在你的进程的虚拟地址空间中划出一块区域,并将这块区域的“后备存储”指定为磁盘上的那个文件
- 此时,这部分虚拟地址大部分还没有对应的物理内存
- 调用
- 按需调页
- 当首次通过返回的指针访问这块映射区域时,
CPU会发现该虚拟地址对应的物理页面不存在,这会触发一个页面错误 - 操作系统捕获到这个错误后,会分配一页物理内存,然后从磁盘文件的对应偏移处读取相应的数据(例如
4KB)填充该页面,最后更新页表,建立虚拟地址到物理地址的映射 - 此后,程序就能像访问普通内存一样访问这些文件数据了
- 当首次通过返回的指针访问这块映射区域时,
- 透明缓存与回写
- 被访问过的文件内容会暂时缓存在物理内存中
- 如果后续再次访问相同数据,且缓存未被置换,则速度极快
- 你修改了映射内存中的数据后,操作系统会负责在适当时机(如系统空闲、调用
FlushViewOfFile或视图解除映射时)将这些脏页面写回磁盘文件,从而保持数据同步
同步
- 互斥量
- 事件
- 信号量
示例
- 写入
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
#include <iostream> #include <Windows.h> #include <cstring> int main() { HANDLE hFile = CreateFile( L"memory_mapped_example.dat", // 文件名 GENERIC_READ | GENERIC_WRITE, // 读写权限 FILE_SHARE_READ, // 共享读权限 NULL, // 默认安全属性 CREATE_ALWAYS, // 总是创建新文件 FILE_ATTRIBUTE_NORMAL, // 普通文件属性 NULL ); if (hFile == INVALID_HANDLE_VALUE) { std::cerr << "创建文件失败! 错误代码: " << GetLastError() << std::endl; return 1; } // 设置文件大小 const DWORD fileSize = 4096; // 4KB文件 SetFilePointer(hFile, fileSize, NULL, FILE_BEGIN); SetEndOfFile(hFile); // 创建文件映射对象 HANDLE hMapFile = CreateFileMapping( hFile, // 文件句柄 NULL, // 默认安全属性 PAGE_READWRITE, // 可读可写 0, // 文件大小高32位 fileSize, // 文件大小低32位 L"Local\\MyFileMapping" // 映射对象名称(可用于进程间共享) ); if (hMapFile == NULL) { std::cerr << "创建文件映射失败! 错误代码: " << GetLastError() << std::endl; CloseHandle(hFile); return 1; } // 将文件映射到进程地址空间 LPVOID pMapView = MapViewOfFile( hMapFile, // 映射对象句柄 FILE_MAP_ALL_ACCESS, // 完全访问权限 0, 0, // 偏移量 fileSize // 映射大小 ); if (pMapView == NULL) { std::cerr << "映射文件视图失败! 错误代码: " << GetLastError() << std::endl; CloseHandle(hMapFile); CloseHandle(hFile); return 1; } // 向映射内存写入数据 char* pData = static_cast<char*>(pMapView); const char* message = "Hello, Memory Mapped File! 这是通过内存映射写入的数据。"; strcpy_s(pData, fileSize, message); // 写入结构化数据示例 int* pNumbers = reinterpret_cast<int*>(pData + 256); for (int i = 0; i < 10; ++i) { pNumbers[i] = i * 100; } std::cout << "数据写入完成!" << std::endl; std::cout << "文本数据: " << pData << std::endl; std::cout << "数字数据: "; for (int i = 0; i < 10; ++i) { std::cout << pNumbers[i] << " "; } std::cout << std::endl; // 强制将更改刷新到磁盘 FlushViewOfFile(pMapView, fileSize); // 清理资源 UnmapViewOfFile(pMapView); CloseHandle(hMapFile); CloseHandle(hFile); std::cout << "写入程序执行完毕,文件已保存。" << std::endl; return 0; } |
- 读取
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
#include <iostream> #include <Windows.h> int main() { // 打开已存在的文件 HANDLE hFile = CreateFile( L"memory_mapped_example.dat", // 文件名 GENERIC_READ, // 只读权限 FILE_SHARE_READ, // 共享读权限 NULL, // 默认安全属性 OPEN_EXISTING, // 打开已存在文件 FILE_ATTRIBUTE_NORMAL, // 普通文件属性 NULL ); if (hFile == INVALID_HANDLE_VALUE) { std::cerr << "打开文件失败! 错误代码: " << GetLastError() << std::endl; std::cerr << "请先运行写入程序创建文件。" << std::endl; return 1; } // 获取文件大小 DWORD fileSize = GetFileSize(hFile, NULL); if (fileSize == INVALID_FILE_SIZE) { std::cerr << "获取文件大小失败! 错误代码: " << GetLastError() << std::endl; CloseHandle(hFile); return 1; } // 创建文件映射对象(只读) HANDLE hMapFile = CreateFileMapping( hFile, // 文件句柄 NULL, // 默认安全属性 PAGE_READONLY, // 只读权限 0, // 文件大小高32位 0, // 文件大小低32位(0表示整个文件) L"Local\\MyFileMapping" // 映射对象名称 ); if (hMapFile == NULL) { std::cerr << "创建文件映射失败! 错误代码: " << GetLastError() << std::endl; CloseHandle(hFile); return 1; } // 将文件映射到进程地址空间(只读) LPVOID pMapView = MapViewOfFile( hMapFile, // 映射对象句柄 FILE_MAP_READ, // 只读访问 0, 0, // 偏移量 0 // 映射整个文件 ); if (pMapView == NULL) { std::cerr << "映射文件视图失败! 错误代码: " << GetLastError() << std::endl; CloseHandle(hMapFile); CloseHandle(hFile); return 1; } // 从映射内存读取数据 char* pData = static_cast<char*>(pMapView); std::cout << "=== 从内存映射文件读取的数据 ===" << std::endl; std::cout << "文本数据: " << pData << std::endl; // 读取结构化数据 int* pNumbers = reinterpret_cast<int*>(pData + 256); std::cout << "数字数据: "; for (int i = 0; i < 10; ++i) { std::cout << pNumbers[i] << " "; } std::cout << std::endl; // 显示文件信息 std::cout << "文件大小: " << fileSize << " 字节" << std::endl; // 显示原始字节数据(前100字节) std::cout << "前100字节的十六进制表示: " << std::hex; for (int i = 0; i < 100 && i < fileSize; ++i) { if (i % 20 == 0) std::cout << std::endl << " "; std::cout << static_cast<unsigned int>(static_cast<unsigned char>(pData[i])) << " "; } std::cout << std::dec << std::endl; // 清理资源 UnmapViewOfFile(pMapView); CloseHandle(hMapFile); CloseHandle(hFile); std::cout << "读取程序执行完毕。" << std::endl; return 0; } |
对比共享内存
| 特性维度 | 共享内存 | 文件内存映射 |
| 实现基础与后端存储 | 主要依赖系统页文件或物理内存,对进程透明 |
显式地将一个普通文件映射到内存 |
| 数据持久化 | 通常非持久化,进程结束、系统重启后数据丢失 | 支持持久化,修改可写回磁盘文件,实现数据持久储存 |
| 创建 | 如Linux的shmget,Windows的CreateFileMapping(传入特定参数) |
如Linux的mmap,Windows的CreateFileMapping(映射真实文件句柄) |
| 通信媒介本质 | 直接共享物理内存页,速度极快,是最高效的IPC方式之一 |
以映射的文件为中介,进程通过映射同一文件通信 |
| 场景 | 高性能、大数据量、实时性要求高的进程间通信,如数据库、科学计算 | 大文件随机读写(提高I/O效率)、需数据持久化的进程间通信 |
命名管道
概述
- 模型
- 命名管道采用经典的客户端-服务器模型
- 服务器进程负责创建管道并给它分配一个唯一的名字,然后等待客户端的连接请求
- 任何知道该管道名称的客户端进程都可以尝试连接
- 命名规范
- 管道的名称遵循
UNC路径格式:\\服务器名\pipe\管道名 - 对于本地通信,服务器名通常用一个小数点表示,即
\\.\pipe\MyPipeName - 这个名称在整个系统内是唯一的,是客户端找到服务器的关键
- 管道的名称遵循
- 与匿名管道的区别
- 匿名管道通常只能在有继承关系(如父子关系)的进程间通信,且是临时的
- 命名管道允许任意进程(无论是否有亲缘关系)通过名称进行连接,并且支持跨网络通信(远程服务器名可以是
IP地址或主机名)
- 内核对象
- 管道实例本质上是一个由内核管理的对象
- 操作系统会为每个管道实例维护输入和输出缓冲区,用于临时存放待读取或待发送的数据,从而协调读写双方可能存在的速度差异
- 模式
- 字节模式:数据被视为一个连续的字节流。读取方无法直接感知消息的边界,可能需要自行解析协议
- 消息模式:数据被分割成一个个独立的消息单位。每次写入操作构成一条完整的消息,读取操作也会一次读取整条消息,保证了消息边界的完整性
同步
- 互斥量
- 事件
- 信号量
示例
server
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
#include <iostream> #include <windows.h> int main() { // 创建命名管道 HANDLE hPipe = CreateNamedPipe( L"\\\\.\\pipe\\MyPipe", // 管道名称 PIPE_ACCESS_DUPLEX, // 双向访问 PIPE_TYPE_MESSAGE | // 消息类型管道 PIPE_READMODE_MESSAGE | // 消息读模式 PIPE_WAIT, // 阻塞模式 PIPE_UNLIMITED_INSTANCES, // 无限制实例 4096, 4096, // 输入输出缓冲区 0, NULL // 默认超时和安全属性 ); if (hPipe == INVALID_HANDLE_VALUE) { std::cout << "创建管道失败: " << GetLastError() << std::endl; return 1; } std::cout << "等待客户端连接..." << std::endl; // 等待客户端连接 BOOL connected = ConnectNamedPipe(hPipe, NULL); if (!connected) { std::cout << "客户端连接失败: " << GetLastError() << std::endl; CloseHandle(hPipe); return 1; } std::cout << "客户端已连接!" << std::endl; // 从客户端读取数据 char buffer[1024]; DWORD bytesRead; BOOL result = ReadFile(hPipe, buffer, sizeof(buffer) - 1, &bytesRead, NULL); if (result && bytesRead > 0) { buffer[bytesRead] = '\0'; std::cout << "收到客户端消息: " << buffer << std::endl; // 向客户端发送响应 const char* response = "Hello from Server!"; DWORD bytesWritten; WriteFile(hPipe, response, strlen(response) + 1, &bytesWritten, NULL); } // 断开连接并清理 DisconnectNamedPipe(hPipe); CloseHandle(hPipe); return 0; } |
client
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
#include <iostream> #include <windows.h> int main() { // 等待管道可用 if (!WaitNamedPipe(L"\\\\.\\pipe\\MyPipe", NMPWAIT_WAIT_FOREVER)) { std::cout << "等待管道失败: " << GetLastError() << std::endl; return 1; } // 连接到命名管道 HANDLE hPipe = CreateFile( L"\\\\.\\pipe\\MyPipe", // 管道名称 GENERIC_READ | GENERIC_WRITE, // 读写权限 0, NULL, // 无共享,默认安全 OPEN_EXISTING, // 打开已存在的管道 FILE_ATTRIBUTE_NORMAL, // 普通属性 NULL // 无模板 ); if (hPipe == INVALID_HANDLE_VALUE) { std::cout << "连接管道失败: " << GetLastError() << std::endl; return 1; } // 设置管道读模式 DWORD mode = PIPE_READMODE_MESSAGE; SetNamedPipeHandleState(hPipe, &mode, NULL, NULL); // 向服务器发送数据 const char* message = "Hello from Client!"; DWORD bytesWritten; BOOL result = WriteFile(hPipe, message, strlen(message) + 1, &bytesWritten, NULL); if (result) { std::cout << "消息发送成功" << std::endl; // 读取服务器响应 char buffer[1024]; DWORD bytesRead; if (ReadFile(hPipe, buffer, sizeof(buffer) - 1, &bytesRead, NULL)) { buffer[bytesRead] = '\0'; std::cout << "服务器响应: " << buffer << std::endl; } } CloseHandle(hPipe); return 0; } |
消息队列
概述
- Windows操作系统本身是消息驱动的
- 当用户进行输入(如点击鼠标、敲击键盘)或系统事件发生时,这些操作会被转换成消息
- 系统会将消息投递到目标窗口所在
线程的消息队列中 - 每个拥有窗口的线程都会维护自己的消息队列,应用程序通过一个持续的
消息循环从队列中取出消息,并将其分发给相应的窗口过程函数进行处理
- 核心点
- 每个消息都包含一个标识符(
Msg)和两个附加参数(wParam和lParam),可用于携带简单的指令或数据
- 每个消息都包含一个标识符(
APIPostMessage/PostThreadMessageSendMessage
- 为何它适用于进程间通信?
- 消息队列能够用于
IPC,是因为这些队列由系统内核维护 - 不同进程的线程,只要能够获取到目标窗口的句柄(
HWND)或线程ID(DWORD),就可以通过上述API向该线程的消息队列发送消息,从而实现跨进程通信
- 消息队列能够用于
WM_COPYDATA
- 传递数据
- 由于
wParam和lParam只能传递简单数据或值,直接传递指针是无效的(因为进程内存空间隔离) - 为了安全地传递数据块,Windows提供了
WM_COPYDATA消息 - 用这种方式时,发送方填充一个
COPYDATASTRUCT结构体,其中包含指向数据的指针和大小
系统会在内部将数据复制到接收进程的地址空间,接收方在处理WM_COPYDATA消息时,就可以安全地访问这份数据副本
- 由于
对比选择
- 极致的速度、最低的延迟
- 例如在实时数据处理、高频交易系统中,
共享内存配合信号量等同步机制是毋庸置疑的选择,尽管它实现最复杂
- 例如在实时数据处理、高频交易系统中,
- 父子进程、简单数据
匿名管道因其极致的简单性是一个非常流行的选择
- 相互独立的进程
- 命名管道、消息队列或共享内存才是可选项
- 同步异步
管道是典型的同步通信:写入方通常需要等待读取方取走数据消息队列天然支持异步:生产者放入消息后即可返回,消费者可以后续处理
- 数据量
海量数据交换,优先考虑共享内存- 仅仅是
通知某个事件发生,一个信号或许就足够了
进程同步方式
- 内核对象
- 互斥量
- 事件
- 信号量
- 用户模式
- 文件锁:
LockFileEx - 条件变量 :
CONDITION_VARIABLE - 自旋锁:使用
CRITICAL_SECTION
- 文件锁:
线程通信方式
- 全局变量、共享内存
- 可直接传递复杂数据
- 消息机制
- 依赖消息循环
- 可通过
wParam和lParam参数传递简单数据或指针
- 事件
- 主要用于通知事件发生,本身不直接传递数据
线程同步方式
- 内核对象
- 互斥量
- 事件
- 信号量
- 用户模式
- 临界区
- 条件变量
InterLocked系列函数
C++的方式std::mutex等:C++对互斥锁的标准实现,常与std::lock_guard配合使用std::atomic:提供跨平台的原子操作,替代Interlocked函数std::condition_variable:与std::unique_lock配合,用于等待特定条件成立,实现复杂同步模式
其他
文件锁
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
#include <windows.h> #include <stdio.h> int main() { HANDLE hFile = CreateFile( L"C:\\Temp\\sharedfile.txt", // 文件路径 GENERIC_READ | GENERIC_WRITE, 0, // 不共享 NULL, OPEN_ALWAYS, // 如果文件不存在则创建 FILE_ATTRIBUTE_NORMAL, NULL ); if (hFile == INVALID_HANDLE_VALUE) { printf("无法打开文件,错误代码: %d\n", GetLastError()); return 1; } // 尝试锁定文件(排他锁) OVERLAPPED overlapped = {0}; BOOL lockResult = LockFileEx( hFile, // 文件句柄 LOCKFILE_EXCLUSIVE_LOCK, // 排他锁 0, // 保留,必须为0 MAXDWORD, // 锁定的最大范围(低32位) MAXDWORD, // 锁定的最大范围(高32位) &overlapped // OVERLAPPED结构 ); if (lockResult) { printf("文件锁获取成功,开始操作共享资源...\n"); // 在此进行需要受保护的文件操作 Sleep(5000); // 模拟耗时操作 // 操作完成后解锁 UnlockFileEx( hFile, 0, MAXDWORD, MAXDWORD, &overlapped ); printf("文件锁已释放。\n"); } else { printf("文件锁获取失败,错误代码: %d\n", GetLastError()); } CloseHandle(hFile); return 0; } |
条件变量
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
#include <windows.h> #include <stdio.h> #define BUFFER_SIZE 10 // 共享资源和同步对象 LONG Buffer[BUFFER_SIZE]; ULONG QueueSize = 0; CRITICAL_SECTION BufferLock; // 保护共享资源的互斥锁 CONDITION_VARIABLE BufferNotEmpty; // 条件变量:缓冲区非空 CONDITION_VARIABLE BufferNotFull; // 条件变量:缓冲区未满 BOOL StopRequested = FALSE; // 生产者线程 DWORD WINAPI ProducerThreadProc(PVOID p) { ULONG ProducerId = (ULONG)(ULONG_PTR)p; while (TRUE) { // 模拟生产数据 Sleep(500); ULONG Item = InterlockedIncrement(&LastItemProduced); EnterCriticalSection(&BufferLock); // 等待条件:缓冲区未满 while (QueueSize == BUFFER_SIZE && StopRequested == FALSE) { SleepConditionVariableCS(&BufferNotFull, &BufferLock, INFINITE); } if (StopRequested) { LeaveCriticalSection(&BufferLock); break; } // 将数据放入缓冲区 Buffer[(QueueStartOffset + QueueSize) % BUFFER_SIZE] = Item; QueueSize++; printf("Producer %u: item %2d, queue size %2u\n", ProducerId, Item, QueueSize); LeaveCriticalSection(&BufferLock); // 通知消费者:缓冲区非空了 WakeConditionVariable(&BufferNotEmpty); } printf("Producer %u exiting\n", ProducerId); return 0; } // 消费者线程 DWORD WINAPI ConsumerThreadProc(PVOID p) { ULONG ConsumerId = (ULONG)(ULONG_PTR)p; while (TRUE) { EnterCriticalSection(&BufferLock); // 等待条件:缓冲区非空 while (QueueSize == 0 && StopRequested == FALSE) { SleepConditionVariableCS(&BufferNotEmpty, &BufferLock, INFINITE); } if (StopRequested && QueueSize == 0) { LeaveCriticalSection(&BufferLock); break; } // 从缓冲区取出数据 LONG Item = Buffer[QueueStartOffset]; QueueStartOffset = (QueueStartOffset + 1) % BUFFER_SIZE; QueueSize--; printf("Consumer %u: item %2d, queue size %2u\n", ConsumerId, Item, QueueSize); LeaveCriticalSection(&BufferLock); // 通知生产者:缓冲区有空间了 WakeConditionVariable(&BufferNotFull); // 模拟处理数据 Sleep(2000); } printf("Consumer %u exiting\n", ConsumerId); return 0; } int main() { // 初始化同步对象 InitializeConditionVariable(&BufferNotEmpty); InitializeConditionVariable(&BufferNotFull); InitializeCriticalSection(&BufferLock); HANDLE hProducer = CreateThread(NULL, 0, ProducerThreadProc, (PVOID)1, 0, NULL); HANDLE hConsumer1 = CreateThread(NULL, 0, ConsumerThreadProc, (PVOID)1, 0, NULL); HANDLE hConsumer2 = CreateThread(NULL, 0, ConsumerThreadProc, (PVOID)2, 0, NULL); puts("Press enter to stop..."); getchar(); // 设置停止标志,并唤醒所有等待线程 EnterCriticalSection(&BufferLock); StopRequested = TRUE; LeaveCriticalSection(&BufferLock); WakeAllConditionVariable(&BufferNotFull); WakeAllConditionVariable(&BufferNotEmpty); WaitForSingleObject(hProducer, INFINITE); WaitForSingleObject(hConsumer1, INFINITE); WaitForSingleObject(hConsumer2, INFINITE); DeleteCriticalSection(&BufferLock); return 0; } |
自旋锁
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
#include <windows.h> #include <stdio.h> // 使用带自旋计数的CRITICAL_SECTION CRITICAL_SECTION g_cs; void InitializeSpinLock() { // 初始化临界区,并设置自旋计数 // SPINLOCK_COUNT 定义了在进入等待状态前自旋的次数 // 使用 -1 表示使用默认值[9](@ref) InitializeCriticalSectionAndSpinCount(&g_cs, 4000); // 自旋4000次后再尝试获取锁 } void WorkerThread(LPVOID lpParam) { int threadId = (int)lpParam; printf("Thread %d is trying to acquire the lock...\n", threadId); EnterCriticalSection(&g_cs); // 获取"自旋锁" // 临界区开始 printf("Thread %d acquired the lock. Working...\n", threadId); Sleep(1000); // 模拟一些工作,持锁时间应非常短 printf("Thread %d releasing the lock.\n", threadId); // 临界区结束 LeaveCriticalSection(&g_cs); // 释放"自旋锁" } int main() { const int NUM_THREADS = 4; HANDLE hThreads[NUM_THREADS]; InitializeSpinLock(); for (int i = 0; i < NUM_THREADS; i++) { hThreads[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)WorkerThread, (LPVOID)i, 0, NULL); } WaitForMultipleObjects(NUM_THREADS, hThreads, TRUE, INFINITE); for (int i = 0; i < NUM_THREADS; i++) { CloseHandle(hThreads[i]); } DeleteCriticalSection(&g_cs); return 0; } |
博客
声明:本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!