繁体   English   中英

线程无法加入for_each并行C ++

[英]Thread unable to join for_each parallel c++

我在下面的代码中编写了一个示例代码来运行for_each的并行实例,但我无法加入线程。 我对并发编程还不算早,所以我不确定我是否做对了所有事情。

template <typename Iterator, typename F>
class for_each_block
{
public :
        void operator()(Iterator start, Iterator end, F f) {
            cout << this_thread::get_id << endl;
            this_thread::sleep_for(chrono::seconds(5));
            for_each(start, end, [&](auto& x) { f(x); });
    }
};

typedef unsigned const long int ucli;

template <typename Iterator, typename F>
void for_each_par(Iterator first, Iterator last, F f)
{
    ucli size = distance(first, last);
    if (!size)
        return;
    ucli min_per_thread = 4;
    ucli max_threads = (size + min_per_thread - 1) / min_per_thread;
    ucli hardware_threads = thread::hardware_concurrency();

    ucli no_of_threads = min(max_threads, hardware_threads != 0 ? hardware_threads : 4);

    ucli block_size = size / no_of_threads;

    vector<thread> vf(no_of_threads);
    Iterator block_start = first;
    for (int i = 0; i < (no_of_threads - 1); i++)
    {
        Iterator end = first;
        advance(end, block_size);
        vf.push_back(std::move(thread(for_each_block<Iterator, F>(),first,end,f)));
        first = end;
    }
    vf.push_back(std::move(thread(for_each_block<Iterator, F>(), first, last, f)));
    cout << endl;
    cout << vf.size() << endl;
    for(auto& x: vf)
    {
        if (x.joinable())
            x.join();
        else
            cout << "threads not joinable " << endl;
    }

    this_thread::sleep_for(chrono::seconds(100));
}

int main()
{
    vector<int> v1 = { 1,8,12,5,4,9,20,30,40,50,10,21,34,33 };
    for_each_par(v1.begin(), v1.end(), print_type<int>);
return 0;
}

在上面的代码中,我无法连接线程。 我也尝试过使用异步期货,但我还是一样。 我在这里想念什么吗?

非常感谢您的帮助,在此先谢谢您。

vector<thread> vf(no_of_threads);

这将创建一个带有no_of_threads默认初始化线程的向量。 由于它们是默认初始化的,所以它们都不是可连接的。 您可能打算这样做:

vector<thread> vf;
vf.reserve(no_of_threads);

PS: std::move在临时上是多余的:); 考虑更改此:

vf.push_back(std::move(thread(for_each_block<Iterator, F>(), first, last, f)));

对此:

vf.emplace_back(for_each_block<Iterator, F>(), first, last, f);

这可能是有趣的,也可能不是。 我可以将代码重构为使用我认为更惯用的方法。 我并不是说您的方法是错误的,但是由于您正在学习线程管理,所以我认为您可能会对其他可能的方法感兴趣。

随时提出适当的问题。 内联评论:

#include <vector>
#include <chrono>
#include <thread>
#include <mutex>
#include <iomanip>
#include <future>

using namespace std;

//
// provide a means of serialising writing to a stream.
//
struct locker
{
    locker() : _lock(mutex()) {}

    static std::mutex& mutex() { static std::mutex m; return m; }
    std::unique_lock<std::mutex> _lock;
};
std::ostream& operator<<(std::ostream& os, const locker& l) {
    return os;
}

//
// fill in the missing work function
//
template<class T>
void print_type(const T& t) {
    std::cout << locker() << hex << std::this_thread::get_id() << " : " << dec << t << std::endl;
}

// put this in your personable library.
// the standards committee really should have given us ranges by now...
template<class I1, class I2>
struct range_impl
{
    range_impl(I1 i1, I2 i2) : _begin(i1), _end(i2) {};

    auto begin() const { return _begin; }
    auto end() const { return _end; }

    I1 _begin;
    I2 _end;
};

// distinct types because sometimes dissimilar iterators are comparable
template<class I1, class I2>
auto range(I1 i1, I2 i2) {
    return range_impl<I1, I2>(i1, i2);
}

//
// lets make a helper function so we can auto-deduce template args
//
template<class Iterator, typename F>
auto make_for_each_block(Iterator start, Iterator end, F&& f)
{
    // a lambda gives all the advantages of a function object with none
    // of the boilerplate.
    return [start, end, f = std::move(f)] {
        cout << locker() << this_thread::get_id() << endl;
        this_thread::sleep_for(chrono::seconds(1));

        // let's keep loops simple. for_each is a bit old-skool.
        for (auto& x : range(start, end)) {
            f(x);
        }
    };
}


template <typename Iterator, typename F>
void for_each_par(Iterator first, Iterator last, F f)
{
    if(auto size = distance(first, last))
    {
        std::size_t min_per_thread = 4;
        std::size_t max_threads = (size + min_per_thread - 1) / min_per_thread;
        std::size_t hardware_threads = thread::hardware_concurrency();

        auto no_of_threads = min(max_threads, hardware_threads != 0 ? hardware_threads : 4);

        auto block_size = size / no_of_threads;

        // futures give us two benefits:
        // 1. they automatically transmit exceptions
        // 2. no need for if(joinable) join. get is sufficient
        //
        vector<future<void>> vf;
        vf.reserve(no_of_threads - 1);
        for (auto count = no_of_threads ; --count ; )
        {
            //
            // I was thinking of refactoring this into std::generate_n but actually
            // it was less readable.
            //
            auto end = std::next(first, block_size);
            vf.push_back(async(launch::async, make_for_each_block(first, end, f)));
            first = end;
        }
        cout << locker() << endl << "threads: " << vf.size()  << " (+ main thread)" << endl;

        //
        // why spawn a thread for the remaining block? we may as well use this thread
        //
        /* auto partial_sum = */ make_for_each_block(first, last, f)();

        // join the threads
        // note that if the blocks returned a partial aggregate, we could combine them
        // here by using the values in the futures.
        for (auto& f : vf) f.get();
    }
}

int main()
{
    vector<int> v1 = { 1,8,12,5,4,9,20,30,40,50,10,21,34,33 };
    for_each_par(v1.begin(), v1.end(), print_type<int>);
    return 0;
}

样本输出:

0x700000081000
0x700000104000

threads: 3 (+ main thread)
0x700000187000
0x100086000
0x700000081000 : 1
0x700000104000 : 5
0x700000187000 : 20
0x100086000 : 50
0x700000081000 : 8
0x700000104000 : 4
0x700000187000 : 30
0x100086000 : 10
0x700000081000 : 12
0x700000104000 : 9
0x700000187000 : 40
0x100086000 : 21
0x100086000 : 34
0x100086000 : 33
Program ended with exit code: 0

请在此处说明std :: move: [start, end, f = std::move(f)] {...};

这是c ++ 14中提供的一种受欢迎的语言功能。 捕获块内的f = std::move(f)等效于: decltype(f) new_f = std::move(f)只是新变量称为f而不是new_f 它允许我们std::move对象到lambda而不是复制它们。

对于大多数函数对象而言,这无关紧要-但是有些对象可能很大,这使编译器有机会使用移动而不是副本(如果有)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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