Multithreading Archives - Grape Programmer https://grapeprogrammer.com Axel Fortun's blog - Passion for Software! Sun, 27 Sep 2020 10:19:33 +0000 en-US hourly 1 https://wordpress.org/?v=6.8 https://grapeprogrammer.com/wp-content/uploads/2021/07/cropped-GrapeProgrammerFavIcon_Min-1.png Multithreading Archives - Grape Programmer https://grapeprogrammer.com 32 32 How to create a thread with C++11 https://grapeprogrammer.com/how-to-create-thread-c11/ Mon, 28 Sep 2020 06:00:00 +0000 https://grapeprogrammer.com/?p=4488

Do you know how to create a thread in C++11? During this post, we will review together the different manner to use the class thread.

We will concentrate on the first purpose: The thread creation!

The Thread library - Create a thread in C++11.

The thread creation is cooked by the constructor of std::thread. To use this library, you should gather two conditions:

1 - The inclusion of the “thread” header.

C++ - Thread requirement

#include <thread>

2 - "std::thread" uses directly the POSIX thread implementation. Then, you must link "pthread".

Bash - How to build with thread - GCC example

g++ -lpthread myCode.cpp

To create a single thread C++11, the std::thread” object constructor needs two parameters:

  • fn - A callable object. It can be a pointer to a function or class member. The return value of this callable object is ignored.
  • args… (optional) - The list of arguments.

If you remember my post about “invoke and apply in C++17”, the callable object is similar to invoke!
Now, let's review the different manners to use the thread class!

Function without arguments.

In C++11, the creation of a thread is pretty simple. You just need a function that returns "void". Unfortunately, You cannot get the returned value. Let's see this basic example below:

C++ - Thread creation with a function

#include <iostream>
#include <thread>

void function_threaded()
{
    std::cout << "(THREAD) Hey, I am thread!" << std::endl;
}

int main()
{
    std::cout << "(MAIN) Hey, I am main!" << std::endl;
    std::thread thread_object(function_threaded);
    // Join the thread. Then, we will wait for the thread termination
    thread_object.join();
    std::cout << "(MAIN) Thread ended!" << std::endl;
    return 0;
}

Here, the object "std::thread" immediately creates and initializes the thread. Here, the thread calls the function "function_threaded".

Output

(MAIN) Hey, I am main!
(THREAD) Hey, I am thread!
(MAIN) Thread ended!

Function with arguments.

Sometimes, the passage of arguments is necessary.

C++ - Thread creation with a function plus arguments

#include <iostream>
#include <thread>

void function_threaded(int a)
{
    std::cout << "(THREAD) Hey, I am thread!" << std::endl;
    std::cout <<"(THREAD) a = " << a << std::endl;
}

int main()
{
    std::cout << "(MAIN) Hey, I am main!" << std::endl;
    std::thread thread_object(function_threaded, 42);
    // Join the thread. Then, we will wait for the thread termination
    thread_object.join();
    std::cout << "(MAIN) Thread ended!" << std::endl;
    return 0;
}

As you see, the example is pretty similar to an initialization without arguments. Compared to the "pthread" implementation, the usage of std::thread simplifies this kind of scenario!

Output

(MAIN) Hey, I am main!
(THREAD) Hey, I am thread!
(THREAD) a = 42
(MAIN) Thread ended!

Class members.

You can initialize a thread by calling a class member:

C++ - Thread creation with a class member

#include <iostream>
#include <thread>

class rectangle
{
    public:
        rectangle(unsigned int height, unsigned int width)
            : height(height), width(width)
            {};
        virtual ~rectangle() {};
        void print_area();
    private:
        unsigned int height;
        unsigned int width;
};

void rectangle::print_area()
{
    unsigned long area = height * width;
    std::cout << "(THREAD) My area is equal to " << area << std::endl;
}

int main()
{
    std::cout << "(MAIN) Hey, I am main!" << std::endl;
    rectangle golden_rectangle(16, 9);
    std::thread thread_object(&rectangle::print_area, golden_rectangle);
    
    thread_object.join();
    std::cout << "(MAIN) Thread ended!" << std::endl;
    return 0;
}

Here a bit more complex. You should at first define the member called. Then, you pass the class object to use. Here, the rectangle area is calculated in a separate thread.

Output

(MAIN) Hey, I am main!
(THREAD) My area is equal to 144
(MAIN) Thread ended!

Lambda expressions.

The lambda expressions can be used for your initialization.

C++ - Thread creation with a lambda expression

#include <iostream>
#include <thread>

