使用C++多线程调用Salsa20算法时内存暴涨问题及解决方案问询
解决Salsa20多线程文件处理内存暴涨问题
内存暴涨的核心原因
你遇到的内存暴涨主要是Windows文件系统缓存导致的:多线程同时读写大文件(如ISO镜像)时,Windows会自动将文件数据缓存到物理内存中以提升后续读写速度,这部分内存由系统管理,不属于你的程序堆/栈内存,但会被计入系统整体内存占用。此外,原代码中的频繁lseek+读写操作会加剧系统缓存的累积。
低内存多线程解决方案
1. 绕过Windows文件系统缓存
使用Windows原生API打开文件时,添加FILE_FLAG_NO_BUFFERING和FILE_FLAG_WRITE_THROUGH标志,直接读写磁盘而不经过系统缓存,从根源上避免缓存占用内存。注意:
- 读写缓冲区大小和文件偏移量必须是磁盘扇区大小的整数倍(通常为4096字节)
- 缓冲区需对齐到扇区边界
修改后的salsa20函数(替换原POSIX文件API):
#include <windows.h> #include <thread> #include <memory> #include <algorithm> // 磁盘扇区大小(可通过GetDiskFreeSpace获取实际值) constexpr DWORD SECTOR_SIZE = 4096; // 缓冲区大小保持40KB,为扇区大小的整数倍 constexpr size_t BN = SECTOR_SIZE * 10; // RAII管理文件句柄,避免泄漏 struct FileHandleDeleter { void operator()(HANDLE h) const { if (h != INVALID_HANDLE_VALUE) CloseHandle(h); } }; using UniqueFileHandle = std::unique_ptr<void, FileHandleDeleter>; void salsa20(const wchar_t* fileName, const char* key) { // 打开文件,禁用系统缓存 HANDLE hFile = CreateFileW( fileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH | FILE_ATTRIBUTE_NORMAL, nullptr ); if (hFile == INVALID_HANDLE_VALUE) return; UniqueFileHandle fileHandle(hFile); // 分配对齐的缓冲区 uint8_t* temp = reinterpret_cast<uint8_t*>(_aligned_malloc(BN, SECTOR_SIZE)); if (!temp) return; DWORD readSize = 0; LARGE_INTEGER filePos = {0}; int bufsec = 0; while (ReadFile(hFile, temp, BN, &readSize, nullptr) && readSize > 0) { // 执行Salsa20加密逻辑 for (int i = 0; i < readSize; i += 64) { int chunkSize = std::min(64, readSize - i); salsa_encrypt(reinterpret_cast<const uint8_t*>(key), bufsec++, temp + i, chunkSize); } // 定位到读取位置并写回数据 SetFilePointerEx(hFile, filePos, nullptr, FILE_BEGIN); DWORD writtenSize = 0; WriteFile(hFile, temp, readSize, &writtenSize, nullptr); // 更新文件位置,准备下一次读取 filePos.QuadPart += readSize; SetFilePointerEx(hFile, filePos, nullptr, FILE_BEGIN); } _aligned_free(temp); }
2. 优化线程资源管理
- 限制并发数:磁盘(尤其是HDD)的并发IO能力有限,过多线程会导致磁头寻道开销增加,反而降低效率。建议并发数等于CPU核心数或磁盘IO队列长度,使用线程池统一管理:
#include <vector> #include <queue> #include <mutex> #include <condition_variable> #include <functional> class ThreadPool { public: ThreadPool(size_t numThreads) { for (size_t i = 0; i < numThreads; ++i) { threads.emplace_back([this] { while (true) { std::function<void()> task; { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, [this] { return stop || !tasks.empty(); }); if (stop && tasks.empty()) return; task = std::move(tasks.front()); tasks.pop(); } task(); } }); } } ~ThreadPool() { { std::unique_lock<std::mutex> lock(mtx); stop = true; } cv.notify_all(); for (auto& t : threads) t.join(); } void enqueue(std::function<void()> task) { { std::unique_lock<std::mutex> lock(mtx); tasks.emplace(std::move(task)); } cv.notify_one(); } private: std::vector<std::thread> threads; std::queue<std::function<void()>> tasks; std::mutex mtx; std::condition_variable cv; bool stop = false; }; int main() { // 根据CPU核心数创建线程池 ThreadPool pool(std::thread::hardware_concurrency()); pool.enqueue([] { salsa20(L"1.iso", "PASSWORDTYPE1___!@#$%^&*()!@#$%^"); }); pool.enqueue([] { salsa20(L"2.iso", "PASSWORDTYPE2___!@#$%^&*()!@#$%^"); }); pool.enqueue([] { salsa20(L"3.iso", "PASSWORDTYPE2___!@#$%^&*()!@#$%^"); }); return 0; }
- 确保资源正确释放:原代码中
read出错时直接break未关闭文件,可能导致资源泄漏,使用RAII(如UniqueFileHandle)可自动管理文件句柄,避免此类问题。
3. 检查加密函数内存泄漏
确认salsa_encrypt函数中没有动态内存分配未释放的情况(如malloc/new后未free/delete),或全局变量累积内存的问题。
额外建议
- 若使用MinGW/Cygwin编译,需确保链接Windows系统库(如
-luser32 -lkernel32) - 不要强制释放系统缓存(如
SetSystemFileCacheSize),该操作需要管理员权限,且可能影响其他程序性能
内容的提问来源于stack exchange,提问作者Melvin Lang
相关产品推荐
相关产品推荐

