std :: packaged_task с std :: placeholder - PullRequest
       9

std :: packaged_task с std :: placeholder

0 голосов
/ 05 января 2019

ОСНОВНОЕ РЕДАКТИРОВАНИЕ ДЛЯ УПРОЩЕНИЯ КОДА (и решено)

Я хотел бы иметь возможность создать упакованную задачу, у которой есть свободный несвязанный аргумент, который я затем добавлю во время вызова упакованной задачи.

В этом случае я хочу, чтобы первый аргумент функции (типа size_t) был несвязанным.

Вот рабочий минимальный пример (это было решение):

#include <vector>
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>
#include <cstdlib>
#include <cstdio>

//REV: I'm trying to "trick" this into for double testfunct( size_t arg1, double arg2), take enqueue( testfunct, 1.0 ), and then internally, execute
// return testfunct( internal_size_t, 1.0 )

template<typename F, typename... Args>
auto enqueue(F&& f, Args&&... args) 
  -> std::future<typename std::result_of<F(size_t, Args...)>::type>
{
  using return_type = typename std::result_of<F(size_t, Args...)>::type;

  //REV: this is where the error was, I was being stupid and thinking this task_contents which would be pushed to the queue should be same (return?) type as original function? Changed to auto and everything worked... (taking into account Jans's result_type(size_t) advice into account.
  //std::function<void(size_t)> task_contents = std::bind( std::forward<F>(f), std::placeholders::_1, std::forward<Args>(args)... );
  auto task_contents = std::bind( std::forward<F>(f), std::placeholders::_1, std::forward<Args>(args)... );

  std::packaged_task<return_type(size_t)> rawtask(
                          task_contents );

  std::future<return_type> res = rawtask.get_future();

  size_t arbitrary_i = 10;
  rawtask(arbitrary_i);
  return res;
}


double testfunct( size_t threadidx, double& arg1 )
{
  fprintf(stdout, "Double %lf Executing on thread %ld\n", arg1, threadidx );
  std::this_thread::sleep_for( std::chrono::milliseconds(1000) );
  return 10; //true;
}

int main()
{
  std::vector<std::future<double>> myfutures;

  for(size_t x=0; x<100; ++x)
    {
      double a=x*10;
      myfutures.push_back(
              enqueue( testfunct, std::ref(a) )
                  );
    }

  for(size_t x=0; x<100; ++x)
    {
      double r = myfutures[x].get();
      fprintf(stdout, "Got %ld: %f\n", x, r );
    }
}

Ответы [ 2 ]

0 голосов
/ 05 января 2019

Код не очень хорошо отформатирован, но решение.

Во-первых, вы должны обернуть результаты при создании лямбды, а не передавать функции, которые могут возвращать что угодно. Но если вы хотите использовать общий указатель на задачу, это работает.

В прототипе:

std::future<void> enqueue(std::function<void(size_t)> f);

using Task = std::function<void(size_t)>;
// the task queue
std::queue<Task> tasks;

std::optional<Task> pop_one();

Реализация становится:

ThreadPool::ThreadPool(size_t threads)
    :   stop(false)
{
    for(size_t i = 0;i<threads;++i)
        workers.emplace_back(
        [this,i]
            {
                for(;;)
                {
                auto task = pop_one();
                if(task)
                {
                    (*task)(i);
                }
                else break;
                }
            }
        );
}

std::optional<ThreadPool::Task> ThreadPool::pop_one()
{
    std::unique_lock<std::mutex> lock(this->queue_mutex);
    this->condition.wait(lock,
        [this]{ return this->stop || !this->tasks.empty(); });
    if(this->stop && this->tasks.empty())
    {
        return std::optional<Task>();
    }
    auto task = std::move(this->tasks.front()); //REV: this moves into my thread the front of the tasks queue.
    this->tasks.pop();

    return task;
}

template<typename T>
std::future<T> ThreadPool::enqueue(std::function<T(size_t)> fun)
{
    auto task = std::make_shared< std::packaged_task<T(size_t)> >([=](size_t size){return fun(size);});

    auto res = task->get_future();
    {
        std::unique_lock<std::mutex> lock(queue_mutex);

        // don't allow enqueueing after stopping the pool
        if(stop)
        {
            throw std::runtime_error("enqueue on stopped ThreadPool");
        }

        tasks.emplace([=](size_t size){(*task)(size);});
    }
    condition.notify_one();
    return res;
}

И теперь вы можете иметь свой основной:

int main()
{
  size_t nthreads=3;
  ThreadPool tp(nthreads);
  std::vector<std::future<bool>> myfutures;

  for(size_t x=0; x<100; ++x)
    {
      myfutures.push_back(
          tp.enqueue<bool>([=](size_t threadidx) {return funct(threadidx, (double)x * 10.0);}));
    }

  for(size_t x=0; x<100; ++x)
    {
      bool r = myfutures[x].get();
      std::cout << "Got " << r << "\n";
    }
}

Теперь существует явный тип возвращаемого значения при переносе лямбды, поскольку тип возвращаемого значения является шаблонным.

0 голосов
/ 05 января 2019

Основные проблемы на ThreadPool::enqueue:

std::function<void(size_t)> task1 = std::bind( std::forward<F>(f), std::placeholders::_1, std::forward<Args>(args)... );

Здесь тип task1 равен std::function<void(std::size_t)>, но результат std::bind при оценке с помощью funct может быть преобразован в std::function<bool(std::size_t)>, и хотя @TC указывает, вы можете присвоить результат от bind до task1, чтобы пройти от task1 до std::make_shared, вам необходимо соблюдать return_type, что у вас есть.

Измените вышеприведенное на:

std::function<return_type(size_t)> task1 = std::bind( std::forward<F>(f), std::placeholders::_1, std::forward<Args>(args)... );

Теперь то же самое для:

auto task = std::make_shared< std::packaged_task<return_type()> >( task1 );

но в данном случае это тип параметра, который отсутствует. Измените его на:

auto task = std::make_shared< std::packaged_task<return_type(std::size_t)> >( task1 );

ThreadPool::tasks хранить функциональные объекты типа std::function<void(std::size_t)>, но вы храните лямбда-выражения, которые не получают аргументов. Измените tasks.emplace(...) на:

tasks.emplace([task](std::size_t a){ (*task)(a); });
...