int main()
{
    std::cout << "(MAIN) Hey, I am main!" << std::endl;

    std::thread thread_object([](){
        std::cout << "(THREAD) I am the thread!" << std::endl;
    };

    thread_object.join();
    std::cout << "(MAIN) Thread ended!" << std::endl;

    return 0;
}

Output

(MAIN) Hey, I am main!
(THREAD) I am the thread!
(MAIN) Thread ended!

The Thread library - Synchronization and ID.

The object "std::thread" contains two useful members for debug and synchronization:

"join" - Synchronize your thread termination.

Join is a public member of the Thread class. The goal of "join" is to stop your caller until that the thread contained in the object is terminated. You can see the usage of "join" in all my examples.

Remember, as I explained before in this post, "join" is extremely important to terminate properly your thread!

"get_id" - Get your thread ID.

As join, get_id is a public member of the Thread class. Pretty useful for debug, get_id retrieves the thread number.

C++ - Thread ID

#include <iostream>
#include <thread>

void function_threaded()
{
    int j = 0;

    for (int i = 0; i < 10000000; ++i)
    {
        j = j + i;
    }
    std::cout << "(THREAD) I am a thread with the ID: " << std::this_thread::get_id() << std::endl;

}

int main()
{
    std::cout << "(MAIN) Hey, I am main!" << std::endl;
    std::thread thread_object(function_threaded);
    std::cout << "(MAIN) I created a thread with the ID: " << thread_object.get_id() << std::endl;
    // Join the thread. Then, we will wait for the thread termination
    thread_object.join();
    std::cout << "(MAIN) Thread ended!" << std::endl;
    return 0;
}

In this example, I create a thread with the function "function_threaded". With "thread_object.get_id()", the "thread creator" can retrieve the "Thread ID".
To get the "Thread ID" inside my thread, I can use "std::this_thread::get_id()".

Output

(MAIN) Hey, I am main!
(MAIN) I created a thread with the ID: 139815045957376
(THREAD) I am a thread with the ID: 139815045957376
(MAIN) Thread ended!

Advanced usage - Create multiple workers in an array.

Sometimes, you need to create a cluster of workers (placed in vector, map, or array!). You will see with C++11, the creation of those kinds of arrays are pretty simple and powerful. Let's review together two manners to create those workers:

Workers inside a vector with lambda.

C++ - Multiple thread creation with a vector, lambda used

#include <iostream>
#include <vector>
#include <thread>

void threaded_function(int i)
{
    std::cout << "(THREAD) I am thread number " << i << std::endl;
    int j = 1;
    for (int i = 0; i < 100000; ++i)
    {
        j = i*j;
    }
    std::cout << "(THREAD) thread number " << i << " terminated" << std::endl;
}

int main()
{
    std::cout << "(MAIN) Hey, I am main!" << std::endl;
    std::vector<std::thread> vector_of_threads;

    for (int i = 0; i < 5; ++i) {
        vector_of_threads.push_back(std::thread([i](){
            threaded_function(i);
       }));
    }

    // Wait for all thread termination
    for (auto& x: vector_of_threads)
    {
        x.join();
    }

    std::cout << "(MAIN) Exiting" << std::endl;

    return 0;
}

It is a simple extension of my example with lambda expressions and argument usage! I created 5 workers with a different identifier for each one.
Then, the "std::thread" object is directly pushed in my vector. Thanks to this vector, I can "join" those workers.

Output

(MAIN) Hey, I am main!
(THREAD) I am thread number 0
(THREAD) I am thread number 1
(THREAD) I am thread number 2
(THREAD) I am thread number 3
(THREAD) thread number 0 terminated
(THREAD) I am thread number 4
(THREAD) thread number 1 terminated
(THREAD) thread number 2 terminated
(THREAD) thread number 3 terminated
(THREAD) thread number 4 terminated
(MAIN) Exiting

Workers inside a vector without lambda.

Of course, you can also create multiple threads without lambda.
Here, a similar example based the method "emplace_back" from "std::vector" to create your cluster.

C++ - Multiple thread creation with a vector

#include <iostream>
#include <vector>
#include <thread>

void multiplication_threaded(int a, int b)
{
    long multiplication_result = a * b;
    std::cout << "(THREAD) Hey, I am thread! My Result is = " << multiplication_result << std::endl;
}

int main()
{
    std::cout << "(MAIN) Hey, I am main!" << std::endl;
    std::vector<std::thread> thread_list;

    for (int i = 0; i < 5; ++i)
    {
        thread_list.emplace_back(multiplication_threaded, i, 2);
    }

    // Wait for all thread termination
    for (auto& x: thread_list)
    {
        x.join();
    }

    return 0;
}

Output

(MAIN) Hey, I am main!
(THREAD) Hey, I am thread! My Result is = 0
(THREAD) Hey, I am thread! My Result is = 2
(THREAD) Hey, I am thread! My Result is = 4
(THREAD) Hey, I am thread! My Result is = 6
(THREAD) Hey, I am thread! My Result is = 8
]]>
pthread_cond_wait usage. How to correctly handle signals. https://grapeprogrammer.com/how-to-use-pthread-cond-wait/ Tue, 01 Sep 2020 16:34:43 +0000 https://grapeprogrammer.com/?p=3555

