Fix the Bug in the Multithreaded Singleton Logger Implementation
Identify and fix the concurrency bug in a classic thread-safe Singleton Logger class in C++. The provided code attempts lazy initialization with double-checked locking but contains subtle issues that may cause race conditions or undefined behavior in a multithreaded environment.
Challenge prompt
The following C++ code implements a Singleton Logger intended to be thread-safe using double-checked locking. However, the current implementation can fail when accessed concurrently by multiple threads. Your task is to identify and fix the concurrency issues in this code so that the singleton instance is correctly initialized exactly once, without data races or undefined behavior. You must preserve lazy initialization and avoid any unnecessary locking after the instance is created.
Guidance
- • Review the double-checked locking pattern and understand why naive implementations can fail.
- • Check for memory visibility issues caused by improper usage of concurrency primitives.
- • Consider using std::atomic or C++11 features to safely publish the singleton instance.
Hints
- • The instance pointer needs to be atomic or protected to prevent reading torn or stale values.
- • Using std::call_once with a std::once_flag is a simpler and safer way to implement thread-safe initialization.
- • Without proper memory ordering or synchronization, other threads might see a partially constructed object.
Starter code
#include <iostream>
#include <mutex>
class Logger {
private:
static Logger* instance;
static std::mutex mtx;
Logger() { std::cout << "Logger initialized" << std::endl; }
~Logger() {}
public:
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
static Logger* getInstance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) {
instance = new Logger();
}
}
return instance;
}
void log(const std::string& message) {
std::cout << message << std::endl;
}
};
Logger* Logger::instance = nullptr;
std::mutex Logger::mtx;
int main() {
Logger* logger1 = Logger::getInstance();
logger1->log("First log message.");
Logger* logger2 = Logger::getInstance();
logger2->log("Second log message.");
std::cout << "Instances equal: " << (logger1 == logger2 ? "true" : "false") << std::endl;
return 0;
}
Expected output
Logger initialized First log message. Second log message. Instances equal: true
Core concepts
Challenge a Friend
Send this duel to someone else and see if they can solve it.