0

I'm trying to use a separate thread to make my getline 'non-blocking', but I have some issues trying to pass args from my thread to my 'main' function.

Here is what I did :

#include <iostream>
#include <string>

void getlineThread(std::string& commandString)
{
    while (true) {
        std::getline(std::cin, commandString);
        std::cout << "here : " << commandString << std::endl;
    }
}

int main()
{
    std::string commandString;
    std::thread thread(getlineThread, std::ref(commandString));

    while (true) {
        if (!commandString.empty()) {
            std::cout << "Command: " << commandString << std::endl;
            break;
        }
    }

    if (thread.joinable()) {
        thread.join();
    }
}

When I write "hello" in the standard input for the getline, here is my standard output :

here : Hello

So the command isn't passed correctly to the main, and the 'commandString' variable stays empty in my main.

I tried to use some things that I don't really master, like 'std::ref' or the handling of '&', so maybe the problem comes from here

Does anyone have an idea?

Amit
  • 645
  • 1
  • 3
  • 19
  • 1
    You have a data-race. That leads to *undefined behavior*. – Some programmer dude May 21 '23 at 09:50
  • 1
    You never add any snchronization, so the `main` thread may never see any updates of the `std::string` or see only partial updates. printing such a string is undefined behaviour... – fabian May 21 '23 at 09:51
  • @fabian and how can I add this ? – Alexandre Boucard May 21 '23 at 09:53
  • 2
    Locking a `std::mutex` for as long as you're accessing the string would do the trick. However with the current implementation of the loop in `main` this would result in the main thread regularly locking down the mutex with no delay, which isn't desirable. You may want to use [`std::condition_variable`](https://en.cppreference.com/w/cpp/thread/condition_variable) to just wait for the background thread to deliver new content. – fabian May 21 '23 at 10:01

2 Answers2

0

You can simply add a mutex, but if you do it in your current threads, getline will block the thread with getLineThread, and mutex will block the main thread, so you'll get no multithreading.

If you add a separate string for getline, and do it outside of the mutex lock, it will unblock the threads, but this will make the main thread printing the last command constantly, barely giving you time to enter a new command. So here a separate string will also help (I have also added exit from cycles, because endless cycles are less fun to debug, and a sleep to emulate work on the main thread):

#include <iostream>
#include <string>
#include <thread>
#include <chrono>
using namespace std::chrono_literals;

std::mutex cs_mutex;

void getlineThread(std::string& commandString)
{
    bool bExit=false;
    std::string input;

    while (!bExit) {
        std::getline(std::cin, input);
        cs_mutex.lock();
        commandString=input;
        std::cout << "here : " << commandString << std::endl;
        if(commandString=="exit") bExit=true;
        cs_mutex.unlock();
    }
}

int main()
{
    std::string commandString;
    std::thread thread(getlineThread, std::ref(commandString));
    bool bExit=false;
    std::string comString;
    while (!bExit) {
        cs_mutex.lock();
        if (commandString!=comString) {
            comString=commandString;
            std::cout << "Command: " << commandString << std::endl;
            if(commandString=="exit") bExit=true;
        }
        cs_mutex.unlock();
        std::this_thread::sleep_for(6000ms);
    }
    if (thread.joinable()) {
        thread.join();
    }
    return 0;
}

Now both threads are working, but if you try to do some heavy stuff (simulated by longer sleep) in the main thread (and that's why you bothered with concurrency, right?) you will find that you can miss some inputs. So your output can look like

first
here : first
Command: first
second
here : second
third
here : third
Command: third

skipping the "second" input. So instead of a single string (commandString) you'd better use a queue, deque or list of strings as a shared object. This way, you just push/push_back your "input" string in the getlineThread, and in the main cycle add a cycle to pop_front/front pop everything while the shared container is not empty.

This way you can get

first
here : first
second
here : second
third
here : third
Command: first
Command: second
Command: third
exit
here : exit
Command: exit
Program ended with exit code: 0

But using queues is not directly related to the question, so I leave it. Also, my code is not optimized for the minimal mutex-lockedness, you can improve it by leaving stuff not related to the shared object outside of the mutex lock. You can also experiment with try_lock instead of lock in the main thread -- it's not very useful here right now, but if instead of a simple push you do something complex, while your main cycle is also doing something important, it could be handy. And, of course, it's better to use wrappers such as std::lock, std::unique_lock, std::lock_quard and C++17 std::scoped_lock instead of bare mutex, but it's better to grasp bare mutex family first.

Mike Tyukanov
  • 579
  • 4
  • 10
0

One way to do it is with std::async and std::future:

#include <iostream>
#include <string>
#include <thread>
#include <future>


std::string GetLineAsync()
{
    std::string input;
    std::getline(std::cin, input);

    return input;
}


int main()
{
    auto future = std::async(std::launch::async, GetLineAsync); // future is of type 'std::future<std::string>'

    while (future.wait_for(std::chrono::seconds(0)) != std::future_status::ready) {
        std::cout << "Waiting for input (doing something else in the meanwhile)..." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1)); // Simulates this thread job
    }

    const std::string input_c = future.get();
    std::cout << "Input: " << input_c << std::endl;
}

Demo: https://onlinegdb.com/E9Gwgje7T

--

If you insist on std::thread, you can use it with std::promise and std::future:

#include <iostream>
#include <string>
#include <thread>
#include <future>


void GetLineAsync(std::promise<std::string>& promise)
{
    std::string input;
    std::getline(std::cin, input);
    promise.set_value(input);
}


int main()
{
    std::promise<std::string> promise;
    auto future = promise.get_future(); // future is of type 'std::future<std::string>'

    std::thread tr(GetLineAsync, std::ref(promise));

    while (future.wait_for(std::chrono::seconds(0)) != std::future_status::ready) {
        std::cout << "Waiting for input (doing something else in the meanwhile)..." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1)); // Simulates this thread job
    }

    tr.join();

    const std::string input_c = future.get();
    std::cout << "Input: " << input_c << std::endl;
}

Demo: https://onlinegdb.com/xx78wIoYvE

--

For the question "When to use std::async vs std::thread?" you can read here: When to use std::async vs std::threads?

Amit
  • 645
  • 1
  • 3
  • 19