Did you already used directly the pthread library? If it is the case, you had already used the "pthread_cond_wait" function! However, did you correctly used it?

What is pthread_cond_wait?

"pthread_cond_wait" or "wait on a condition" is a function from the pthread library.

This function is ideal to synchronize two threads. Specially useful for initialization sequence, to notify when a message is received, etc...
"pthread_cond_wait" needs to be coupled with a mutex to work correctly. This mutex will be released on entering into our function:

These functions atomically release mutex and cause the calling thread to block on the condition variable cond; atomically here means "atomically with respect to access by another thread to the mutex and then the condition variable". That is, if another thread is able to acquire the mutex after the about-to-block thread has released it, then a subsequent call to pthread_cond_broadcast() or pthread_cond_signal() in that thread shall behave as if it were issued after the about-to-block thread has blocked.

Don't underestimate the usage of this mutex. It is useful for the unexpected wakeups of pthread_cond_wait :

pthread_cond_wait - Spurious wakeups.

At first sight, it looks easy to use "pthread_cond_wait"! We combine this with a single mutex to synchronize two thread and let's roll.
However, did you know that the usage of a Boolean is recommended?

When using condition variables there is always a Boolean predicate involving shared variables associated with each condition wait that is true if the thread should proceed. Spurious wakeups from the pthread_cond_timedwait() or pthread_cond_wait() functions may occur. Since the return frompthread_cond_timedwait() or pthread_cond_wait() does not imply anything about the value of this predicate, the predicate should be re-evaluated upon such return.

As you see, the function is not safety at 100%.
Indeed, Some spurious wakeups can occurs during the process. This case can triggers terrible consequences: Corruptions, unexpected messages, crashes...

A clean solution to use pthread_cond_wait.

For this post, I will feature to you a simple and clean solution to protect pthread_cond_wait at 100%! And this thanks to the combination of a Mutex, Boolean and Condition variable.

How to use correctly pthread_cond_wait:

pthread_cond_t g_condition_wait = PTHREAD_COND_INITIALIZER;
pthread_mutex_t g_mutex_used = PTHREAD_MUTEX_INITIALIZER;
int g_is_notified = 0;


void* function_thread()
{
    pthread_mutex_lock(&g_mutex_used);

    long_initialization_3_phase("THREAD");

    g_is_notified = 1;
    pthread_cond_signal(&g_condition_wait);
    printf("[THREAD] Initalization completed, notifying main!\n");
    pthread_mutex_unlock(&g_mutex_used);

    return NULL;
}

int main()
{
    pthread_t my_thread;

    pthread_create(&my_thread, NULL, function_thread, NULL);

    pthread_mutex_lock(&g_mutex_used);
		
    while (g_is_notified == 0)
    {
        // Wait for signal. g_is_notified is protected by the Mutex.
        pthread_cond_wait(&g_condition_wait, &g_mutex_used);
    }

    printf("[MAIN] Notified! Starting initialization\n");

    long_initialization_3_phase("MAIN");

    pthread_mutex_unlock(& g_mutex_used);
    pthread_join(my_thread, NULL);


    return 0;
}

A quick explanation... Let's focus on the main function.

Here, "pthread_cond_wait" is surrounded by a while loop conditioned on the boolean "g_is_notified". Thanks to the "g_mutex_used"  lock, this Boolean is fully protected.
As we saw below, the mutex is unlocked during the "pthread_cond_wait" call. It allows an assignment of "g_is_notified" by the second thread represented by the function "function_thread". Then, three cases:

  • Nominal scenario : The boolean "g_is_notified" is set. The signal call will unblock our "pthread_cond_wait". The loop is correctly exited.
  • Spurious WakeUp: Great! In this case, we return directly to "pthread_cond_wait"!
  • Boolean modified before entering in the loop: Can happens following to your configuration... In that case, the condition status is "memorized" by the Boolean. We never enter in "pthread_cond_wait".

