C++ 多线程文件写入常用方式及比较
在 C++ 中实现多线程文件写入有多种方法,各有优缺点。
| 方法 | 并发性 | 实现难度 | 适用场景 | 性能特点 | 数据一致性 |
|---|---|---|---|---|---|
| 互斥锁单文件 | 低 | ★☆☆☆☆ | 低频小数据 | 线程数↑ 性能↓ | 强一致 |
| 队列+单写入线程 | 中 | ★★★☆☆ | 高频小数据流 | 稳定但吞吐受限 | 强一致 |
| 多文件合并 | 高 | ★★☆☆☆ | 大数据量批处理 | 最佳并行I/O性能 | 最终一致 |
| 文件分段写入 | 高 | ★★★★☆ | 固定大小记录 | 依赖磁盘寻址性能 | 部分一致 |
| 内存映射文件 | 高 | ★★★★★ | 高频随机访问/超大文件 | 最高但同步复杂 | 需手动控制 |
以下是 5 种常用方式的详细比较和实现示例:
1. 互斥锁保护单文件写入
实现方式:
#include <fstream>
#include <thread>
#include <mutex>
std::mutex file_mutex;
void write_with_lock(const std::string& msg, const std::string& filename) {
std::lock_guard<std::mutex> lock(file_mutex);
std::ofstream file(filename, std::ios::app);
file << msg << "\n";
}
特点:
实现简单直接
高并发时锁竞争严重
适合低频小数据写入
线程数增加时性能下降明显
2. 队列+单写入线程模式
实现方式:
#include <queue>
#include <condition_variable>
std::queue<std::string> write_queue;
std::mutex queue_mutex;
std::condition_variable queue_cv;
bool stop_writing = false;
void writer_thread(const std::string& filename) {
std::ofstream file(filename);
while(true) {
std::string msg;
{
std::unique_lock<std::mutex> lock(queue_mutex);
queue_cv.wait(lock, []{return !write_queue.empty() || stop_writing;});
if(stop_writing && write_queue.empty()) break;
msg = write_queue.front();
write_queue.pop();
}
file << msg << "\n";
}
}
void enqueue_message(const std::string& msg) {
{
std::lock_guard<std::mutex> lock(queue_mutex);
write_queue.push(msg);
}
queue_cv.notify_one();
}
特点:
写入操作完全串行化
避免多线程I/O竞争
单写入线程可能成为瓶颈
适合高频率小数据写入
需要处理线程安全队列
3. 多文件写入后合并
在 C++ 中实现多线程分别写入不同文件然后合并是一种有效的并行 I/O 策略,可以避免多线程同时写入同一个文件带来的锁竞争问题。
实现方式:
-
每个线程写入独立的临时文件
-
所有线程完成后,主线程合并这些临时文件
-
清理临时文件
#include <iostream>
#include <fstream>
#include <vector>
#include <thread>
#include <mutex>
#include <sstream>
#include <algorithm>
#include <filesystem>
namespace fs = std::filesystem;
// 每个线程写入自己的临时文件
void thread_write_task(int thread_id, const std::string& temp_dir, int lines_to_write) {
std::ostringstream filename;
filename << temp_dir << "/temp_" << thread_id << ".txt";
std::ofstream outfile(filename.str());
if (!outfile.is_open()) {
std::cerr << "Thread " << thread_id << " failed to open file for writing\n";
return;
}
for (int i = 0; i < lines_to_write; ++i) {
outfile << "Thread " << thread_id << " - Line " << i << "\n";
}
outfile.close();
}
// 合并所有临时文件
void merge_files(const std::string& temp_dir, const std::string& output_filename) {
std::ofstream outfile(output_filename, std::ios::out | std::ios::app);
if (!outfile.is_open()) {
std::cerr << "Failed to open output file for merging\n";
return;
}
// 遍历临时目录中的所有文件
for (const auto& entry : fs::directory_iterator(temp_dir)) {
if (entry.path().extension() == ".txt" &&
entry.path().filename().string().find("temp_") == 0) {
std::ifstream infile(entry.path());
if (infile.is_open()) {
outfile << infile.rdbuf();
infile.close();
// 删除临时文件
fs::remove(entry.path());
}
}
}
outfile.close();
}
int main() {
const int num_threads = 4;
const int lines_per_thread = 100;
const std::string temp_dir = "temp_files";
const std::string output_file = "merged_output.txt";
// 创建临时目录
if (!fs::exists(temp_dir)) {
fs::create_directory(temp_dir);
}
// 启动多个线程写入不同文件
std::vector<std::thread> threads;
for (int i = 0; i < num_threads; ++i) {
threads.emplace_back(thread_write_task, i, temp_dir, lines_per_thread);
}
// 等待所有线程完成
for (auto& t : threads) {
t.join();
}
// 合并文件
merge_files(temp_dir, output_file);
// 删除临时目录(应该已经为空)
fs::remove(temp_dir);
std::cout << "All threads completed. Files merged to " << output_file << std::endl;
return 0;
}
特点:
写入阶段完全无锁
最大化并行I/O性能
需要额外合并步骤
适合大数据量写入
需要临时文件管理
4. 文件分段写入(预分配空间)
文件分段写入是一种高效的多线程文件操作方式,它将文件划分为多个不重叠的区域,每个线程负责写入自己独立的文件段,从而避免锁竞争。
实现方式:
#include <iostream>
#include <fstream>
#include <thread>
#include <vector>
#include <mutex>
#include <atomic>
void write_segment(const std::string& filename, size_t offset, size_t size, int thread_id) {
// 每个线程打开自己的文件流
std::ofstream file(filename, std::ios::binary | std::ios::in | std::ios::out);
if (!file) {
std::cerr << "Thread " << thread_id << " failed to open file\n";
return;
}
file.seekp(offset);
for (size_t i = 0; i < size; ++i) {
char byte = 'A' + (thread_id % 26); // 每个线程写入不同字符
file.put(byte);
// 模拟一些处理工作
if (i % 100 == 0) {
std::this_thread::yield();
}
}
std::cout << "Thread " << thread_id << " finished writing " << size
<< " bytes at offset " << offset << std::endl;
}
void segmented_write(const std::string& filename, size_t file_size, int num_threads) {
// 预分配文件空间
{
std::ofstream tmp(filename, std::ios::binary);
tmp.seekp(file_size - 1);
tmp.put('\0');
}
std::vector<std::thread> threads;
size_t segment_size = file_size / num_threads;
for (int i = 0; i < num_threads; ++i) {
size_t offset = i * segment_size;
size_t size = (i == num_threads - 1) ? (file_size - offset) : segment_size;
threads.emplace_back(write_segment, filename, offset, size, i);
}
for (auto& t : threads) {
t.join();
}
}
int main() {
const std::string filename = "segmented_output.bin";
const size_t file_size = 1024 * 1024; // 1MB
const int num_threads = 4;
segmented_write(filename, file_size, num_threads);
std::cout << "File writing completed. Total size: " << file_size << " bytes\n";
return 0;
}
特点:
允许并行写入不同区域
需要精确控制写入位置
不适合可变长度数据
需要预分配文件空间
适合固定大小记录写入
5. 内存映射文件(mmap)
内存映射文件(mmap)是一种高效的文件I/O操作方式,它将文件直接映射到进程的地址空间,使得文件操作可以像操作内存一样简单高效
内存映射文件的工作流程:
-
打开文件描述符
-
将文件映射到进程地址空间
-
像操作普通内存一样读写文件
-
取消映射并关闭文件
实现方式:
///POSIX 实现 (Linux/macOS)
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <iostream>
void mmap_write_posix() {
const char* filename = "mmap_example.dat";
const size_t file_size = 1024; // 1KB文件
// 1. 打开/创建文件
int fd = open(filename, O_RDWR | O_CREAT, 0644);
if (fd == -1) {
perror("open");
return;
}
// 2. 调整文件大小
if (ftruncate(fd, file_size) == -1) {
perror("ftruncate");
close(fd);
return;
}
// 3. 内存映射
void* map = mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) {
perror("mmap");
close(fd);
return;
}
// 4. 写入数据 - 像操作内存一样
char* data = static_cast<char*>(map);
const char* message = "Hello, Memory-mapped File!";
strncpy(data, message, strlen(message));
// 5. 同步到磁盘(可选)
if (msync(map, file_size, MS_SYNC) == -1) {
perror("msync");
}
// 6. 清理
if (munmap(map, file_size) == -1) {
perror("munmap");
}
close(fd);
}
///Windows 实现
#include <windows.h>
#include <iostream>
void mmap_write_windows() {
const wchar_t* filename = L"mmap_example_win.dat";
const size_t file_size = 1024;
// 1. 创建文件
HANDLE hFile = CreateFileW(filename,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE) {
std::cerr << "CreateFile failed: " << GetLastError() << std::endl;
return;
}
// 2. 设置文件大小
HANDLE hMapping = CreateFileMappingW(hFile,
NULL,
PAGE_READWRITE,
0,
file_size,
NULL);
if (!hMapping) {
std::cerr << "CreateFileMapping failed: " << GetLastError() << std::endl;
CloseHandle(hFile);
return;
}
// 3. 映射视图
LPVOID map = MapViewOfFile(hMapping,
FILE_MAP_WRITE,
0,
0,
file_size);
if (!map) {
std::cerr << "MapViewOfFile failed: " << GetLastError() << std::endl;
CloseHandle(hMapping);
CloseHandle(hFile);
return;
}
// 4. 写入数据
const char* message = "Windows Memory-mapped File Example";
strncpy_s(static_cast<char*>(map), file_size, message, strlen(message));
// 5. 清理
UnmapViewOfFile(map);
CloseHandle(hMapping);
CloseHandle(hFile);
}
多线程安全写入
使用内存映射文件实现多线程写入需要注意同步问题
#include <atomic>
#include <thread>
#include <vector>
std::mutex mmap_mutex;
std::atomic<size_t> write_pos(0);
void thread_write(void* map, size_t file_size, int thread_id) {
const int chunk_size = 128;
char buffer[128];
while (true) {
size_t current_pos = write_pos.fetch_add(chunk_size);
if (current_pos >= file_size) break;
snprintf(buffer, sizeof(buffer),
"Thread %d writes at position %zu\n", thread_id, current_pos);
// 实际写入需要加锁或确保不重叠
std::lock_guard<std::mutex> lock(mmap_mutex);
memcpy(static_cast<char*>(map) + current_pos, buffer, strlen(buffer));
}
}
void concurrent_mmap_write() {
const size_t file_size = 4096; // 4KB
const int num_threads = 4;
// 创建并映射文件(参考前面的POSIX或Windows实现)
// ...
std::vector<std::thread> threads;
for (int i = 0; i < num_threads; ++i) {
threads.emplace_back(thread_write, map, file_size, i);
}
for (auto& t : threads) {
t.join();
}
// 清理资源...
}
特点:
内存操作速度
可精细控制同步
实现复杂
需要处理内存同步
适合高频随机访问
性能优化技巧
批量写入:合并多次小写入为单次大写入
// 坏: 多次小写入 for(auto& item : data) file << item; // 好: 单次大写入 std::string buffer; for(auto& item : data) buffer += item; file << buffer;缓冲区设置:
std::ofstream file; file.rdbuf()->pubsetbuf(buffer, 8192); // 8KB缓冲区写时复制:对只追加日志考虑COW技术
IO优先级:使用posix_fadvise指导内核缓存策略
选择建议
-
简单日志记录:互斥锁单文件(<100线程)
-
高吞吐日志系统:队列+单写入线程
-
大数据处理:多文件写入后合并
-
固定长度数据库:文件分段写入
-
高性能键值存储:内存映射文件
实际选择时应根据具体场景测试验证,特别是在不同存储介质(SSD/HDD)上的表现可能有显著差异。
10万+

被折叠的 条评论
为什么被折叠?



