如何在 Cilk Plus 中组织非线程安全资源池(每个工作人员一个资源)?

Posted

技术标签:

【中文标题】如何在 Cilk Plus 中组织非线程安全资源池(每个工作人员一个资源)?【英文标题】:How to organize a pool of non thread-safe resources in Cilk Plus (one resource per worker)? 【发布时间】:2015-07-19 12:12:57 【问题描述】:

我有一个串行代码,我想使用 Cilk Plus 进行并行化;主循环对不同的数据集重复调用一个处理函数,所以迭代是相互独立的,除了使用非线程安全的资源,它被封装到一个类(比如nts)中一个外部库,它接受一个文件名并对其进行 I/O。

如果我使用的是 OpenMP,我会创建一个资源池,其中包含与我拥有的线程一样多的资源,并根据线程 ID 访问这些资源:

std::vector<nts> nts_pool;
for (std::size_t i0; i < omp_get_num_threads(); ++i)
    nts_pool.push_back(nts);

nts_pool[omp_get_thread_num()].do_stuff();  // from inside the task

使用 Cilk Plus,我可以使用 __cilkrts_get_nworkers()__cilkrts_get_worker_number() API 做同样多的事情,但从英特尔论坛上的多个帖子中,我了解到这被认为是解决问题的错误解决方案,并且是正确的解决方案将是使用持有人超对象。

现在,持有者解决方案看起来确实不错,除了我真的希望创建的视图与我拥有的工作线程一样多。也就是说,对于 3 个工作线程,我想要 3 个对象而不是更多。理由是,正如我所说,资源是由第三方库提供的,构建起来非常昂贵,而且我必须在之后处理生成的文件,所以越少越好。

不幸的是,我发现不是为每个工作人员创建一个视图并保持它直到同步,而是持有者根据我不理解的逻辑以某种方式创建和销毁视图,并且似乎没有影响这种行为的方法。

是否有可能让持有人按照我想要的方式行事,如果没有,我的问题的惯用 Cilk Plus 解决方案是什么?

这是我用来调查持有者的程序,请注意,它在一次运行期间在我的测试机器上创建多达 50 个视图,这些视图似乎是随机分配和销毁的:

#include <iostream>
#include <atomic>

#include <cilk/cilk.h>
#include <cilk/holder.h>
#include <cilk/reducer_ostream.h>
#include <cilk/cilk_api.h>

cilk::reducer_ostream *hyper_cout;

class nts 
public:
    nts() : tag_std::to_string(++id_) 
        *hyper_cout << "NTS constructor: " << tag_ << std::endl;
    
    ~nts() 
        *hyper_cout << "NTS destructor: " << tag_ << std::endl;
    
    void print_tag() 
        *hyper_cout << "NTS tag: " << tag_ << std::endl;
    
    static void is_lock_free() 
        *hyper_cout << "Atomic is lockfree: " << id_.is_lock_free() << std::endl;
    
private:
    const std::string tag_;
    static std::atomic_size_t id_;
;

std::atomic_size_t nts::id_0;

class nts_holder 
public:
    void print_tag()  nts_().print_tag(); 
private:
    cilk::holder<nts> nts_;
;

int main() 

    __cilkrts_set_param("nworkers", "4");

    cilk::reducer_ostream coutstd::cout;
    hyper_cout = &cout;

    *hyper_cout << "Workers: " <<  __cilkrts_get_nworkers() << std::endl;
    nts::is_lock_free();

    nts_holder ntsh;
    ntsh.print_tag();

    for (std::size_t i0; i < 1000; ++i) 
        cilk_spawn [&] () 
            ntsh.print_tag();
         ();
    

    cilk_sync;

    return 0;


【问题讨论】:

【参考方案1】:

您是正确的,持有人是解决这个特定问题的诱人但低效的解决方案。如果您的程序正确地使用了每个工人一个插槽的插槽数组,那么在这种情况下使用__cilkrts_get_nworkers()__cilkrts_get_worker_number() API 确实没有任何问题。我们确实不鼓励使用它们;更喜欢编写忽略数字工作者的 Cilk Plus 代码,因为这样通常可以更好地扩展。但是,在某些情况下,包括这种情况,为每个工作人员创建一个插槽是最好的策略。

【讨论】:

非常感谢您的明确答复;在这种情况下,我会选择我的一系列插槽解决方案!但是,如果您能进一步澄清为什么持有人会以他们目前的方式行事,我将不胜感激。这对我来说特别违反直觉,因为想到的最简单的实现是一组插槽...... :-) 持有者行为方式的主要原因是历史性的:持有者建立在 reducer 机制之上,它保留了关联操作的要求,即使所讨论的操作通常是无操作。正如您所发现的,这通常不是一个有用的属性,但它可以用于,例如,检测一个值没有改变,因此可以在不重新计算的情况下使用它。当允许交换性时,我们正在为 reducer 和 holder 寻找更有效的(即 slot-per-worker)机制。

以上是关于如何在 Cilk Plus 中组织非线程安全资源池(每个工作人员一个资源)?的主要内容,如果未能解决你的问题,请参考以下文章

将 Cilk Plus 与 MinGW-w64 (gcc 4.9.2) 一起使用?

你知道如何安全正确的关闭线程池吗?

JedisPool 网络连接池

Spring学习记录6——ThreadLocal简介

产品经理问我:手动创建线程不香吗,为什么非要用线程池呢?

如何让Task在非线程池线程中执行?