As you see, it covers all cases and risks. Here, the thread is correctly protected and initialized!
In all cases, our Boolean is fully protected by the mutex "g_mutex_used" taken in the main and the second thread.

Now, let's run the code!

[THREAD] Initialization phase 0 done
[THREAD] Initialization phase 1 done
[THREAD] Initialization phase 2 done
[THREAD] Initalization completed, notifying main!
[MAIN] Notified! Starting initialization
[MAIN] Initialization phase 0 done
[MAIN] Initialization phase 1 done
[MAIN] Initialization phase 2 done
]]>
Thread creation trap in C++- Be careful! https://grapeprogrammer.com/thread-creation-trap-in-c/ Sun, 09 Aug 2020 08:21:51 +0000 https://grapeprogrammer.com/?p=1587

Do you know how to avoid thread creation trap in C++? This quick post is a parallel with one of my previous ones, the context switch (Don't hesitate to check it!)

What happens when a thread is created in C++?

A thread creation in C++ is not free. To avoid details and give a quick description, we can resume a thread creation to this:

  1. Initialization of the thread - In the stack, instructions (and variables) are created. Memory consumed.
  2. Starting of the thread - Potentially launch of the context switch. Thanks to std::thread (C++11), the thread execution is launched at constructor time.

Finally, all those instructions consume CPU time. In fact, Multiple threads creation in your application can cost heavy, specially for the next reasons...

Thread creation trap in C++ - Synchronization

Few years ago, during my job, my team received a proposition to migrate a piece of single threaded code to a multithreaded one. Great, the management and product owners was happy to hear that, let's develop now!
It was not really a good idea...

Indeed, we jumped to a new kind of behavior without any design modification. The consequences was terrible:

  • Deadlocks.
  • Race condition - Variables was not correctly protected.
  • Effects on IUI.

Despite that the application was more reactive, bugs was harder to detect (and can effect other component due to instability). Finally, a design modification had been necessary.

Thread creation trap in C++ - Context switch

A multithreaded application is not necessary quicker than a single threaded one. Actually, I already described this in one of my previous post:

July 18, 2020

Do you know what is the context switch? Hey, I am Axel from Grape Programmer. Today, I want to introduce to you some basis about System Programming in multithreaded environment.The purpose of Context SwitchThe context switch is a procedure done by a computer CPU. It allows switching from one single task/process to another one. This procedure

To summarize, each thread can trigger a mechanism named "Context Switch". Each switching of context is a CPU cost more or less important.

Indeed, migrating a single threaded application to a multithreaded one will imply the "Context Switch". This context switch can damage the performances of your application.

To conclude, each thread creation should be evaluated properly. It requires:

  • Design review, thread synchronization - Semaphore, Mutex, Signals...
  • Variable protection - Mutex
  • Performance analysis - Context Switch impact.
]]>
What is Context switch https://grapeprogrammer.com/what-is-context-switch/ Sat, 18 Jul 2020 17:59:23 +0000 https://grapeprogrammer.com/?p=1277

Do you know what is the context switch?

Hey, I am Axel from Grape Programmer. Today, I want to introduce to you some basis about System Programming in multithreaded environment.

The purpose of Context Switch

The context switch is a procedure done by a computer CPU. It allows switching from one single task/process to another one. This procedure helps to avoid conflicts between those tasks.

Otherwise, The word "context" concerns all the data stores and used to follow correctly a process. It means that values in registry, like process instructions, are stored or redeployed in registry.

When the context switch is called? It can be called by:

  • Multithreading - Following the conditions (Like mutexes, conditions...), the context switch can be called to switch from one thread to another one.
  • Interruption - Specifically used to interrupt CPU usage in order to access data in hard drive.
  • Switch between user and kernel mode - This is more called a "mode switch". Here, to run a specific kernel routine (like system call, signals, networking...) it could require a process state saving.

What is the cost of a Context Switch?

A context switch cost can be expensive in terms of time execution.
Indeed, the fact of saving process state or restoring a process state is not free! Of course, the cost of switching depends on your hardware architecture.

It is not rare to see a multithreaded application which is slower than a single-threaded one. In general, this kind of issue is due to design purpose (like tentative to migrate a single-thread application to a multithreaded one... Which is rarely a good idea). Due to multiple thread synchronization, the number of process switches are excessive, slowing down the execution time.

Quick word, the case of cout/printf

In my very first post, I talked about the usage of cout/printf:

