Task : There are many streams (say, 50) in which the function of writing to the data file is called (there is only one file for writing). How to make so that streams do not interfere with each other, and at the same time it was possible to view the file through the conductor?

Those. How to implement a thread-safe write to a file, if it closes after each write to it?

  • one
    It is very similar that you invent a logger. - KoVadim
  • four
    The correct option is to take ready. Bicycle option - or use mutex. There are many more options. - KoVadim
  • one
    If this is not a lab, take log4cxx, and do not reinvent the wheel. If this is a lab, write it yourself. - VladD
  • one
    I once took drdobbs.com/cpp/a-lightweight-logger-for-c/240147505?pgno=1 and adjusted the pens for my task. Log4cxx also seems to be good, but I did not use it, I can not say how comfortable it is. - KoVadim
  • one
    As an option: streams are written not directly to a file, but to a queue (thread safe). A separate logger stream raises data from this queue and writes to the file. The file itself must be shared for reading so that it can be viewed by other programs. - Alexander Petrov

2 answers 2

For thread-safe writing to a file, you can use synchronization objects — mutexes and events, which at a time can provide access to the file to only one stream. That is, each stream, before writing to the file, checks the state of the synchronization object and, if it is free, occupies it and writes the file, and then releases it. If he is busy, he enters the queue and falls asleep until this object is released. But it may not fall asleep but do other useful work. To create synchronization objects, there are WinAPI CreateEvent , CreateMutex and SetEvent . To wait for an object - Select and WaitForSingleObjects .

    Thanks to the tip from @Alexander Petrov from the comments to the question, this is the result that personally suits me personally in terms of functionality. Below I will give an example, suddenly someone will need something similar.

    Description: The logger class was created to output any information from different classes used in the project to different files. Therefore, several files are opened (for convenience, a date is still written before the file name). The class instance is spinning in a separate thread.

    File "Logger.h" :

     #pragma once #include "stdafx.h" extern std::string PathEXE; // где хранится файл class Logger { public: Logger(){ std::cout << "\nLog class created. "+PathEXE; OpenLogFiles(); }// открывает файлы. void AddToLog(unsigned type, std::string text); // функция, вызываемая извне, для добавления в буффер, соответствующий желаемому файлу void Run();// функция, проверяющая постоянно буфферы на наличие данных private: //method void OpenLogFiles(); void output(std::string text, int i);// функция, печатающая в файл std::string pop(int i, int j);// функция, выбирающая что печатать //data std::vector<std::vector<std::string>> buffer; // вектор буфферов. Размер - кол-во файлов std::vector<HANDLE> file; // хэндлеры файлов std::vector<DWORD> filesize; // позиции в файлах std::mutex mutex_; //мютекс }; 

    File "Logger.cpp" :

     #include "stdafx.h" #include "Logger.h" void Logger::OpenLogFiles() { using namespace boost; gregorian::date TODAY = gregorian::day_clock::local_day(); std::string date = to_string(TODAY.day().as_number()) + "-" + to_string(TODAY.month().as_number()) + "-" + to_string(TODAY.year()) + "_"; std::string tempPath = PathEXE + date; std::vector<std::string> path; path.push_back(tempPath + "LOGS_1.txt"); path.push_back(tempPath + "LOGS_2.txt"); path.push_back(tempPath + "LOGS_3.txt"); path.push_back(tempPath + "LOGS_4.txt"); path.push_back(tempPath + "LOGS_5.txt"); path.push_back(tempPath + "LOGS_6.txt"); path.push_back(tempPath + "LOGS_0.txt"); for (unsigned i = 0; i < path.size(); ++i) { file.push_back(CreateFileA( path[i].c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL) ); // открываем файл с флагом shared_reading if (file[i] == INVALID_HANDLE_VALUE) // проверка на правильность { printf("ERROR %x \n", GetLastError()); std::cout << "\nRELOAD NEEDED. LOG ERROR";//messagebox } filesize.push_back(0); // заполняем вектор позиций time_t rawtime; struct tm * t; time(&rawtime); t = localtime(&rawtime); std::string time = ""; std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now(); time += to_string(t->tm_year + 1900) + "." + to_string(t->tm_mon + 1) + "." + to_string(t->tm_mday) + "/" + to_string(t->tm_hour) + ":" + to_string(t->tm_min) + ":" + to_string(t->tm_sec) + "/" + to_string(t1.time_since_epoch().count() % 1000); output("Logs created on: "+time, i);// сразу пишем в файл, когда создан std::vector<std::string> buf;//создаем буффер buffer.push_back(buf); } } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void Logger::Run() { while (true) { std::this_thread::sleep_for(std::chrono::milliseconds(1));//для уменьшения загрузки ЦП for (unsigned i = 0; i < buffer.size(); ++i) { int size = buffer[i].size(); if (size>0) { for (int k = 0; k < size; ++k) { output(pop(i, k), i); } } } } } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void Logger::output(std::string text, int i) { WriteFile(file[i], (text).c_str(), (text).size(), &filesize[i], NULL);// пишем в файл filesize[i] += (text).size();// сдвигаем указатель } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// std::string Logger::pop(int i, int j) { std::string text; text = buffer[i][j]; // читаем начало буффера mutex_.lock(); buffer[i].erase(buffer[i].begin());// elfkztv yfxfkj ,eaathf mutex_.unlock(); text += " s=" + boost::to_string(buffer[i].size()); // добавляем информацию, сколько еще в буффере (у меня максимальный размер был 1 через час работы) return text; } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void Logger::AddToLog(unsigned type, std::string text) { if (buffer.size() < type){ std::cout << "size!"; return; } // проверка на ошибку размера mutex_.lock(); buffer[type].push_back(text);// добавляем в конец буффера mutex_.unlock(); } 

    File "main.cpp" :

     #include "stdafx.h" #include "Logger.h" Logger; std::string PathEXE = "C:\\Projects\\tests\\Release\\"; // задаем путь для файлов auto logqqq=Logger();// создаем экземпляр класса void Runner() { logqqq.Run(); } void helper(std::string text, int i, int timee) { while (true) { logqqq.AddToLog(i, "\n|text here|\n"); std::this_thread::sleep_for(std::chrono::milliseconds(timee)); } } int main() { std::cout << "\nstart"; std::thread my_thread(&Runner);// запускаем логгер std::this_thread::sleep_for(std::chrono::milliseconds(100));// ждем просто так //запуски потоков, кидающих что-то в буффер: //параметр 1 - текст(сейчас не активен), параметр 2 - файл, параметр 3 - частота "вброса" в миллисекундах. std::thread my1(&helper, "\ntext1", 0, 1); std::thread my2(&helper, "\ntext2", 1, 2); std::thread my3(&helper, "\ntext3", 2, 3); std::thread my4(&helper, "\ntext4", 3, 4); std::thread my5(&helper, "\ntext5", 4, 5); std::thread my6(&helper, "\ntext6", 5, 6); std::thread my7(&helper, "\ntext7", 6, 7); cout << "\nall threads started!"; my1.join(); return 0; } 

    PS Maybe not as optimally as possible, but I did not notice any problems with the speed of work. Yes, and in the amount of memory spent, too.