文件读写进阶之多线程/多进程读写【c++】

在C++中,多线程读写文件需要特别小心,以避免数据竞态(Race Condition)和死锁(Deadlock)等问题。以下是几种常见的多线程读写文件的方式:

1. 使用互斥锁(Mutex)保护文件操作

互斥锁是控制并发访问资源的常用方式。在多线程读写文件时,可以使用互斥锁来确保每次只有一个线程可以访问文件,从而避免数据竞态。

#include <iostream>
#include <fstream>
#include <thread>
#include <mutex>

std::mutex fileMutex;

void writeToFile(const std::string &filename, const std::string &message) 
{
    std::lock_guard<std::mutex> lock(fileMutex); // 锁住互斥锁
    std::ofstream outFile(filename, std::ios::app);
    if (outFile.is_open()) 
    {
        outFile << message << std::endl;
    }
}

void threadFunction(const std::string &filename) 
{
    for (int i = 0; i < 10; ++i) 
    {
        writeToFile(filename, "Thread writing " + std::to_string(i));
    }
}

int main() 
{
    std::string filename = "output.txt";
    std::thread t1(threadFunction, filename);
    std::thread t2(threadFunction, filename);

    t1.join();
    t2.join();

    return 0;
}

说明

  • std::mutex 用于控制对共享资源(文件)的访问。
  • std::lock_guard<std::mutex> 是一种RAII风格的锁管理方式,自动管理互斥锁的生命周期。

2. 读写锁(Shared Mutex)

C++17 引入了std::shared_mutex,允许多个线程同时读取文件,但在有线程写入文件时阻止其他线程读取。适用于读多写少的场景。

#include <iostream>
#include <fstream>
#include <thread>
#include <shared_mutex>

std::shared_mutex fileMutex;

void readFromFile(const std::string &filename) 
{
    std::shared_lock<std::shared_mutex> lock(fileMutex); // 共享锁,用于读取
    std::ifstream inFile(filename);
    std::string line;
    while (std::getline(inFile, line)) 
    {
        std::cout << line << std::endl;
    }
}

void writeToFile(const std::string &filename, const std::string &message) 
{
    std::unique_lock<std::shared_mutex> lock(fileMutex); // 独占锁,用于写入
    std::ofstream outFile(filename, std::ios::app);
    if (outFile.is_open()) 
    {
        outFile << message << std::endl;
    }
}

void readThread(const std::string &filename) 
{
    readFromFile(filename);
}

void writeThread(const std::string &filename, const std::string &message) 
{
    writeToFile(filename, message);
}

int main() 
{
    std::string filename = "output.txt";
    std::thread t1(readThread, filename);
    std::thread t2(writeThread, filename, "Thread writing");

    t1.join();
    t2.join();

    return 0;
}

说明

  • std::shared_lock 用于读取操作,允许多个线程同时持有。
  • std::unique_lock 用于写入操作,确保写入时没有其他线程在读取或写入。

3. 使用线程池和任务队列

为了高效处理大量文件操作任务,可以使用线程池和任务队列,将所有的文件操作任务提交到一个线程池中,按顺序执行,确保线程安全。

#include <iostream>
#include <fstream>
#include <thread>
#include <mutex>
#include <queue>
#include <vector>
#include <functional>
#include <condition_variable>

class ThreadPool 
{
public:
    ThreadPool(size_t numThreads);
    ~ThreadPool();
    void enqueueTask(const std::function<void()>& task);

private:
    std::vector<std::thread> workers;
    std::queue<std::function<void()>> tasks;
    std::mutex queueMutex;
    std::condition_variable condition;
    bool stop;
};

ThreadPool::ThreadPool(size_t numThreads) : stop(false) 
{
    for (size_t i = 0; i < numThreads; ++i) 
    {
        workers.emplace_back([this] 
        {
            while (true) 
            {
                std::function<void()> task;
                {
                    std::unique_lock<std::mutex> lock(queueMutex);
                    condition.wait(lock, [this] { return stop || !tasks.empty(); });
                    if (stop && tasks.empty()) return;
                    task = std::move(tasks.front());
                    tasks.pop();
                }
                task();
            }
        });
    }
}

