7

我有小但经常使用的功能对象。每个线程都有自己的副本。一切都是静态分配的。副本不共享任何全局或静态数据。我是否需要保护这些对象免受虚假共享?

谢谢你。编辑:这是一个使用 Boost.Threads 的玩具程序。字段数据是否会发生虚假共享?

#include <boost/thread/thread.hpp>

struct Work {
    void operator()() {
        ++data;
    }

    int data;
};

int main() {
    boost::thread_group threads;
    for (int i = 0; i < 10; ++i)
        threads.create_thread(Work());
    threads.join_all();
}
4

3 回答 3

6

线程之间的错误共享是指 2 个或更多线程使用相同的高速缓存行。

例如:

struct Work {
    Work( int& d) : data( d ) {}
    void operator()() {
        ++data;
    }

    int& data;
};

int main() {
    int false_sharing[10] = { 0 };
    boost::thread_group threads;
    for (int i = 0; i < 10; ++i)
        threads.create_thread(Work(false_sharing[i]));
    threads.join_all();

    int no_false_sharing[10 * CACHELINE_SIZE_INTS] = { 0 };
    for (int i = 0; i < 10; ++i)
        threads.create_thread(Work(no_false_sharing[i * CACHELINE_SIZE_INTS]));
    threads.join_all();
}

第一个块中的线程确实遭受错误共享。第二个块中的线程没有(感谢CACHELINE_SIZE)。

堆栈上的数据总是“远离”其他线程。(例如在windows下,至少有几页)。

使用您定义的函数对象,可能会出现错误共享,因为Workget 的实例是在堆上创建的,并且该堆空间在线程内部使用。

这可能会导致多个Work实例相邻,因此可能会导致共享高速缓存行。

但是......您的样本没有意义,因为数据从未在外部接触过,因此不必要地诱导了错误共享。

防止此类问题的最简单方法是将您的“共享”数据本地复制到堆栈上,然后处理堆栈副本。完成工作后,将其复制回输出 var。

例如:

struct Work {
    Work( int& d) : data( d ) {}
    void operator()()
    {
        int tmp = data;
        for( int i = 0; i < lengthy_op; ++i )
           ++tmp;
        data = tmp;
    }

    int& data;
};

这可以防止所有共享问题。

于 2010-07-26T09:55:12.887 回答
2

我做了一些研究,似乎没有解决虚假分享的灵丹妙药。这是我想出的(感谢克里斯托弗):1)用未使用或不常用的东西从两边填充数据。2)将您的数据复制到堆栈中,并在完成所有艰苦工作后将其复制回来。3) 使用缓存对齐的内存分配。

于 2010-07-26T22:55:54.137 回答
0

我对细节并不完全安全,但这是我的看法:

(1)您的简化示例被破坏了,因为 boostcreate_thread需要一个参考,您传递了一个临时的。

(2) 如果您要 vector<Work>在每个线程中使用一个项目,或者将它们按顺序存储在内存中,则会发生错误共享。

于 2010-07-26T09:53:15.660 回答