Task
- это какой-то кусок вычислений. Сам код вычисления находится в методеRun()
и определяется пользователем.Executor
- это набор потоков, которые могут выполнятьTask
-и.Executor
должен запускать потоки в конструкторе, во время работы новых потоков создаваться не должно.- Чтобы начать выполнять
Task
, пользователь должен отправить его вExecutor
с помощью методаSubmit()
. - После этого, пользователь может дождаться пока
Task
завершится, позвав методTask::Wait
.
class MyPrimeSplittingTask : public Task {
Params params_;
public:
MyPrimeSplittingTask(Params params) : params_{params} {
}
bool is_prime = false;
void Run() override {
is_prime = CheckIsPrime(params_);
}
}
bool DoComputation(std::shared_ptr<Executor> pool, Params params) {
auto my_task = std::make_shared<MyPrimeSplittingTask>(params);
pool->Submit(my_task);
my_task->Wait();
return my_task->is_prime;
}
-
Task
может завершиться успешно (IsCompleted
), с ошибкой (IsFailed
) и быть отменён (IsCanceled
). После того, как с ним произошло одно из этих событий - он считается выполненным (IsFinished
). -
Пользователь может в любой момент отменить
Task
с помощью методаCancel()
. В этом случае, если выполнениеTask
-и еще не началось, то оно и не должно начаться. -
Task
может иметь зависимости. Например в задаче reduce сначала должны были выполниться reduce-ы по кускам вектора, а потом один финальный reduce по промежуточным значениям. Пользователь может сказать, что одинTask
должен выполняться только после того как выполнился какой-то другойTask
, позвав методTask::AddDependency
. -
Task
может иметь триггеры (Task::AddTrigger
). В таком случае он должен начать выполнение после того как хотя бы один триггер завершился. -
Task
может иметь один триггер по времени (Task::SetTimeTrigger
). В этом случае он должен начать выполнение если наступило времяdeadline
. -
В общем случае,
Executor::Submit
не должен начинать выполнение сразу, а дожидаться условия:- Или есть зависомости и все они выполнились
- Или один из триггеров выполнился
- Или выставлен
deadline
, и наступило времяdeadline
. Если у таска нет зависимостей, нет триггеров и не выставлен deadline, то его можно выполнять сразу же. ПокаSubmit
на таск не вызван, выполнять его нельзя.
-
Executor
предоставляет API для того, чтобы остановить выполнение.Executor::StartShutdown
- начинает процесс остановки.Task
-и, которые были посланы послеStartShutdown
, должны сразу переходить в состояние Canceled. Функция может быть вызвана несколько раз.Executor::WaitShutdown
- блокируется, пока Executor не остановится. Функция может быть вызвана несколько раз.Executor::~Executor
- неявно делает shutdown и дожидается завершения потоков.
Интерфейсы Task
и Executor
являются довольно многословными, во второй
части задания вам нужно будет реализовать класс Future
и несколько комбинаторов к нему.
-
Future
- этоTask
, у которого есть результат (какое-то значение). -
Интерфейсы комбинаторов определены в классе
Executor
:Invoke(fn)
- выполнитьfn
внутриExecutor
-а, результат вернуть черезFuture
.Then(input, fn)
- выполнитьfn
, после того как закончитсяinput
. ВозвращаетFuture
на результатfn
не дожидаясь выполненияinput
.WhenAll(vector<FuturePtr<T>>) -> FuturePtr<vector<T>>
- собирает результат несколькихFuture
в один.WhenFirst(vector<FuturePtr<T>>) -> FuturePtr<T>
- возвращает результат, который появится первым.WhenAllBeforeDeadline(vector<FuturePtr<T>>, deadline) -> FuturePtr<vector<T>>
- возвращает все результаты, которые успели появиться до deadline.