ThreadPool::~ThreadPool() 
{
    {
        std::unique_lock<std::mutex> lock(queueMutex);
        stop = true;
    }
    condition.notify_all();
    for (std::thread &worker : workers) worker.join();
}

void ThreadPool::enqueueTask(const std::function<void()>& task) 
{
    {
        std::unique_lock<std::mutex> lock(queueMutex);
        tasks.push(task);
    }
    condition.notify_one();
}

void writeToFile(const std::string &filename, const std::string &message) 
{
    std::ofstream outFile(filename, std::ios::app);
    if (outFile.is_open()) 
    {
        outFile << message << std::endl;
    }
}

int main() 
{
    ThreadPool pool(4);
    std::string filename = "output.txt";
    for (int i = 0; i < 10; ++i) 
    {
        pool.enqueueTask([filename, i] 
        {
            writeToFile(filename, "Task " + std::to_string(i));
        });
    }
    return 0;
}

说明

  • 线程池管理多个工作线程,通过任务队列调度任务,避免了多线程竞争资源。
  • 线程池能够有效利用系统资源,并确保任务按顺序执行。

4. 异步文件操作

使用std::async实现异步文件操作,让不同线程异步地读取或写入文件,从而提升程序的响应能力。

#include <iostream>
#include <fstream>
#include <future>

void asyncWriteToFile(const std::string &filename, const std::string &message) 
{
    std::ofstream outFile(filename, std::ios::app);
    if (outFile.is_open()) 
    {
        outFile << message << std::endl;
    }
}

void asyncReadFromFile(const std::string &filename) 
{
    std::ifstream inFile(filename);
    std::string line;
    while (std::getline(inFile, line)) 
    {
        std::cout << line << std::endl;
    }
}

int main() 
{
    std::string filename = "output.txt";
    auto futureWrite = std::async(std::launch::async, asyncWriteToFile, filename, "Hello from async");
    auto futureRead = std::async(std::launch::async, asyncReadFromFile, filename);

    futureWrite.get(); // 等待写入完成
    futureRead.get();  // 等待读取完成

    return 0;
}

说明

  • std::async 提供了一种异步执行函数的机制,返回std::future对象,用于在稍后获取结果或等待任务完成。
  • 适合需要同时进行多个I/O操作且不希望阻塞主线程的场景。

5. 避免文件竞争与死锁

  • 文件竞争:多个线程同时读写同一个文件时,需要确保互斥访问,否则可能导致数据竞争问题。
  • 死锁防范:使用std::lock_guardstd::unique_lock以避免死锁,并尽可能减少锁的持有时间。

c++多进程读写文件

在C++中,多进程读写同一文件需要考虑文件锁定和同步问题,以防止数据竞态和文件损坏。以下是几种常见的处理方式:

1. 文件锁定(File Locking)

使用文件锁定机制可以确保在多进程环境中对同一文件的读写操作是互斥的。常见的文件锁定方式包括:

1.1. POSIX 文件锁定

在类Unix系统(如Linux、macOS)中,可以使用fcntlflock来实现文件锁定。

  • fcntl:提供了记录锁(record locking),可以锁定文件的特定区域,支持读锁和写锁。

#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <iostream>
#include <string>

void writeToFileWithLock(const std::string &filename, const std::string &message) 
{
    int fd = open(filename.c_str(), O_WRONLY | O_CREAT, 0666);
    if (fd == -1) 
    {
        std::cerr << "Error opening file." << std::endl;
        return;
    }

    struct flock lock;
    lock.l_type = F_WRLCK;  // 写锁
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;  // 锁定整个文件

    if (fcntl(fd, F_SETLKW, &lock) == -1) 
    {
        std::cerr << "Error locking file." << std::endl;
        close(fd);
        return;
    }

    // 写入数据
    write(fd, message.c_str(), message.size());

    // 解锁
    lock.l_type = F_UNLCK;
    fcntl(fd, F_SETLK, &lock);
    
    close(fd);
}

