lundi 4 juillet 2022

Are I/O streams really thread-safe?

I wrote a program that writes random numbers to one file in the first thread, and another thread reads them from there and writes to another file those that are prime numbers. The third thread is needed to stop/start the work. I read that I/O threads are thread-safe. Since writing to a single shared resource is thread-safe, what could be the problem? Output: always correct record in numbers.log, sometimes no record in numbers_prime.log when there are prime numbers, sometimes they are all written.

#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;
}

Aucun commentaire:

Enregistrer un commentaire