c++ - I/O 流真的是线程安全的吗?

标签 c++ multithreading c++11

我写了一个程序,在第一个线程中将随机数写入一个文件,另一个线程从那里读取它们并将那些是素数的文件写入另一个文件。需要第三个线程来停止/开始工作。我读到 I/O 线程是线程安全的。由于写入单个共享资源是线程安全的,那么问题是什么? 输出:numbers.log中总是正确的记录,有时在numbers_prime.log中没有记录当有质数时,有时它们都被写入。


#include <iostream>
#include <fstream>
#include <thread>
#include <mutex>
#include <vector>
#include <condition_variable>
#include <future>
#include <random>
#include <chrono>
#include <string>


using namespace std::chrono_literals;

std::atomic_int ITER_NUMBERS = 30;
std::atomic_bool _var = false;
bool ret() { return _var; }
std::atomic_bool _var_log = false;
bool ret_log() { return _var_log; }
std::condition_variable cv;
std::condition_variable cv_log;
std::mutex              mtx;
std::mutex mt;
std::atomic<int> count{0};
std::atomic<bool> _FL = 1;
int MIN = 100;
int MAX = 200;

bool is_empty(std::ifstream& pFile) // function that checks if the file is empty
{
    return pFile.peek() == std::ifstream::traits_type::eof();
}


bool isPrime(int n) // function that checks if the number is prime
{
    if (n <= 1)
        return false;
    
    for (int i = 2; i <= sqrt(n); i++)
        if (n % i == 0)
            return false;
    
    return true;
}


void Log(int min, int max) { // function that generates random numbers and writes them to a file numbers.log
    std::string str;
    std::ofstream log;
    std::random_device seed;
    std::mt19937 gen{seed()};
    std::uniform_int_distribution dist{min, max};
    log.open("numbers.log", std::ios_base::trunc);
    for (int i = 0; i < ITER_NUMBERS; ++i, ++count) {
        std::unique_lock<std::mutex> ulm(mtx);
        cv.wait(ulm,ret);
        str = std::to_string(dist(gen)) + '\n';
        log.write(str.c_str(), str.length());
        log.flush();
        _var_log = true;
        cv_log.notify_one();
        //_var_log = false;
        //std::this_thread::sleep_for(std::chrono::microseconds(500000));
        
    }
    log.close();
    _var_log = true;
    cv_log.notify_one();
    _FL = 0;
}




void printCheck() { // Checking function to start/stop printing
    
    std::cout << "Log to file? [y/n]\n";
    while (_FL) {
        char input;
        std::cin >> input;
        std::cin.clear();
        if (input == 'y') {
            _var = true;
            cv.notify_one();
        }
        if (input == 'n') {
            _var = false;
        }
    }
}

void primeLog() { // a function that reads files from numbers.log and writes prime numbers to numbers_prime.log
    std::unique_lock ul(mt);
    int number = 0;
    std::ifstream in("numbers.log");
    std::ofstream out("numbers_prime.log", std::ios_base::trunc);
    if (is_empty(in)) {
        cv_log.wait(ul, ret_log);
    }
    int oldCount{};
    for (int i = 0; i < ITER_NUMBERS; ++i) {
        if (oldCount == count && count != ITER_NUMBERS) { // check if primeLog is faster than Log. If it is faster, then we wait to continue
            cv_log.wait(ul, ret_log);
            _var_log = false;
        }
        if (!in.eof()) {
            in >> number;
            if (isPrime(number)) {
                out << number;
                out << "\n";
            }
            oldCount = count;
        }
    }
}


int main() {
    std::thread t1(printCheck);
    std::thread t2(Log, MIN, MAX);
    std::thread t3(primeLog);
    t1.join();
    t2.join();
    t3.join();
    return 0;
}

最佳答案

这与 I/O 流线程安全无关。所示代码的逻辑已损坏。

显示的代码似乎遵循将单个逻辑算法分解为多个部分并将它们分散到很远很广的设计模式。这使得理解它在做什么变得更加困难。所以让我们重写一点,使逻辑更清晰。在 primeLog 中,让我们改为这样做:

            cv_log.wait(ul, []{ return _var_log; });
            _var_log = false;

现在更清楚的是,这会等待 _var_log 被设置,然后再继续其愉快的方式。一旦它被立即重置。

随后的代码从文件中准确地读取一个数字,然后循环回到此处。因此,primeLog 的主循环在循环的每次迭代中始终只处理一个数字。

现在的问题很容易看出,一旦我们转到另一边,并做同样的澄清:

        std::unique_lock<std::mutex> ulm(mtx);
        cv.wait(ulm,[]){ return _var; });

        // Code that generates one number and writes it to the file

        _var_log = true;
        cv_log.notify_one();

一旦 _var 设置为 true,它就保持为 true。这个循环开始全速运行,不断迭代。在循环的每次迭代中,它盲目地将 _var_log 设置为 true 并向另一个线程的条件变量发出信号。

C++ 执行线程彼此完全独立,除非它们以某种方式显式同步。

在另一个执行线程苏醒并决定从文件中读取第一个数字之前,没有什么可以阻止此循环全速运行,遍历其整个数字范围。它会这样做,然后返回并等待它的条件变量再次发出信号,以获取下一个数字。它对第 2 号的希望和梦想将无法满足。

在生成线程循环的每次迭代中,另一个执行线程的条件变量都会收到信号。

条件变量不是信号量。如果在发出信号时没有任何东西在等待条件变量——那就太糟糕了。当某个执行线程决定等待条件变量时,它可能会也可能不会立即被唤醒。

这两个执行线程中的一个依赖于它在其循环的每次迭代中接收条件变量通知。

其他执行线程中的逻辑无法实现此保证。这可能不是唯一的缺陷,可能还有其他缺陷,有待进一步分析,这只是最明显的逻辑缺陷。

关于c++ - I/O 流真的是线程安全的吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72858061/

相关文章:

c++ - 比较两个字符串后陷入无限循环

c++ - 这个异步技巧会起作用还是当我访问它时状态会悬空?

c++ - 以参数作为派生类的绑定(bind)函数

c - 在多个线程中共享一个结构体

c++ - 如何从 openMP (C++) 中的线程生成子线程

c++ - c++中唯一标识任意对象

C++:通用 "call-functions-f-followed-by-g"方法?

c++ - 如何纠正对运算符优先级的错误假设以消除代码的副作用?

c++ - 控制台输入中基于向上箭头的命令历史记录 (C++)

C++ : Restrict method access in derived class