flock:提供了简单的整文件锁定,可以锁定整个文件。

#include <sys/file.h>
#include <unistd.h>
#include <iostream>
#include <string>
#include <fcntl.h>

void writeToFileWithFlock(const std::string &filename, const std::string &message) 
{
    int fd = open(filename.c_str(), O_WRONLY | O_CREAT, 0666);
    if (fd == -1) 
    {
        std::cerr << "Error opening file." << std::endl;
        return;
    }

    if (flock(fd, LOCK_EX) == -1)  // 加写锁
    {
        std::cerr << "Error locking file." << std::endl;
        close(fd);
        return;
    }

    // 写入数据
    write(fd, message.c_str(), message.size());

    flock(fd, LOCK_UN);  // 解锁
    close(fd);
}
1.2. Windows 文件锁定

在Windows系统中,可以使用LockFileUnlockFile函数来实现文件锁定。

#include <windows.h>
#include <iostream>

void writeToFileWithLock(const std::string &filename, const std::string &message) 
{
    HANDLE hFile = CreateFile(filename.c_str(), GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) 
    {
        std::cerr << "Error opening file." << std::endl;
        return;
    }

    // 锁定文件的指定部分或整个文件
    OVERLAPPED ol = {0};
    if (!LockFileEx(hFile, LOCKFILE_EXCLUSIVE_LOCK, 0, MAXDWORD, MAXDWORD, &ol)) 
    {
        std::cerr << "Error locking file." << std::endl;
        CloseHandle(hFile);
        return;
    }

    // 移动文件指针到文件末尾
    SetFilePointer(hFile, 0, NULL, FILE_END);

    DWORD bytesWritten;
    WriteFile(hFile, message.c_str(), message.size(), &bytesWritten, NULL);

    // 解锁文件
    UnlockFileEx(hFile, 0, MAXDWORD, MAXDWORD, &ol);
    CloseHandle(hFile);
}

2. 进程间通信(IPC)和协调

在某些情况下,可以通过进程间通信(如消息队列、共享内存、管道等)来协调多个进程对同一文件的读写操作。

2.1. 使用信号量(Semaphore)

信号量可以用于控制对文件的访问权限,确保同一时间只有一个进程能够写入文件。

#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
#include <iostream>
#include <fstream>

void writeToFileWithSemaphore(int semid, const std::string &filename, const std::string &message) 
{
    struct sembuf sb = {0, -1, 0}; // 等待信号量
    semop(semid, &sb, 1);

    std::ofstream outFile(filename, std::ios::app);
    if (outFile.is_open()) 
    {
        outFile << message << std::endl;
        outFile.close();
    }

    sb.sem_op = 1; // 释放信号量
    semop(semid, &sb, 1);
}

int main() 
{
    key_t key = ftok("semfile", 65);
    int semid = semget(key, 1, 0666 | IPC_CREAT);
    semctl(semid, 0, SETVAL, 1);

    writeToFileWithSemaphore(semid, "output.txt", "Process writing");

    semctl(semid, 0, IPC_RMID); // 删除信号量
    return 0;
}
2.2. 共享内存中的写入协调

多个进程可以通过共享内存和同步原语(如信号量或互斥锁)协调对文件的写入。这种方法更为复杂,但在处理大量并发文件操作时可以提高性能。

3. 确保原子性

对于一些简单的操作,可以利用系统调用或文件操作的原子性来避免并发问题。例如,rename在类Unix系统中是原子操作,可以用来确保对文件的写操作是安全的。

4. 避免死锁

在设计多进程文件读写机制时,务必避免死锁。例如:

  • 确保所有进程按照相同的顺序获取锁。
  • 使用超时机制检测和恢复死锁情况。

c标准库多进程读写文件

在C语言中,当多个进程需要同时读写同一文件时,为了避免数据竞态、文件损坏等问题,需要采取合适的同步措施。以下是几种常见的方法:

1. 使用文件锁定(File Locking)

1.1. POSIX 文件锁定(fcntl)