I have one remark to complete my first post:
Mostly, A simple"printf"/"cout" call can trigger a context switch. Indeed, if the I / O stream is already locked by another thread, we should wait for the availability of I / O stream. Then, a context saving. So, a future switching.

For your multithreaded application, my recommendation is to load a logger library like glog or spdlog!

]]>
Thread Destruction – Join is your friend! https://grapeprogrammer.com/thread-destruction-join-is-your-friend/ Fri, 12 Jun 2020 16:25:27 +0000 https://grapeprogrammer.com/?p=429

The strategy of thread ending (with a join for instance) is primordial for your multithreaded program.

​Sometimes, on ​​aged programs with large history, I​ had seen threads without join or/and any kind of destruction strategy.

A thread without any destruction strategy is extremely dangerous and can trigger issues that are painful to analyze! The consequences of threads without join are easy to ​show, let see this simple code:

#include <iostream>
#include <thread>

void threadFunction()
{
    std::cout << "(THREAD) Hey, I am thread!" << std::endl;
}

int main()
{
    std::cout << "(THREAD) Hey, I am main!" << std::endl;
    std::thread threadCreated(threadFunction);
    return 0;
}

​Let see ​the output...

(THREAD) Hey, I am main!
(THREAD) Hey, I am thread!
terminate called without an active exception
Aborted (core dumped)

​Here my application crashed. Why?

The response is ​that we exited the program before that "threadFunction" finished. A simple join added resolve this issue:

#include <iostream>
#include <thread>

void threadFunction()
{
    std::cout << "(THREAD) Hey, I am thread!" << std::endl;
}

int main()
{
    std::cout << "(THREAD) Hey, I am main!" << std::endl;
    std::thread threadCreated(threadFunction);
    threadCreated.join();
    return 0;
}
(THREAD) Hey, I am main!
(THREAD) Hey, I am thread!

​Here the main function waits for the thread termination before to exit the program. Now I am exiting properly my program.

​Be careful


​I want to keep your attention here. In this example, the consequences ​was easy to analyze and not dramatic.


Now, let's imagine this kind of issue in a large program. Imagine that a super class generates a thread.

What happens if this class is destroyed without join on the concerned thread?


There is an undefined behavior ​(in the best case, a crash during testing).

​A thread not correctly managed can, in the long term, trigger those kind of ​behaviors:

​Example of risks ​if a thread is not correctly exited

1.

​Nothing special, the thread exit ​by himself. However, if the design is not review, this case is dangerous and can ​evolve to the 2nd or worst, the 3rd case.

2.

​Crash... During the test, this is the best case for me. it is ideal to apply an action to this thread and review the design.

3.

​Memory modification, corruption... The worst one: Vicious, extremely dangerous and hard to detect.

​During my carrier, I already ​saw ​the third case:

A crash (fortunately) occurred in the program, nothing special on the line when the crash happens. So, we started to analyze and debug the code. This ​​debug ​took an entire week.

Finally, it was a detached thread which manipulated object supposed to be destroyed.

​The resolution ​of those kind of issue are not easy to apply(Risk of deadlock, bad memory manipulation...). Fortunately, after few tentative, the risk of deadlock was limited and the bug had been fixed!

​Always mastery your thread ​design

​This last section is a simple message:

​Always mastery your threads

​A thread without any strategy of initialization and destruction can have terrible consequences on long term.


  • A thread creation should not be done without design ​specification.
  • A single thread application design​ is not always compatible for multithreading.
  • A thread not correctly designed can manipulate destroyed objects.

A thread should always be destroyed properly. With or without join, it should​ be correctly designed to avoid surprises.

​The multithreading is a ​wonderful tool, thanks to this, your application can be really reactive!

However, as I featured here, the question of multithreading in C++ ​should be taken with great caution..

​In any kind of project, we should ​always ​be able to predict the threads behavior, in any cases and any states.

I hope that I ​​​was not too "judgmental" during this post. You certainly already apply those principles in your project.

]]>
Why logging to standard output (cout/printf) is not a good idea for your multi threaded C/C++ program. https://grapeprogrammer.com/cout-printf-glog/ Thu, 21 May 2020 09:55:50 +0000 http:/?p=1

​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:

  • ​1 - Create a home made logger protected by Mutex (or MsgQueue usage).
  • ​2 - Use a third party library (for instance, log4cxx, boost or glog).
  • ​3 - For Real-Time environment, a hardware logger could be necessary.

​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:

  1. Timestamp
  2. Filename
  3. ​Line
  4. Thread number

For multithreading ​purpose, those kind of libraries are ​useful for logging or analysis.

]]>