Reputation: 83
I have a simple class in C++20 that logs data to a file line by line for Windows platform. To ensure thread safety, I use a static std::mutex
to serialize the write operations.
However, I encounter an issue: if I don't explicitly call flush
after each write while holding the lock, some lines are missing in the output file. When I call flush
, all lines are correctly written to the file.
Here’s a simplified version of the code:
class Logger {
public:
void Log(const std::string& message) {
std::lock_guard lock(mtx_);
log_file_ << std::format("{}\n", message);
// log_file.flush(); // Uncommenting this works fine
}
private:
static std::mutex mtx_;
std::ofstream log_file_{ "log.txt", std::ios::app };
};
std::mutex Logger::mtx_;
int main()
{
std::vector<std::future<void>> futures;
// Launch the function asynchronously
for (int i = 0; i < 25; ++i) {
futures.push_back(std::async(std::launch::async, []() {
Logger logger;
logger.Log("Test message"); }));
}
// Wait for all tasks to complete
for (auto& future : futures) {
future.get();
}
}
Why does this happen? Why doesn't the operating system automatically handle flushing the output when it's serialized using a mutex
, without explicitly calling flush
?
Is this expected behavior for file streams in C++?
Upvotes: 2
Views: 152
Reputation: 4808
In your example, every thread creates a new Logger
instance. Each instance has its own stream, which means each instance also has its own internal buffer. You do not synchronize these buffers.
One solution is to use a single shared stream. While broken messages (e.g., "this is a te"
followed by "this is a st"
) could occur in your example, this won't happen here because there is only one buffer. Note that depending on your platform and compiler, it might be necessary to define _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR
.
A possible implementation:
#include <fstream>
#include <mutex>
#include <vector>
#include <thread>
class logger_t
{
public:
static logger_t& instance()
{
static logger_t inst; // thread safe since c++11
return inst;
}
void message(const std::string m)
{
std::lock_guard lock(mutex_);
os_ << m;
}
protected:
logger_t() = default;
~logger_t() = default;
std::mutex mutex_;
std::ofstream os_{"c:/temp/log.txt", std::ios::app};
};
void f()
{
logger_t::instance().message( "this is a test " );
}
int main()
{
std::vector<std::thread> threads;
for (int i = 0; i < 123; ++i)
threads.emplace_back(f);
for (auto& t : threads)
t.join();
}
Upvotes: 3