在类Unix系统中,使用fcntl提供的文件锁定机制可以对文件的特定部分或整个文件进行加锁。可以使用以下几种锁定方式:

  • 读锁(共享锁):允许多个进程同时读,但不允许写。
  • 写锁(排他锁):不允许其他进程读或写。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

void write_to_file_with_lock(const char *filename, const char *message) 
{
    int fd = open(filename, O_WRONLY | O_CREAT, 0666);
    if (fd == -1) 
    {
        perror("Error opening file");
        return;
    }

    struct flock lock;
    lock.l_type = F_WRLCK;  // 写锁
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;  // 锁定整个文件

    if (fcntl(fd, F_SETLKW, &lock) == -1) 
    {
        perror("Error locking file");
        close(fd);
        return;
    }

    // 写入数据
    write(fd, message, strlen(message));

    // 解锁
    lock.l_type = F_UNLCK;
    fcntl(fd, F_SETLK, &lock);
    
    close(fd);
}

int main() 
{
    write_to_file_with_lock("example.txt", "Hello from process!\n");
    return 0;
}
1.2. flock 文件锁定

flock提供了简单的整文件锁定,可以使用共享锁和排他锁。

#include <stdio.h>
#include <sys/file.h>
#include <unistd.h>
#include <string.h>

void write_to_file_with_flock(const char *filename, const char *message) 
{
    int fd = open(filename, O_WRONLY | O_CREAT, 0666);
    if (fd == -1) 
    {
        perror("Error opening file");
        return;
    }

    if (flock(fd, LOCK_EX) == -1)  // 加写锁
    {
        perror("Error locking file");
        close(fd);
        return;
    }

    // 写入数据
    write(fd, message, strlen(message));

    flock(fd, LOCK_UN);  // 解锁
    close(fd);
}

int main() 
{
    write_to_file_with_flock("example.txt", "Hello from process!\n");
    return 0;
}

2. 使用信号量进行文件访问控制

信号量(Semaphore)可以用于多进程同步控制,确保同一时刻只有一个进程能够进行文件写操作。

#include <stdio.h>
#include <fcntl.h>
#include <semaphore.h>
#include <unistd.h>
#include <string.h>

void write_to_file_with_semaphore(sem_t *sem, const char *filename, const char *message) 
{
    sem_wait(sem);  // 等待信号量

    FILE *file = fopen(filename, "a");
    if (file != NULL) 
    {
        fputs(message, file);
        fclose(file);
    } 
    else 
    {
        perror("Error opening file");
    }

    sem_post(sem);  // 释放信号量
}

int main() 
{
    sem_t *sem = sem_open("/file_sem", O_CREAT, 0644, 1);

    if (sem == SEM_FAILED) 
    {
        perror("sem_open failed");
        return 1;
    }

    write_to_file_with_semaphore(sem, "example.txt", "Hello from process!\n");

    sem_close(sem);
    sem_unlink("/file_sem");

    return 0;
}

3. 避免文件竞争与死锁

当多个进程可能同时访问文件时,确保:

  • 锁的顺序:所有进程在请求多个资源时应按相同的顺序获取锁,以避免死锁。
  • 超时机制:通过设置锁定超时或使用非阻塞锁定方式,避免死锁的发生。

4. 原子性操作

某些系统调用是原子的(如writerename),可以利用这些原子操作确保多进程读写文件时的安全性。例如,在文件写入完成后,通过rename替换原始文件。

c标准库多线程读写文件

在C语言中,多线程读写同一文件需要特别小心,以避免数据竞态(race conditions)、文件损坏等问题。以下是几种常见的处理方法:

1. 使用互斥锁(Mutex)保护文件操作

互斥锁(pthread_mutex_t)可以用于同步多线程对文件的访问,确保在一个线程读写文件时,其他线程不能进行读写操作。

示例代码:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

pthread_mutex_t file_mutex = PTHREAD_MUTEX_INITIALIZER;

void *write_to_file(void *arg) 
{
    FILE *file;
    const char *message = (const char *)arg;

    pthread_mutex_lock(&file_mutex); // 加锁
    file = fopen("example.txt", "a");
    if (file != NULL) 
    {
        fputs(message, file);
        fclose(file);
    }
    pthread_mutex_unlock(&file_mutex); // 解锁

    return NULL;
}

