如何在不让用户等待的情况下管理多个线程?

时间:2019-02-09 22:17:35

标签: c++ multithreading

很抱歉,如果措辞不佳,我不确定如何准确地描述标题中的内容。但基本上,我的目标是让用户输入时间,并让程序在时间过去时提醒他们。时间过去后,该程序将寻找另一个时间,同时允许用户输入更多时间。基本上,它看起来像这样:

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
    }
}

因此,当用户插入更多时间时,另一个线程正在等待打印最早的计划时间。如果用户输入的当前时间之后的新时间,则不会有问题。但是,如果他们输入的时间要早​​于下一次,该怎么办?

这是问题所在。假设最早的排定时间是从现在开始的一个月。用户输入从现在起两周的新时间。那么我们该怎么办?我猜想作另外一个话题。但是随后用户想输入下周的时间。然后从现在开始三天。然后是明天的时间。依此类推。

我是多线程的新手,但是让所有这些新线程不受监管地制造当然不是一个好主意,对吗?那么我们如何控制它呢?当需要删除线程时,我们需要使用.join,对吗?有没有一种方法可以实现联接,而又不需要用户等待时间过去并允许他们继续输入更多次而不会中断?

2 个答案:

答案 0 :(得分:0)

欢迎使用StackOverflow。我本人对使用C ++进行线程编程还很陌生,所以我对最好的库和技术不熟悉,但是我至少可以分享一些我所知道的基础知识,并希望可以让您对从那里去的感觉

如果我理解正确,我相信您的问题主要围绕join(),所以我将从这里开始。

调用join()是等待线程加入之前继续前进的方式,但是创建线程不必立即执行,否则将毫无意义。您可以让线程按照自己的喜好进行操作,它会在完成时结束,而没有主线程的任何其他输入(请纠正我,我错了)。

join()的重要之处在于,您可以在所有线程上调用它,以等待它们退出程序(否则,当然可以安全地中止它们)。否则,即使在main()返回之后,它们仍将继续运行,并且由于它们不再连接至正在运行的进程,因此在尝试访问内存时将导致问题。另一个潜在的用途可能是让一些工作线程在计算中的某些检查点匹配以共享结果,然后再抓取下一部分工作。

希望有帮助。尽管我想和您分享一些想法,以防您或将来的读者不熟悉此示例中涉及的其他一些概念。


您不会指出是否有跟踪时间并在线程之间共享的方法,因此,我只提供一个快速提示:

在添加或弹出缓冲区之前先对其进行锁定。

这样做很重要,以避免出现竞争状态,在这种情况下,一个线程可能会试图弹出某些线程而另一线程正在添加某些线程,并导致出现奇怪的问题,特别是如果您最终使用类似set from the standard library这样的线程并确保插入时只有给定元素的一个副本。

如果您不熟悉锁定机制,则可以找到在C ++中使用互斥对象和信号量的示例,或者搜索“锁定”或“同步对象”。您可以考虑使用standard library中不起眼的Mutex。


关于实际创建线程,我想到了几件事。一种想法是使用线程池。那里有几个用于处理线程池的库,其中一个例子是Apple的开源Grand Central Dispatch(通常称为libdispatch),可以肯定地在Linux上使用,但是对于Windows,您可能需要查找其他内容(不幸的是,我对Windows平台并不熟悉)。它们管理您正在使用和未使用的线程的生命周期,因此可能会有所帮助。再说一遍,对自己本人有点陌生,我不是100%肯定这对您来说是最好的,尤其是因为您还有项目的其他部分需要解决,但是值得一试。 >

即使不使用线程池(例如,您使用pthreads),只要您对其设置了合理的限制,我也不必担心自己独自启动一堆线程(多少是合理的)不确定,但是如果您查看macOS中的活动监视器,Windows中的Task Manager或Linux中的TOP,您会发现在任何给定时间,您计算机上的许多程序可能会摇摆很多线程,现在我有5090个线程正在运行和327个进程。每个进程大约有15.5个线程。某些进程的线程数远远超过该线程。

希望那里有所帮助。

答案 1 :(得分:0)

这里是一个示例,据我了解您正在尝试使用标准库。通常,将通过各种std::mutexstd::conditional_variable和相关标志来控制线程,以实现所需的效果。有一些库可以简化复杂情况下的线程处理,最显着的是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();
        }
    }