简体   繁体   中英

Multi-thread console text animations with std::cout

I'm trying to create a function that can animate multiple strings to the console at the same time. By "animate," I mean print a character, wait a specified amount of time, then print the next character, and so on.

This is what I've tried so far:

/**
 @param msg        Message to animate
 @param sleep_time Time to wait between each letter
 @param wait       Whether or not to wait for the current thread to join before returning
*/
void animate(const std::string& msg, const unsigned long long sleep_time, const bool wait = true)
{
    const std::atomic<std::chrono::milliseconds> t_sleep_time =
        std::chrono::milliseconds(sleep_time);

    std::stringstream msg_strm;
    msg_strm << msg;

    std::thread animate_thread([&msg_strm, &t_sleep_time]() -> void
    {
        char letter;

        while ((letter = msg_strm.get()) != EOF)
        {
            std::cout << letter << std::flush;
            std::this_thread::sleep_for(t_sleep_time.load());
        }

        return;
    });

    if (wait)
    {
        animate_thread.join();
    }
    else
    {
        animate_thread.detach();
    }
}

This is the driver code for it:

int main()
{
    animate("Hello", 500, false);
    std::cout << '\n' << std::endl;
    animate("Welcome", 400, true);
    std::cout << "\n\nEnd" << std::endl;
}

And this is the output ("Wecome" animates as sluggishly):


Welcome

End

What happened to "Hello"? I'm very new to multi-threading, so a detailed explanation would be very much appreciated. Ideally, what I'd like to happen, is to have "Hello" animating on one line and "Welcome" on the next. Is this possible?

First of all msg_strm lives on the stack thus you cannot pass it by value to the thread because it goes out of scope and that is why, most probable Hello was not showing. Also another problem that you have is that you are calling detach thus the program might exit before the first thread finish.

To achieve what you are trying to do I suggest to use ANSI escape codes . Therefore the following might not work on all command prompts. Also note that std::cout is not thread safe if you print in steps.

#include <atomic>
#include <iostream>
#include <string>
#include <thread>

std::atomic<int> g_lines = 1;

std::thread animate(const std::string& msg, const unsigned long long sleep_time)
{
    // NOTE: `[=]` means capture all variables used by value. Note that globals
    // are not captured.  Also note that capture by value is needed because
    // `msg` can go out-of-scope.
    return std::thread([=] {
        auto line = g_lines++;

        for (size_t column = 1; column <= msg.size(); column++)
        {
            // using `ANSI escape codes` move the cursor to the correct
            // position; \x1B[{line};{column}H

            std::cout << "\x1B[" + std::to_string(line) + ";"
                             + std::to_string(column) + "H" + msg[column - 1];

            std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time));
        }
    });
}

int main()
{
    auto t1 = animate("Hello", 500);
    auto t2 = animate("Welcome", 400);

    // you need to join all threads else if you call detach, the program might
    // exit before all threads finish.
    t1.join();
    t2.join();

    std::cout << "\n\nEnd" << std::endl;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM