Professional C__ - Marc Gregoire [399]
Logger::Logger()
{
// Start background thread.
mThread = thread{&Logger::processEntries, this};
}
void Logger::log(const std::string& entry)
{
// Lock mutex and add entry to the queue.
unique_lock mQueue.push(entry); // Notify condition variable to wake up thread. mCondVar.notify_all(); } void Logger::processEntries() { // Open log file. ofstream ofs("log.txt"); if (ofs.fail()) { cerr << "Failed to open logfile." << endl; return; } // Start processing loop. unique_lock while (true) { // Wait for a notification. mCondVar.wait(lock); // Condition variable is notified, so something is in the queue. lock.unlock(); while (true) { lock.lock(); if (mQueue.empty()) { break; } else { ofs << mQueue.front() << endl; mQueue.pop(); } lock.unlock(); } } } Code snippet from logger\Version1\Logger.cpp This Logger class can be tested by using the following test code, which launches a number of background threads, all logging a few messages to the same Logger instance: void logSomeMessages(int id, Logger& logger) { for (int i = 0; i < 10; ++i) { stringstream ss; ss << "Log entry " << i << " from thread " << id; logger.log(ss.str()); } } int main() { Logger logger; vector // Create a few threads all working with the same Logger instance. for (int i = 0; i < 10; ++i) { threads.push_back(thread{logSomeMessages, i, ref(logger)}); } // Wait for all threads to finish. for (auto& t : threads) { t.join(); } return 0; } Code snippet from logger\Version1\main.cpp If you build and run this naïve initial version, you will notice a couple of problems with it, especially when you run it on a multicore machine. The first problem is that the background Logger thread will be terminated abruptly when the main() function finishes. This means that messages still in the queue will not be written to the file on disk. Some run-time libraries will even issue an error or generate a crash dump when the background Logger thread is abruptly terminated. You need to add a mechanism to gracefully shut down the background thread and wait until the background thread is completely shut down before terminating the application itself. This can be done by adding a Boolean member variable to the class. The destructor of the Logger class will set this Boolean to true, notify the background thread, and wait until the thread is shut down. The notification will trigger the background thread to wake up, check this Boolean value and, if it’s true, write all messages in the queue to the file and terminate the processing loop. The new definition of the class is as follows: class Logger { public: // Starts a background thread writing log entries to a file. Logger(); // Gracefully shut down background thread. virtual ~Logger(); // Add log entry to the queue. void log(const std::string& entry); protected: void processEntries(); bool mExit; // Remaining of the class omitted for brevity }; Code snippet from logger\Version2\Logger.h The Logger constructor needs to initialize the mExit variable: Logger::Logger() : mExit(false) { // Start background thread. mThread = thread{&Logger::processEntries, this}; } Code snippet from logger\Version2\Logger.cpp The destructor sets the Boolean variable, wakes up the thread, and then waits until the thread is shut down: Logger::~Logger() { // Gracefully shut down the thread by setting mExit // to true and notifying the thread. mExit = true; // Notify condition variable to wake up thread. mCondVar.notify_all(); // Wait until thread is shut down. mThread.join(); } Code snippet from logger\Version2\Logger.cpp The processEntries() method needs to check this Boolean variable and terminate the processing loop when it’s true: void Logger::processEntries() { // Open log file. ofstream ofs("log.txt"); if (ofs.fail()) { cerr << "Failed to open logfile."