简体   繁体   中英

Using std::thread and std::function from a std::bind with a function with arguments and a non-void return

Let's say we have a function odd which is a bool(int) function. I'd like to execute this function in parallel but with different parameter (differ numbers).

bool odd(int i) { return (((i&1)==1)?true:false); }

Here's the code I'm trying to use (which works but has a wart).

std::size_t num = 256;
std::vector<bool> results(num);
std::vector<std::function<bool(int)>> funcs(num);
std::vector<std::packaged_task<bool(int)>> tasks(num);
std::vector<std::future<bool>> futures(num);
std::vector<std::thread> threads(num);
for (std::size_t i = 0; i < num; i++) {
    results[i] = false;
    funcs[i] = std::bind(odd, static_cast<int>(i));
    tasks[i] = std::packaged_task<bool(int)>(funcs[i]);
    futures[i] = tasks[i].get_future();
    threads[i] = std::thread(std::move(tasks[i]),0); // args ignored
}
for (std::size_t i = 0; i < num; i++) {
    results[i] = futures[i].get();
    threads[i].join();
}
for (std::size_t i = 0; i < num; i++) {
    printf("odd(%d)=%s\n", i, (results[i]?"true":"false"));
}

I'd like to get rid of the arguments to the thread creation, as they are dependent on the argument types of the function bool(int) . I'd like to make a function template of this code and be able to make a massive parallel function executor.

template <typename _returnType, typename ..._argTypes>
void exec_and_collect(std::vector<_returnType>& results,
                      std::vector<std::function<_returnType(_argTypes...)>> funcs) {
    std::size_t numTasks = (funcs.size() > results.size() ? results.size() : funcs.size());
    std::vector<std::packaged_task<_returnType(_argTypes...)>> tasks(numTasks);
    std::vector<std::future<_returnType>> futures(numTasks);
    std::vector<std::thread> threads(numTasks);
    for (std::size_t h = 0; h < numTasks; h++) {
        tasks[h] = std::packaged_task<_returnType(_argTypes...)>(funcs[h]);
        futures[h] = tasks[h].get_future();
        threads[h] = std::thread(std::move(tasks[h]), 0); // zero is a wart
    }
    // threads are now running, collect results
    for (std::size_t h = 0; h < numTasks; h++) {
        results[h] = futures[h].get();
        threads[h].join();
    }
}

Then called like this:

std::size_t num = 8;
std::vector<bool> results(num);
std::vector<std::function<bool(int)>> funcs(num);
for (std::size_t i = 0; i < num; i++) {
    funcs[i] = std::bind(odd, static_cast<int>(i));
}
exec_and_collect<bool,int>(results, funcs);

I'd to remove the zero in the std::thread(std::move(task), 0); line since it's completely ignored by the thread. If I do completely remove it, the compiler can't find the arguments to pass to the thread create and it fails.

You could just not be micromanaging/control freak in the generic code. Just take any task returntype() and let the caller handle the binding of arguments:

Live On Coliru

#include <thread>
#include <future>
#include <iostream>
#include <vector>
#include <functional>

bool odd(int i) { return (((i&1)==1)?true:false); }

template <typename _returnType>
void exec_and_collect(std::vector<_returnType>& results,
                      std::vector<std::function<_returnType()>> funcs
                    ) {
    std::size_t numTasks = std::min(funcs.size(), results.size());

    std::vector<std::packaged_task<_returnType()>> tasks(numTasks);
    std::vector<std::future<_returnType>> futures(numTasks);

    std::vector<std::thread> threads(numTasks);

    for (std::size_t h = 0; h < numTasks; h++) {
        tasks[h] = std::packaged_task<_returnType()>(funcs[h]);
        futures[h] = tasks[h].get_future();
        threads[h] = std::thread(std::move(tasks[h]));
    }

    // threads are now running, collect results
    for (std::size_t h = 0; h < numTasks; h++) {
        results[h] = futures[h].get();
        threads[h].join();
    }
}

int main() {
    std::size_t num = 8;
    std::vector<bool> results(num);
    std::vector<std::function<bool()>> funcs(num);

    for (std::size_t i = 0; i < num; i++) {
        funcs[i] = std::bind(odd, static_cast<int>(i));
    }
    exec_and_collect<bool>(results, funcs);
}

Note this is a quick job, I've seen quite a few things that are overly specific here still.

  • In particular all the temporary collections are just paper weight (you even move each tasks[h] out of the vector even before moving to the next task, so why keep a vector of dead bits?)
  • There's no scheduling at all; you just create new threads willy nilly. That's not gonna scale (also, you want pluggable pooling models; see the Executor specifications and Boost Async's implementation of these)

UPDATE

A somewhat more cleaned up version that demonstrates what unneeded dependencies can be shed:

  • no temporary vectors of packaged tasks/threads
  • no assumption/requirement to have std::function<> wrapped tasks (this removes dynamic allocations and virtual dispatch internally in the implementation)
  • no requirement that the results must be in a vector (in fact, you can collect them anywhere you want using a custom output iterator)
  • move-awareness (this is arguably a "complicated" part of the code seeing that there is no std::move_transform , so go the extra mile using std::make_move_iterator

Live On Coliru

#include <thread>
#include <future>
#include <iostream>
#include <vector>
#include <algorithm>

#include <boost/range.hpp>

bool odd(int i) { return (((i&1)==1)?true:false); }

template <typename Range, typename OutIt>
void exec_and_collect(OutIt results, Range&& tasks) {
    using namespace std;
    using T = typename boost::range_value<Range>::type;
    using R = decltype(declval<T>()());

    auto tb = std::make_move_iterator(boost::begin(tasks)),
         te = std::make_move_iterator(boost::end(tasks));

    vector<future<R>> futures;

    transform(
            tb, te,
            back_inserter(futures), [](auto&& t) {
                std::packaged_task<R()> task(std::forward<decltype(t)>(t));
                auto future = task.get_future();
                thread(std::move(task)).detach();
                return future;
            });

    // threads are now running, collect results
    transform(begin(futures), end(futures), results, [](auto& fut) { return fut.get(); });
}

#include <boost/range/irange.hpp>
#include <boost/range/adaptors.hpp>

using namespace boost::adaptors;

int main() {
    std::vector<bool> results;
    exec_and_collect(
            std::back_inserter(results), 
            boost::irange(0, 8) | transformed([](int i) { return [i] { return odd(i); }; })
        );

    std::copy(results.begin(), results.end(), std::ostream_iterator<bool>(std::cout << std::boolalpha, "; "));
}

Output

false; false; false; false; false; false; false; false;

Note that you could indeed write

    exec_and_collect(
            std::ostream_iterator<bool>(std::cout << std::boolalpha, "; "), 
            boost::irange(0, 8) | transformed([](int i) { return [i] { return odd(i); }; })
        );

and do without any results container :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM