0

Imagine I have a worker thread executing some kind of code (conceptually):

Call1(); //executes on worker thread
Call2(); //executes on worker thread
Call3(); //executes on worker thread
ret = CallThatMustExecuteOnMainThread(arg1, arg2, argN);

As the name hints CallThatMustExecuteOnMainThread along with its arguments should be executed on the main thread and return value should be assigned to ret. The execution flow on worker thread should be stopped while CallThatMustExecuteOnMainThread() is being evaluated and resumed afterwards. For the sake of argument CallThatMustExecuteOnMainThread will call some external dynamic library function. There will be only one worker thread executing the calls above.

The main thread executes the following loop (conceptually):

for (;;) {
  if (hasTaskFromWorkerThread)
    ExecuteTaskFromWorkerThread();
  else
    SomethingElse();
}

I can't quite figure out what kind of sync and data transfer mechanisms should I use to implement the required behavior above.

ADDED LATER

Here's a proof-of-concept implementation I put together.

#include <iostream>
#include <string>
#include <chrono>
#include <thread>
#include <future>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <optional>
#include <conio.h>

std::mutex m;
std::condition_variable cv;

std::function<int()> onMainFunc;
using ResultType = decltype(onMainFunc)::result_type;
std::optional<ResultType> result;


const std::thread::id MAIN_THREAD_ID = std::this_thread::get_id();

static int ExecOnMainImpl(int x, int y) {
    return x + y;
}

struct S {
    static int ExecOnMain(int x, int y) {
        if (std::this_thread::get_id() == MAIN_THREAD_ID)
            return ExecOnMainImpl(x, y);
        else {
            {
                std::lock_guard<decltype(m)> lk(m);
                result.reset();
                onMainFunc = [x, y]() -> int { return ExecOnMainImpl(x, y); };
            }
            cv.notify_one();
            {
                std::unique_lock lk(m);
                cv.wait(lk, [] { return result.has_value(); });
                return result.value();
            }
        }
    }
};

void worker_thread() {
    using namespace std::chrono_literals;
    for (;;) {
        std::this_thread::sleep_for(5000ms);
        std::cout << S::ExecOnMain(1, 2) << "\n";
    }
}

int main()
{
    using namespace std::chrono_literals;

    auto future = std::async(std::launch::async, worker_thread);

    for (;;) {
        {
            std::unique_lock lk(m);
            if (cv.wait_for(lk, 500ms, [] { return onMainFunc != nullptr; })) {
                result = onMainFunc();
                onMainFunc = nullptr;
                lk.unlock();
                cv.notify_one();
            }
            else
                std::cout << "Doing something else" << "\n";
        }
    }

    future.get();
    
    return 0;
}

This outputs:

Doing something else
Doing something else
Doing something else
Doing something else
Doing something else
Doing something else
Doing something else
Doing something else
Doing something else
3
Doing something else
Doing something else
Doing something else
Doing something else
Doing something else
Doing something else
Doing something else
Doing something else
Doing something else
3
Doing something else
Doing something else
......

Does this look sane? Any suggestions are welcome.

lhog
  • 105
  • 1
  • 8
  • Mutex and shared memory comes to mind, but there are many other options including IPC. If it's just a number, it's `atomic` that I'd try. – lorro Aug 14 '22 at 22:07
  • Is your main thread a UI thread that manages the on screen elements, pumps messages and should not block? Or can the main thread "wait" for the data to be available from the other threads. Knowing that will greatly shape my answer. – selbie Aug 14 '22 at 22:10
  • The "sync and data transfer mechanisms" are always the same, no matter what kind inter-thread synchronization is needed: mutex and condition variables. They are the building blocks of every non-trivial inter-thread synchronization in C++, so there's nothing unique here, I don't see anything novel about this. This question is too general and non-specific, and needs to be focused. – Sam Varshavchik Aug 14 '22 at 22:17
  • 2
    _"The execution flow on worker thread should obviously be stopped while `CallThatMustExecuteOnMainThread()` is being evaluated"_ - How is that obvious? – Ted Lyngmo Aug 14 '22 at 22:18
  • @selbie yes, main thread is just pumping messages and do certain updates and rendering of the UI, if it has no tasks to execute from the worker thread. – lhog Aug 14 '22 at 22:45
  • @SamVarshavchik The existing examples I found mostly deal with condition variables and I expect they won't fit here because the main thread needs to do other stuff in case it has no task from worker thread to take care of. Cond vars put the waiting thread to sleep, if I got it right. – lhog Aug 14 '22 at 22:45
  • No, condition variables don't "put the waiting thread to sleep". Mutexes and condition variables are the basic building blocks for inter-thread synchronization. If that synchronization takes the form of what's described in the question, then additional logic gets implemented on top of that, with mutexes and condition variables taking care of properly synchronizing the execution threads. There are very few magic buttons hiding in C++ that only need to be pushed in order to make everything happen, from start to finish. This isn't one of them. – Sam Varshavchik Aug 14 '22 at 22:49
  • 1
    And about those "existing examples" that you "found": there's a popular myth: how to write C++ code without learning core C++ fundamental concepts? Answer: run a Google search and copy/paste the results. This only works if the found code's functionality is 100% identical to what's needed. This doesn't work if the found code is junk code, but without learning core C++ fundamentals there's no way to tell if it's junk. And if it's not a 100% match, knowledge of core fundamentals is needed to change it accordingly, but a Google search won't help with this, now, by definition. – Sam Varshavchik Aug 14 '22 at 22:51
  • Check if this solution helps: https://stackoverflow.com/questions/10602429/how-to-make-a-threadpool-in-c-tbb – Something Something Aug 15 '22 at 00:42
  • @MadFred I don't see any code in the accepted solution block nor any code in other blocks, except the question, too. – lhog Aug 15 '22 at 13:02
  • I've updated the question with P-o-C level code, that seems to work, but I'm not 100% sure if it's correct. – lhog Aug 15 '22 at 13:03

0 Answers0