Creating Threads in C++

This week seems to be flying by. It is already Wednesday.

On my last post I mentioned that I get up around 05:00 AM every day of the week. I prepare and have breakfast, shower and get dressed, and go down to work around 06:00 AM. By the time I go to sleep around 07:30 PM, I am pretty tiered.

So what happens if something comes up around 05:00 PM CST at work? The company I work for is in California. That implies that it is relatively early in the afternoon at 03:00 PM PST. If urgent I will take a look and then address it on the next day. This is because at 05:00 PM CST I am fried. Since I wake up at 05:00 AM CDT, which is considerably early on the west coast (03:00 AM PST), I have plenty of time (about 5 hours) to get things done before people starts arriving around 09:00 AM PST.

If I know I have to be working late afternoon, I get up later; that way I will not be so tried if something is schedule for mid to late afternoon in the office.

Similar things happen at home on weekends. For example, this Friday evening, my wife and I will be attending a reunion hosted by some friends in the neighborhood. I will plan to get up an hour or two later on Friday.

Lately I have been working with C++ on a project. The project is done and I am just running some tests to make sure all is well. I always spend some time tweaking and polishing the software before it is checked in.

In this post I will cover some basic operations with threads. Earlier today I was discussing the “await” and “promise” keywords in the C++ language. They appeared in one of the latest versions. We were discussing that Node.js and JavaScript support them and it is very simple to create a thread to perform some task (e.g., OCR) and have the main thread perform other tasks and then wait for the result to be presented to the main thread. I want to write a few simple posts which will lead to the use of wait and promise in C++.

Obviously for this post, I am using C++ and for an IDE I decided on Visual Studio 2019 Enterprise Edition on a Windows 10 computer.

main <<< Hello!!!
main <<< working ...
threadFunc <<< entering
threadFunc <<< delay: 7 value: 100
threadFunc <<< delay: 9 value: 103
main <<< done working
threadFunc <<< exiting
main <<< delay: 9 value: 100
main <<< Bye-bye!!!

The console screen capture shows a couple messages generated by the main program.

A tread is started which displays a couple variables that have been passed to it. The variables are incremented in the thread and probably used for something. We will see this shortly.

While the thread is execution, the main thread is performing some work. In practice “some work” is implemented in this case by sleeping.

At some point the main program completes its work and waits for the thread to exit.

After the thread exists a couple of variables are displayed. It seems that delay was updated in the thread and the main thread can see the changes. The value was also updated in the thread but the main thread was not able to see the changes.

//
// Main entry point.
//
int main()
{

    // **** welcome message ****
    cout << "main <<< Hello!!!\n";

    // **** call the thread function (without / with argument(s)) ****
    //thread t1(threadFunc);
    int delay   = 7;
    int value   = 100;
    //thread t1{ threadFunc, ref(delay) };
    thread t1(threadFunc, ref(delay), value);

    // **** replace the thread function with a lambda expression ****
    //thread t1{ [](int &delay, int value) {
    //thread t1{ [=]() {
    //thread t1{ [&]() {

    //    // **** increase the delay and value ****
    //    cout << "lambda <<< delay: " << delay << " value: " << value << endl;
    //    delay += 2;
    //    value += 3;
    //    cout << "lambda <<< delay: " << delay << " value: " << value << endl;

    //    // **** sleep for a few ****
    //    this_thread::sleep_for(chrono::seconds(delay));

    ////}, ref(delay), value};
    //}};

    // **** do some work ****
    cout << "main <<< working ...\n";
    this_thread::sleep_for(chrono::seconds(3));
    cout << "main <<< done working\n";

    // **** wait for the thread to complete ****
    t1.join();

    // **** display the delay ****
    cout << "main <<< delay: " << delay << " value: " << value << endl;

    // **** good bye message ****
    cout << "main <<< Bye-bye!!!\n";

    // **** ****
    return 0;
}

The main thread displays a message.

