0

I'm making a timer program the counts the time passed since the program started. In the background I'm also checking for keyboard input (enter/return to exit, click on the window); this is done in a separate thread I've run as detached.

It seems the second thread cannot receive the input from the main thread. When I use the keyboard or mouse, nothing happens. Also, nothing appears on the screen, just white.

std::mutex g_mutex;
std::condition_variable cv;

// check for input from the user using the window object
// sets stopProgram to true if the user wishes to exit
void poll(sf::RenderWindow& window, bool& stopProgram) {
    std::unique_lock<std::mutex> lk(g_mutex);
    // wait for main thread to open window
    cv.wait(lk, [&] { return !stopProgram && window.isOpen(); });
    sf::Event event;
    while (true) {
        if (window.pollEvent(event)) {
            // if user wants to exit program
            if (event.type == sf::Event::Closed || (event.type == sf::Event::KeyPressed &&
                (event.key.code == sf::Keyboard::Return || event.key.code == sf::Keyboard::Escape))) {
                window.close();
                // main thread will explicitly exit the main loop
                stopProgram = true;
                break;
            }
        }
    }
}

int main()
{
    int hour = 0, minute = 0, second = 0;
    auto text = textObject();
    bool stopProgram = false;
    // run a background thread that checks for input while the main program runs
    std::thread(poll, std::ref(window), std::ref(stopProgram)).detach();
    std::once_flag flag;

    std::lock_guard<std::mutex> lk(g_mutex);
    while (window.isOpen()) {
        // notify once window opens
        std::call_once(flag, [&] { cv.notify_one(); });
        // set timestamp
        text->setString(makeTimeStamp(hour, minute, second));
        // if the background thread set stopProgram, end the program
        if (stopProgram) break;
        window.clear(sf::Color::White);
        window.draw(*text);
        window.display();
        // update time
        second = (second + 1) % MAX_SEC;
        if (second == 0) minute = (minute + 1) % MAX_MIN;
        if (second == 0 && minute == 0) hour = (hour + 1) % MAX_HOUR;
        // sleep one second
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

Is my use of multithreading correct? If so, can only the main thread receive input and that's why it's not working?

Update: If I get rid of the while (true) and use while (window.pollEvent(event)) and move the lock_guard to just before if (stopProgram) then the text (timestamp) appears on the screen, but I still cannot process input.

7
  • calling std::this_thread::sleep_for(std::chrono::seconds(1)); doesn't necessarily means that your program will resume in 1 second. So, if you want an accurate counting check the difference between the current time and the previous. Commented Aug 12, 2016 at 21:01
  • Also, unique_lock is used only with shared mutexes. using unique_lock on non-shared mutex is basically calling lock_guard. You should read about shared mutexes on en.cppreference.com/w/cpp/thread/shared_mutex Commented Aug 12, 2016 at 21:04
  • @TalShalti How do I check if the difference in time is exactly one second? Commented Aug 12, 2016 at 21:08
  • Sleeping for 1s only guarantee you'll sleep at least 1 second. To calculate difference in time, use the function std::chrono::system_clock::now() before and after the thing you want to calculate the time for, and then substract one value from the other Commented Aug 12, 2016 at 21:11
  • cant you just use global values and or a function? You can access the same functions and global variables, regardless of the thread Commented Aug 12, 2016 at 23:17

1 Answer 1

1

main thread launches poll thread.

std::thread(poll, std::ref(window), std::ref(stopProgram)).detach();

main thread acquires g_mutex and never ever releases it.

std::lock_guard<std::mutex> lk(g_mutex);

poll thread waits for g_mutex to be released:

std::unique_lock<std::mutex> lk(g_mutex);

but the main thread never releases it, so the poll thread never does anything.

To fix it. Change the beginning of the main() function:

int main()
{
    int hour = 0, minute = 0, second = 0;
    auto text = textObject();
    volatile bool stopProgram = false;
    // run a background thread that checks for input while the main program runs
    std::thread(poll, std::ref(window), std::ref(stopProgram)).detach();

    while (!window.isOpen()) { /* busy loop */ }
    {
        std::lock_guard<std::mutex> lk(g_mutex);
        cv.notify_all();
    }
    while (window.isOpen()) {
       ...

This SFML API makes things more difficult than other windowing frameworks that I've used. It would be sooo useful if there were a thread-safe window.pushCustomEvent() function.

Sign up to request clarification or add additional context in comments.

2 Comments

What goes inside /* busy loop */ and why is stopProgram now volatile?
Nothing goes in /*busy loop*/. The comment tells the reader that I didn't accidentally write a while loop with nothing in it. I intended to write a while loop with nothing in it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.