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

谢谢。编辑:这是一个使用 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();
}
有帮助吗?

解决方案

线程之间的错误共享是指 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 下,至少几页)。

通过定义函数对象,可能会出现错误共享,因为 Work 在堆上创建,并且该堆空间在线程内部使用。

这可能会导致几个 Work 实例相邻,因此可能会导致缓存线共享。

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

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

例如:

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;
};

这可以防止共享时出现的所有问题。

其他提示

我做了一个公平的研究,似乎没有银弹解决方案是错误的分享。这是我想出的(感谢克里斯托弗): 1)从两侧用未使用或更少使用的东西填充您的数据。 2)将数据复制到堆栈中并在完成所有努力工作后重新复制。 3)使用缓存对齐的内存分配。

我'没有完全安全的细节,但这是我的看法:

(1)您的简化示例被打破,因为Boost create_thread预期参考,您将传递临时。

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top