It then defines a couple variables and sets their values.

A thread is called using a function named threadFunc() and we are passing two variables. The first one is passed by reference, while the second one is passed by value.

Please ignore the commented out code. I will cover it shortly.

The main thread then does some work (sleeping). When the work is done, the main thread joins the t1 thread. We display the value of the two variables we passed to the thread and exit.

Note that the delay variable was incremented by the t1 thread. Since the variable was passed by reference, the value seen by the main thread matches the new value. The value variable is also incremented as indicated by the message in the t1 thread. When the thread completes and we display the contents of value, the value has not changes. This is because by default arguments are passed by value.

//
// This function implements a thread.
//
void threadFunc (
                int &delay,
                int value
                )
{

    // **** ****
    cout << "threadFunc <<< entering\n";

    // **** increase the delay and value ****
    cout << "threadFunc <<< delay: " << delay << " value: " << value << endl;
    delay += 2;
    value += 3;
    cout << "threadFunc <<< delay: " << delay << " value: " << value << endl;

    // **** sleep for a few ****
    this_thread::sleep_for(chrono::seconds(delay));

    // **** ****
    cout << "threadFunc <<< exiting\n";
}

The threadFunc() executed in the thread receives two arguments. The delay variable is passed by reference and the second argument named value is passed by value.

We display both values and update them. The t1 thread then sleeps. You can experiment with the code watch it run. The delay can be initially set to a small value and incremented in the t1 to a larger one.

//
// Main entry point.
//
int main()
{

    // **** welcome message ****
    cout << "main <<< Hello!!!\n";

    // **** call the thread function (without / with argument(s)) ****
    //thread t1(threadFunc);
    int delay   = 7;
    int value   = 100;
    //thread t1{ threadFunc, ref(delay) };
    //thread t1(threadFunc, ref(delay), value);

    // **** replace the thread function with a lambda expression ****
    //thread t1{ [](int &delay, int value) {
    //thread t1{ [=]() {
    thread t1{ [&]() {

        // **** increase the delay and value ****
        cout << "lambda <<< delay: " << delay << " value: " << value << endl;
        delay += 2;
        value += 3;
        cout << "lambda <<< delay: " << delay << " value: " << value << endl;

        // **** sleep for a few ****
        this_thread::sleep_for(chrono::seconds(delay));

    //}, ref(delay), value};
    }};

    // **** do some work ****
    cout << "main <<< working ...\n";
    this_thread::sleep_for(chrono::seconds(3));
    cout << "main <<< done working\n";

    // **** wait for the thread to complete ****
    t1.join();

    // **** display the delay ****
    cout << "main <<< delay: " << delay << " value: " << value << endl;

    // **** good bye message ****
    cout << "main <<< Bye-bye!!!\n";

    // **** ****
    return 0;
}

We are now enabling a lambda expression which we will call instead of the threadFunc() function. The code is similar. The first and last statements are missing.

Note that we have three options on using the lambda expression. The third one is defined as [&] which passes all arguments by reference. You can experiment with the other options. For example [=] passes all arguments by value, so your compiler should not like the assignment to the variable.

main <<< Hello!!!
main <<< working ...
lambda <<< delay: 7 value: 100
lambda <<< delay: 9 value: 103
main <<< done working
main <<< delay: 9 value: 103
main <<< Bye-bye!!!

The output confirms what we expect when using the [&].

It is good to experiment with the code to make sure you understand what is happening.

The entire code for this project can be found in my GitHub repository.

If you have comments or questions regarding this, or any other post in this blog, or if you would like for me to serve of assistance with any phase in the SDLC (Software Development Life Cycle) of a project associated with a product or service, please do not hesitate and leave me a note below. If you prefer, send me a message using the following address:  john.canessa@gmail.com. All messages will remain private.

Keep on reading and experimenting. It is the best way to learn, refresh your knowledge and enhance your developer toolset!

John

Twitter:  @john_canessa

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.