2016-05-18 2 views
1

Я пытаюсь использовать простой пример пула потоков из книги Энтони Уильямса «Параллельность C++ в действии». Я даже нашел здесь код (класс thread_pool) в одном из сообщений: Synchronizing tasks , но у меня другой вопрос. Я хотел бы представить задачу (функция-член) в очереди со следующей подписью:C++ 11 thread pool - задачи с входными параметрами

class A; 
class B; 
bool MyClass::Func(A*, B*); 

Как бы мне нужно изменить thread_pool класс, или как я могу упаковать функции в некоторой пустоте F(), который предполагается использовать в качестве примера в этом примере? Вот наиболее соответствующая часть класса для меня (подробности смотрите по ссылке выше):

class thread_pool 
{ 
    thread_safe_queue<std::function<void()> work_queue; // bool MyClass::Func(a,b) ?? 

    void worker_thread() { 
    while(!done) {   
    std::function<void()> task; 
    if(work_queue.try_pop(task)) { 
    task(); // how should my function MyClass::Func(a,b) be called here?      
    } 
    else { 
    std::this_thread::yield(); 
    } 
    } 
    } 

    // -- Submit a task to the thread pool 
    template <typename FunctionType> 
    void submit(FunctionType f) { 
    work_queue.push(std::function<void()>(f)); // how should bool MyClassFunc(A*, B*) be submitted here 
} 

}

И, наконец, как я могу назвать представить функцию в моем коде?

Большое спасибо за вашу помощь (к сожалению, я еще не очень опытен в использовании всех возможностей C++ 11, что, вероятно, также объясняет, почему мне нужна помощь здесь, но ответ на этот вопрос должен был бы начать с :)).

ответ

3

Вы должны привязать параметры к значению при вставке задачи в очередь. Это означает, что вам нужно создать оболочку для вашей функции, которая хранит значения для this и значения для двух параметров функции. Есть много способов сделать это, например. лямбда-функции или std::bind.

work_queue.push_back([obj, a, b]() {obj->Func(a,b)}); 
work_queue.push_back(std::bind(&MyClass::Func, obj, a, b)); 

Ваша функция отправки должна принимать эти параметры и создавать привязку, например.

template<typename F, typename... Args> 
void submit(F f, Args&&... args) { 
    work_queue.push_back(std::bind(f, std::forward<Args>(args)...)); 
} 

Для функции члена может быть удобно создать специальную перегрузку для функций-членов и объектов.

+0

Мне интересно, какой контроль над ссылками при использовании связующего ... Вкладыши всегда хранят копии оригинального типа (это необходимо для работы)? В этом случае у вас есть небольшое снижение производительности при дублировании, но это хорошее решение да. –

+0

@WernerErasmus 'std :: bind', а также лямбда-захват копируют свои аргументы. Если вы хотите хранить ссылки, вы должны сделать это явным путем захвата по ссылке '[& a, & b]() {}' или используя 'std :: bind (f, std :: ref (a), std :: ref (b)). – Jens

+0

Копирование - это безопасный вариант, но есть также случай, когда вы не используете bind (как и я), хотя это может быть преждевременная оптимизация. Тем не менее, этот тип кода будет использоваться «в целом», и поэтому может потребоваться оптимизация для непредвиденного (например, большого аргумента). –

0

Я написал что-то, что делает что-то (очень) похожее на это раньше. Я отправлю код здесь, и вы можете посмотреть. GenCmd - это оболочка функции. Очередь выглядит так и используется/определена в Impl (код опущен). Вам нужно только посмотреть на реализацию GenCmd, так как это содержит необходимую работу.

ConcurrentQueue<std::unique_ptr<Cmd>> cqueue_; 

Я завернут зЬй :: функция <> полиморфны в очереди. std_utility содержит make_index_sequence, который используется для извлечения значений из кортежа (google make_index_sequence, чтобы найти реализацию где-нибудь, если это еще не часть вашей std-библиотеки).

#include <functional> 
#include <memory> 
#include <iostream> 
#include <utility> 
#include <boost/noncopyable.hpp> 

class CmdExecutor : public boost::noncopyable 
{ 
    public: 
    CmdExecutor(std::ostream& errorOutputStream); 
    ~CmdExecutor(); 

    template <class Receiver, class ... FArgs, class ... CArgs > 
     void process(Receiver& receiver, void (Receiver::*f)(FArgs...), CArgs&&... args) 
    { 
     process(std::unique_ptr<Cmd>(new GenCmd<void(Receiver,FArgs...)>(f, receiver, std::forward<CArgs>(args)...))); 
    } 

    private: 
    class Cmd 
    { 
     public: 
     virtual void execute() = 0; 
     virtual ~Cmd(){} 
    }; 

    template <class T> class GenCmd; 

    template <class Receiver, class ... Args> 
    class GenCmd<void(Receiver, Args...)> : public Cmd 
    { 
     public: 
     template <class FuncT, class ... CArgs> 
     GenCmd(FuncT&& f, Receiver& receiver, CArgs&&... args) 
      : call_(std::move(f)), 
      receiver_(receiver), 
      args_(args...) 
     { 
     } 
     //We must convert references to values... 

     virtual void execute() 
     { 
      executeImpl(std::make_index_sequence<sizeof...(Args)>{}); 
     } 

     private: 
     template <std::size_t ... Is> 
     void executeImpl(std::index_sequence<Is...>) 
     { 
      // We cast the values in the tuple to the original type (of Args...) 
      call_(receiver_, static_cast<Args>(std::get<Is>(args_))...); 
     } 

     std::function<void(Receiver&, Args...)> call_; 
     Receiver& receiver_; 
     // NOTE: 
     // References converted to values for safety sake, as they are likely 
     // to not be around when this is executed in other context. 
     std::tuple<typename std::remove_reference<Args>::type...> args_; 
    }; 

    void process(std::unique_ptr<Cmd> command); 

    class Impl; 
    Impl* pimpl_; 
}; 

Это в основном используется следующим образом:

... 
CmdExecutor context_; 
... 

void MyClass::myFunction() 
{ 
    ArgX x; 
    ArgY y; 

    context_.process(*this, &MyClass::someFunction, x, y); 
} 

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

Примечание: Если вызывается функция принимает RValue (Arg & &), сохраненный тип отливают к исходному типу, поэтому вызывает движение, и оказание применимый аргумент команды (который будет вызываться только один раз) пусто (это намерение, по крайней мере - непроверено ...)