哪种方法可以并行处理多个作业而不会阻塞主线程

时间:2018-04-18 02:43:43

标签: c++ multithreading timer

我有一个要求,即单个进程应该并行处理多个作业。应定期运行这些作业中的每一个(例如,每10秒)。此外,主线程需要注意停止信号,接收时应停止所有线程并退出。

以下是我处理此要求的方法。

主线程将为每个作业创建线程并等待停止信号。每个线程负责处理循环机制。当主线程收到停止信号时,它会向线程发送一个信号停止。

在这种机制中,踏板将一直运行。所以我认为可能有更好的方法来处理这个问题。 也许是,主线程将跟踪下一次应该执行每个作业的时间,并在需要时启动线程。线程将执行一些操作并在完成时退出。在这种情况下,线程不会一直运行。

我目前还不知道如何实施替代方法。所以,我想知道上面哪一个可能是好方法?此外,如果还有其他更好的选择,请建议。

编辑[19 / Mar]:

也许我最初应该提到这一点,但现在就去了。

假设我有两份工作,两者都不需要同时运行。例如,Job 1应该每10秒运行一次。工作2应该每20秒运行一次。

此外,如果Job本身需要更多时间,那么必须有一些机制来正确识别和处理它。也许跳过执行或等待以前的工作完成&然后重新开始。目前,这方面的要求尚不清楚。但是,我应该能够识别并处理这种情况。

3 个答案:

答案 0 :(得分:1)

以下是一些建议。

首先让主线程旋转线程以根据需要完成任务。主线程在创建线程时将其分离,然后完全忘记它。分离的线程在返回时会自行清理。这可以轻松简化主线程,并且还可以消除跟踪和管理不同线程的开销。如果线程几乎完全独立并且永远不会遇到无限循环,则此方法有效。主线程在收到停止信号时才会退出,而停止信号又会终止进程,但这取决于操作系统,因此请查看What happens to a detached thread when main() exits?

你启动一个充当任务/线程管理器的线程。此对象/线程将根据需要创建新线程,并通过某些池或线程跟踪正确等待它们。然后主线程可以轻松地通过互斥锁保护标志向线程跟踪器发送消息,在这种情况下,线程跟踪器只是等待所有生成的线程然后死掉。这需要比上述解决方案更多的开销,但它为您提供了有关哪些线程运行时的更多信息。如果您需要直接终止线程或者如何在必要时发送消息,您还可以更好地控制。此外,它还允许安全清理,根据您的操作系统,线程可能会因过程死亡而缩短。如果线程需要能够更容易地接收消息并且如果你想确保一个线程在停止信号之后运行完成(想想R / W操作),那就更好了。

您还可以将主线程和线程/管理器混合为一个,但这会使主线程变得更复杂,并且不会为大多数通用场景显着降低开销。

答案 1 :(得分:1)

看一下在给定的intervalls下运行函数的以下类:

    import {          BrowserModule        } from '@angular/platform-browser';
    import {          NgModule        } from '@angular/core';
    import {          FormsModule        } from '@angular/forms';
    import {          HttpClientModule        } from '@angular/common/http';
    import {          RouterModule        } from '@angular/router';

    import {          AppComponent        } from './app.component';
    import {          MobilesComponent        } from  ./mobiles/mobiles.component';
    import {          MobileService        } from ./mobiles/mobile_service';



    @NgModule({
      declarations: [
        ...
      ],
      imports: [
        BrowserModule,
        FormsModule,
        HttpClientModule,
        RouterModule.forRoot([
          ...
        ])
      ],
      providers: [MobileService],
      bootstrap: [AppComponent]
    })
    export class AppModule {}
    /*platformBrowserDynamic().bootstrapModule(AppModule);*/

在start()上创建一个新线程,它以给定的间隔运行给定的'回调'。对于10秒的间隔,回调几乎每10秒调用一次,除非它在下一次启动到期时仍然有效。在这种情况下,它立即再次运行。

stop()设置quit-flag并等待线程退出。这个非常简单的主路由实现不会中断shouldQuit-checks的睡眠,因此最多可能需要一个完整的时间间隔才能退出CallbackTimer。

请注意,回调时间可能略有偏差,因此如果您在同一个intervall中启动其中两个,则可能在一段时间后不会同时运行。但漂移应该是最小的,如果它的关键,你应该考虑另一种解决方案。

以下是一个用法示例:

class CallbackTimer
{
public:
    ~CallbackTimer()
    {
        stop();
    }

    void start(std::chrono::milliseconds interval, std::function<void()> callback)
    {
        stop();
        shouldQuit = false;

        handle = std::async([=,callback=std::move(callback)]() {

            while (!shouldQuit)
            {
                auto nextStart = std::chrono::steady_clock::now() + interval;
                callback();
                std::this_thread::sleep_until(nextStart);
            }
        });
    }

    void stop()
    {
        if (handle.valid())
        {
            shouldQuit = true;
            handle.get();
        }
    }

private:
    std::atomic_bool shouldQuit;
    std::future<void> handle;
};

对于更快的stop(),您可以实现一个“beginStop()”方法,该方法仅将'shouldQuit'标志设置为true,并在所有实例上调用“stop()”之前为所有实例调用此方法。这样,在第一个实例的关闭完成之前,第二个实例的关闭不会延迟。

答案 2 :(得分:0)

如果您的主线程只需管理线程,您可以每10秒启动一次新线程并等待它们完成。

以下示例并行运行两个线程,等待它们完成并在循环之前等待一秒钟。 (shouldQuit()函数使用Visual C ++中的编译器特定函数;也许你必须在那里插入自己的代码)

#include <future>
#include <iostream>
#include <conio.h>
#include <thread>
#include <chrono>

int a()
{
    std::cout << 'a' << std::flush;
    return 1;
}

int b()
{
    std::cout << 'b' << std::flush;
    return 2;
}

bool shouldQuit()
{
    while (_kbhit())
    {
        if (_getch() == 27)
            return true;
    }
    return false;
}

int main()
{
    auto threadFunctions = { a, b };

    while(!shouldQuit())
    {
        auto threadFutures = std::vector<std::future<int>>{};

        // Run asynchronous tasks
        for (auto& threadFunction: threadFunctions)
            threadFutures.push_back(std::async(threadFunction));

        // Wait for all tasks to complete
        for (auto& threadFuture : threadFutures)
            threadFuture.get();

        // Wait a second
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    return 0;
}

线程消耗的时间(在我们等待它们完成时)不是时间的一部分。线程调用之间的暂停对于运行时间最长的线程为1秒,对于所有其他线程至少为1秒。

注意:每隔几秒运行一个新线程并不是最有效的处理方式。在大多数操作系统上创建新线程是一项昂贵的操作。如果性能至关重要,您应该重复使用问题中描述的现有线程。但该计划的总体复杂性将会增加。

使用std::asyncstd::future可以正确处理异常。如果线程抛出异常,则会捕获此异常并将其存储在std :: future中。它在调用future.get()的线程中重新抛出。因此,如果你的线程可能抛出异常,那么最好在try .. catch中包装你的future.get()调用。

future.get()调用将调用线程与为其创建的线程同步。如果线程已经完成,它只返回返回值。如果线程仍在工作,则调用线程将被阻塞,直到线程完成。