1

Sorry if this was worded poorly, I wasn't sure how to give an accurate description of what I wanted in the title. But basically my goal is to have the user input times and for the program to alert them when the time has passed. After the time has passed, the program looks for another time while allowing the user to input more times. Basically, it would look something like this:

void printTime(tm time) {
    //sleep until time
    cout << "it is " << time << endl;
    lookForNextTime();
}

void lookForNextTime() {
    //find earliest time
    printTime(time);
}

int main() {
    //create thread in lookForNextTime
    while(true) {
        //ask user to insert more times until they quit
    }
}

So while the user is inserting more times, the other thread is waiting to print out the earliest scheduled time. If the user inputs a new time that is after the current scheduled time, there shouldn't be an issue. But what happens if they input a time that is meant to come before the next time?

This is the problem. Let's say the earliest scheduled time is a month from now. The user inputs a new time that is two weeks from now. So what do we do? Make another thread, I guess. But then the user wants to input a time next week. And then a time three days from now. And then a time tomorrow. And so on.

I'm new to multithreading, but surely it's not a good idea to just let all of these new threads be made without regulation, right? So how do we control it? And when it's time to remove a thread, we need to use .join, correct? Is there a way to implement join that doesn't require the user to wait for the time to pass and allows them to continue inputting more times without interruption?

Jazael
  • 51
  • 2
  • 9
  • I'd suggest having a look at `boost::asio`, and thinking about how you can use `boost::asio::deadline_timer::expires_at()`. Your main thread would create another thread that calls `boost::asio::io_context::run()`, you can use `boost::asio::deadline_timer::async_wait()` to get a callback when the timers expire – Chad Feb 09 '19 at 22:24
  • Once upon an imbedded system, there were 5 user buttons, all triggering asynchronous actions. The scheme was simple, but maybe not suitable for you. A single task 'scanned' the buttons at 8 hz (8 times per second), Each scan would read a single byte. Each bit (in the byte) would be compared to previous state, a change of state triggered a semaphore to another thread. Users seldom pressed the button for too short a duration (a miss), which was immediately noticed, and the button repressed. Although there were 5, typically, the user pressed with 1 finger, with no disadvantage. – 2785528 Feb 09 '19 at 22:52

2 Answers2

0

Welcome to StackOverflow. I am fairly new to threading in C++ myself so I'm not familiar with the best libraries and techniques, but I can share at least a little about the basics I do know, and hopefully that gives you a feel of where to go from there.

If I understand correctly I believe you question mainly revolves around join() so I'll start there.

Calling join() is how you wait for a thread to join before moving on, but you don't have to do that as soon as you create one or it would be pretty pointless. You can let the thread go on its own merry way and it will end when it is done without any further input from the main thread (please correct me I am mistaken).

The important thing about join() is that you call it on all the threads to wait for them before exiting the program (or otherwise aborting them somehow, safely of course). Otherwise they will continue running even after main() returns and will cause issues when they try to access memory as they are no longer attached to a running process. Another potential use might be to have a few worker threads match up at certain checkpoints in a calculation to share results before grabbing the next chunk of work.

Hope that helps. I had a few more thoughts though that I thought I would share in case you or some future reader aren't familiar with some of the other concepts involved in this example.


You don't indicate whether you have an way in mind for keeping tracking of the times and sharing them between threads, so so I'll just throw out a quick tip:

Lock your buffer before you add or pop from it.

Doing so is important in order to avoid race conditions where one thread could be trying to pop something off while the other is adding and causing weird issues to arise, especially if you end up using something like set from the standard library which sorts and ensures you only have one copy of any given element upon insertion.

If you aren't familiar with locking mechanisms then you could find examples of using Mutexes and Semaphores in C++, or do a search for 'locking' or 'Synchronization Objects'. You may consider the humble Mutex from the standard library.


As far as actually creating the threads, a couple things come to mind. One thought is using thread pools. There are several libraries out there for handling threading pools, with one such example being Apple's open source Grand Central Dispatch (commonly known as libdispatch) which can be used on Linux for sure, but for Windows you would want to look up something else (I'm not that familiar with the Windows platform unfortunately). They manage the life cycles of the threads you are and are not using so they could potentially help. Again, being a bit new to this myself I'm not 100% sure that would be the best thing for you, especially since you have other parts of the project to work out still, but it may be worth looking in to.

Even without using thread pools (say you use pthreads) I don't think you need to worry too much about starting a bunch of threads on your own as long as you put some reasonable limit on it (how much is reasonable I'm not sure but if you check out Activity Monitor in macOS or Task Manager in Windows or TOP in Linux you will see that at any given time many programs on your machine may be rocking quite a few threads-right now I have 5090 threads running and 327 processes. That's about 15.5 threads per process. Some process go much higher than that).

Hope something in there helps.

RTHarston
  • 429
  • 4
  • 13
  • Is there any way to abort a thread? The purpose of the initial thread is consistently loop in the background between printing times and finding more times. I'm not sure when it would be able to join, and if I could somehow abort it and start fresh, that'd be easier when considering new times. – Jazael Feb 10 '19 at 01:18
  • I tried creating a thread "t" and using t.std::thread::~thread(); when ending the program, but this just gave me the error: "terminate called without an active exception Aborted (core dumped)" – Jazael Feb 10 '19 at 01:38
  • @Jazael What do you mean "abort it and start fresh"? You say you want it looping in the background. When would you want to abort it? Note that the first answer here explains that ~thread() kills *every* thread, including the main one. https://stackoverflow.com/questions/12207684/how-do-i-terminate-a-thread-in-c11 – RTHarston Feb 10 '19 at 20:44
  • @Jazael Option 3 in answer 1 of that question mentions that you can try and get the other thread to terminate itself, but probably leaks recourses. He explains that there is no simple built in way of telling a single thread to terminate, you have to create some sort of signal and have that thread check if it has received that signal via exception or maybe a bool value that it periodically checks (the thread may have an old value in cache so you have to make sure it checks the real value) but if you do that you have to make sure the thread cleans up properly. – RTHarston Feb 10 '19 at 20:57
  • Try looking up "C++ background timer" to see if someone has done something similar before and looking and their code (a quick search resulted in several SO questions and tutorials from other sites). I think that should help you find some code that will work for your purposes. – RTHarston Feb 10 '19 at 20:59
0

Here is an example from what I understood you're trying to do using the standard library. Usually threading will be controlled through various std::mutex, std::conditional_variable, and related flags to achieve the desired affect. There are libraries that can simplify threading for more complex scenarios, most prominently boost::asio.

    #include <iostream>
    #include <string>
    #include <thread>
    #include <chrono>
    #include <condition_variable>
    #include <mutex>

    bool spuriousWakeup = true;
    bool timerSet = false;
    bool terminated = false;

    int timerSeconds = 0;

    std::thread timerThread;
    std::mutex timerMutex;
    std::condition_variable timerWakeup;

    void printTimeThreadFunc()
    {
        // thread spends most of the time sleeping from condition variable
        while (!terminated){
            std::unique_lock<std::mutex> lock(timerMutex);

            if(timerSet){
                // use condition variable to sleep for time, or wake up if new time is needed
                if(timerWakeup.wait_for(lock, std::chrono::seconds(timerSeconds), []{return !spuriousWakeup;})){
                    std::cout << "setting new timer for " << timerSeconds << " seconds!" << std::endl;
                }
                else{
                    std::cout << "timer expired!" << std::endl;
                    // timer expired and there is no time to wait for
                    // so next time through we want to get the un-timed wait
                    // to wait indefinitely for a new time 
                    timerSet = false;
                }
            }
            else{
                std::cout << "waiting for timer to be set!" << std::endl;
                timerWakeup.wait(lock, []{return !spuriousWakeup;});
            }
            spuriousWakeup = true;
        }
    }

    int main()
    {
        // timer thread will exist during program execution, and will
        // be communicated with through mutex, condition variable, and flags.
        timerThread = std::thread(printTimeThreadFunc);

        while (!terminated){
            // get input from user
            std::string line; 
            std::getline(std::cin, line);

            // provide a way to quit
            if (line == "end") {
                terminated = true;
                break;
            }

            // make sure its a number
            try{
                // put scope on lock while we update variables
                {
                    std::unique_lock<std::mutex> lock(timerMutex);
                    timerSet = true;
                    timerSeconds = std::stoi(line);
                    spuriousWakeup = false;
                }
                // let thread know to process new time
                timerWakeup.notify_one();

            }
            catch (const std::invalid_argument& ia) {
                std::cerr << "Not a integer" << ia.what() << '\n';
            }
        }

        // clean up thread
        if(terminated && timerThread.joinable()){
            timerWakeup.notify_one();
            timerThread.join();
        }
    }
dmoody256
  • 538
  • 4
  • 12