Did you already hear a programmer ask this question:
Hey ! Why are we using a special library for logging ? We could use cout/printf instead and implement our personal header on it.
This is not a stupid question. I saw many young programmer ignoring why a special library is used to log in a multi threaded application.
It is perfect to introduce to you one of the fundamentals concerning the multi-threading in C/C++.
You see, the standard outputs as "cout" and "printf" are competitive between threads. Although C++11 standard gives some guarantees for the completeness.
However the performance and format are compromised. We can get this kind of issue with a simple test. Let's go with the following code:
#include <iostream> #include <thread> class ThreadManager { public: ThreadManager(); ~ThreadManager(); void createThread(size_t number); static void myThreadFunction(size_t assignedNumber); private: std::thread threadObject; size_t threadNumber; };
#include <ThreadManager.h> static const size_t NUMBER_OF_THREAD_CREATED = 5; static const size_t NUMBER_OF_LOGS_PER_THREADS = 10; ThreadManager::ThreadManager() : threadNumber(-1) { } ThreadManager::~ThreadManager() { threadObject.join(); } void ThreadManager::createThread(size_t number) { threadNumber = number; threadObject = std::thread(myThreadFunction, number); } void ThreadManager::myThreadFunction(size_t assignedNumber) { for (size_t i = 0; i < NUMBER_OF_LOGS_PER_THREADS; ++i) { std::cout << "Thread created: " << assignedNumber << " - Log number " << i << std::endl; } } int main() { // Initialize threads ThreadManager threadManagerList[NUMBER_OF_THREAD_CREATED]; std::cout << "Program running !" << std::endl; for(size_t i = 0; i < NUMBER_OF_THREAD_CREATED; ++i) { threadManagerList[i].createThread(i); } std::cout << "All thread created !" << std::endl; return 0; }
This code is pretty simple: The main will create 5 threads with 10 logs per threads... Let see a piece of the output:
Here we are exactly in the main issue: The format is not complete - Prints between thread 0 and thread 1 are melted.
There are several way to resolve this:
Simple resolution with "glog"
I'll feature to you a simple resolution of this with a third party library used for logging, glog:
https://github.com/google/glog
Now, let's modify this code with a basic implementation of Glog:
ThreadManager::ThreadManager() : threadNumber(-1) { } ThreadManager::~ThreadManager() { threadObject.join(); } void ThreadManager::createThread(size_t number) { threadNumber = number; threadObject = std::thread(myThreadFunction, number); } void ThreadManager::myThreadFunction(size_t assignedNumber) { for (size_t i = 0; i < NUMBER_OF_LOGS_PER_THREADS; ++i) { LOG(INFO) << "Thread created: " << assignedNumber << " - Log number " << i; } } int main() { // Initialize glog google::InitGoogleLogging(""); // Initialize threads ThreadManager threadManagerList[NUMBER_OF_THREAD_CREATED]; LOG(INFO) << "Program running !"; for(size_t i = 0; i < NUMBER_OF_THREAD_CREATED; ++i) { threadManagerList[i].createThread(i); } LOG(INFO) << "All thread created !"; return 0; }
Now, let's take a look to this new output (with the environment variable "GLOG_logtostderr=1" set) !
It looks good now ! Thanks to this kind of Library, the logger is now thread-safe and able to support new functionalities easily like:
- Timestamp
- Filename
- Line
- Thread number
For multithreading purpose, those kind of libraries are useful for logging or analysis.