int main() 
{
    pthread_t t1, t2;

    pthread_create(&t1, NULL, write_to_file, "Thread 1: Hello World!\n");
    pthread_create(&t2, NULL, write_to_file, "Thread 2: Hello World!\n");

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    pthread_mutex_destroy(&file_mutex);
    return 0;
}

2. 使用读写锁(Read-Write Lock)

C语言中,使用POSIX线程库提供的读写锁(pthread_rwlock_t)可以允许多个线程同时读取文件,但写操作依然是互斥的。

示例代码:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

pthread_rwlock_t file_rwlock = PTHREAD_RWLOCK_INITIALIZER;

void *read_from_file(void *arg) 
{
    FILE *file;
    char buffer[256];

    pthread_rwlock_rdlock(&file_rwlock); // 加读锁
    file = fopen("example.txt", "r");
    if (file != NULL) 
    {
        while (fgets(buffer, sizeof(buffer), file)) 
        {
            printf("%s", buffer);
        }
        fclose(file);
    }
    pthread_rwlock_unlock(&file_rwlock); // 解锁

    return NULL;
}

void *write_to_file(void *arg) 
{
    FILE *file;
    const char *message = (const char *)arg;

    pthread_rwlock_wrlock(&file_rwlock); // 加写锁
    file = fopen("example.txt", "a");
    if (file != NULL) 
    {
        fputs(message, file);
        fclose(file);
    }
    pthread_rwlock_unlock(&file_rwlock); // 解锁

    return NULL;
}

int main() 
{
    pthread_t t1, t2, t3;

    pthread_create(&t1, NULL, write_to_file, "Thread 1: Writing...\n");
    pthread_create(&t2, NULL, read_from_file, NULL);
    pthread_create(&t3, NULL, write_to_file, "Thread 3: Writing...\n");

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);

    pthread_rwlock_destroy(&file_rwlock);
    return 0;
}

3. 使用文件锁(File Locking)

POSIX的fcntl函数允许为文件设置记录锁(record locking),可以锁定文件的特定部分或整个文件,确保其他线程在锁定期间不能对该文件进行读写操作。

示例代码:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>

void *write_to_file_with_lock(void *arg) 
{
    int fd = open("example.txt", O_WRONLY | O_CREAT, 0666);
    if (fd == -1) 
    {
        perror("Error opening file");
        return NULL;
    }

    struct flock lock;
    lock.l_type = F_WRLCK;  // 写锁
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;  // 锁定整个文件

    fcntl(fd, F_SETLKW, &lock);  // 加锁

    write(fd, (const char *)arg, strlen((const char *)arg));

    lock.l_type = F_UNLCK;
    fcntl(fd, F_SETLK, &lock);  // 解锁

    close(fd);
    return NULL;
}

int main() 
{
    pthread_t t1, t2;

    pthread_create(&t1, NULL, write_to_file_with_lock, "Thread 1: Writing with lock...\n");
    pthread_create(&t2, NULL, write_to_file_with_lock, "Thread 2: Writing with lock...\n");

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    return 0;
}

4. 线程安全的I/O库(例如:GNU C Library 中的 flockfile/funlockfile

使用flockfilefunlockfile可以锁定标准I/O流,确保多线程环境下的fwritefprintf等I/O操作的原子性。

示例代码:
#include <stdio.h>
#include <pthread.h>

void *thread_safe_write(void *arg) 
{
    FILE *file = fopen("example.txt", "a");
    if (file == NULL) 
    {
        perror("Error opening file");
        return NULL;
    }

    flockfile(file);  // 锁定文件流
    fputs((const char *)arg, file);
    funlockfile(file);  // 解锁文件流

    fclose(file);
    return NULL;
}

int main() 
{
    pthread_t t1, t2;

    pthread_create(&t1, NULL, thread_safe_write, "Thread 1: Using flockfile\n");
    pthread_create(&t2, NULL, thread_safe_write, "Thread 2: Using flockfile\n");

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值