使用多个资源的线程安全类

时间:2017-11-02 23:55:46

标签: c++ multithreading

在C ++ 11代码中(可能使用OpenMP),我希望在利用CPU内核提供的并行性时处理多个资源(因此使用线程)。我的问题是保护类的“上下文”免于并发,但我不确定如何达到这个目标!它更像是一个设计问题,而不是纯粹的C ++代码问题,因为目前在单个线程上,我的代码运行得非常完美。现在我想让它适应多线程。

假设我的CPU有4个内核和4 * N资源需要处理。我想要获得的是:启动4个线程,每个线程处理N个资源。这样说,它似乎是无并发的,但不幸的是它不是!

我目前的具体案例是将文件中的资源加载到c ++类中,但是你必须将我的问题视为更常见的情况。

根据上下文,我的意思是:存储在变量成员中的c ++类的当前状态。通过资源,我的意思是(在我的具体情况下):一个文件(ascii或二进制不重要)来读取和存储数据到C ++类。资源是异构的(例如声音,纹理,但它们究竟是什么并不是非常重要)。资源加载的顺序对我来说并不重要(以及平衡的CPU费用)。

我当前的代码,在单个线程上,对于单一类型的资源,看起来像以下伪代码:

class Loader { ... };
class Resource { ... };
string filenames[N] = { "file1.txt", "file2.txt", ... "fileN.txt" };
Resource resources[N];

for (i = 0; i < N; ++i)
{
    Loader.loadFromFile(filename[i], &resources[i]);
}

对于多线程,使用OpenMP形式,我想写一些类似的东西:

#pragma omp parallel for
for (i = 0; i < N; ++i)
{
    Loader.loadFromFile(filename[i], &resources[i]);
}

不幸的是,我很害怕它不会像伪代码中那样容易,因为类Loader中存储的“上下文”使得它不是线程安全的。这是我目前的实施(简化):

template <class T>
class ILoader
{
public:
    ...
    virtual void loadFromFile(std::string const& filename, T &resource) = 0;
};

class Resource1Loader : public ILoader<Resource1>
{
public:
    virtual void loadFromFile(std::string const& filename, Resource1 &resource) override
    {
        m_infile.open(filename, ...);
        ...
        resource.xxx = m_infile.read(...);
    }

private:
    std::ifstream m_infile; // The context
};

所有类型的资源(Resource1,Resource2 ... ResourceN)都很长。

注意:为什么我实现这样的类?使LoaderManager包含一个加载器列表(==一个哈希表,其中key是文件扩展名,值是加载器)和一个包含Loaders返回的资源列表的ResourceManager。

m_infile是我的“非线程安全上下文”。这是我读取文件的文件描述符。我被封锁了如何保护它。

这是我的解决方案,但我不喜欢它们:

  1. 使用互斥锁来保护loadFromFile()的整个代码,我将失去所有的并行性。
  2. 如果我将std :: ifstream m_infile修改为std :: ifstream m_infile [MAX_THREADS]并使用OpenMP线程ID(作为参数传递),我认为这太过分了。
  3. (类似于2.)创建N个Loaders的临时实例(其中N =线程数)。就我而言,这意味着我的LoaderManager必须克隆其加载器。
  4. 将m_infile作为loadFromFile的参数(或资源的私有成员)传递。
  5. 使用1.的想法,因为我的资源是异构的:
  6. 而不是:

    #pragma omp parallel for
    for (i = 0; i < X; ++i)
    {
        LoaderR1.loadFromFile(filenameR1[i], &resourcesR1[i]);
    }
    #pragma omp parallel for
    for (i = 0; i < Y; ++i)
    {
        LoaderR2.loadFromFile(filenameR2[i], &resourcesR2[i]);
    }
    ...
    

    要做到这一点:

    #pragma omp parallel for
    for (i = 0; i < min(X, Y); ++i)
    {
        LoaderR1.loadFromFile(filenameR1[i], &resourcesR1[i]);
        LoaderR2.loadFromFile(filenameR2[i], &resourcesR2[i]);
    }
    

    其中X!= Y且其中:

    Resource1 resourcesR1[X]; string filenamesR1[X] = { "fileR1_1.txt", "fileR1_2.txt", ... "fileR1_X.txt" };
    Resource2 resourcesR2[Y]; string filenamesR2[Y] = { "fileR2_1.txt", "fileR2_2.txt", ... "fileR2_Y.txt" };
    ...
    
    ResourceN resourcesRn[Z]; string filenamesRn[Z] = { "fileRn_1.txt", "fileRn_2.txt", ... "fileRn_Z.txt" };
    

    这意味着:每个线程加载N Resource1和N Resource2。虽然互斥锁保护R1免受R1(以及R2对R2)的影响,但它允许将R1和R2加载在一起。

    那么如何修改我的课程以达到我的目标呢?注意:我想在运行时加载资源,但在运行时没有“动态”加载,如OpenGL loading resource in separated thread中所述(虽然很有趣,但它不是我正在看的)。

    提前致谢!

1 个答案:

答案 0 :(得分:0)

没有理由认为m_infile必须是成员变量。只需将加载程序代码更改为:

virtual void loadFromFile(std::string const& filename, Resource1 &resource) override
{
    std::ifstream infile.open(filename, ...);
    ...
    resource.xxx = infile.read(...);
}

您还没有清楚地解释为什么您认为ifstream对象需要成为成员变量,但实际上它不太可能是必需的。