C++编程:多线程文件写入

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 策略,可以避免多线程同时写入同一个文件带来的锁竞争问题。

实现方式

  1. 每个线程写入独立的临时文件

  2. 所有线程完成后,主线程合并这些临时文件

  3. 清理临时文件

#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操作方式,它将文件直接映射到进程的地址空间,使得文件操作可以像操作内存一样简单高效

内存映射文件的工作流程:

  1. 打开文件描述符

  2. 将文件映射到进程地址空间

  3. 像操作普通内存一样读写文件

  4. 取消映射并关闭文件

实现方式

///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指导内核缓存策略

选择建议

  1. 简单日志记录:互斥锁单文件(<100线程)

  2. 高吞吐日志系统:队列+单写入线程

  3. 大数据处理:多文件写入后合并

  4. 固定长度数据库:文件分段写入

  5. 高性能键值存储:内存映射文件

       实际选择时应根据具体场景测试验证,特别是在不同存储介质(SSD/HDD)上的表现可能有显著差异。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值