std :: thread的线程安全数组?

Luk*_*uka 4 c++ multithreading

mystruct** = (mystruct**)calloc(10, sizeof(mystruct*);
for(unsignd int i = 0; i < 10; i++)
    mystruct[i] = (mystruct*)calloc(10, sizeof(mystruct);

thread t[10];

for(unsigned int i = 0; i < 10; i++)
    t[i] = thread(new_piece, mystruct[i]);

for(unsigned int i = 0; i < 10; i++)
    t[i].join();
Run Code Online (Sandbox Code Playgroud)

该函数new_piece将数据写入mystruct[i].更具体地说,该函数会更改值mystruct[i][0], mystruct[i][1], ..., mystruct[9]

如何使上述操作线程安全?

Cou*_*per 6

正如评论中已经提到的那样,代码似乎是"线程安全的",但是它可能会受到"缓存抖动"的影响.

首先让我解释一下它是什么,以及为什么会在你的代码中发生这种情况:

什么是缓存抖动:

"缓存行"是从内存中提取到缓存中的最小数据单元.请注意,缓存行的大小是硬件属性.没有语言结构可以产生这个价值.现代CPU上的大多数缓存行大小为64字节.

当不同的线程访问恰好在内存中布局的不同变量(共享相同的高速缓存行)时,在具有多个CPU和一致高速缓存的系统上发生高速缓存抖动.这将导致过多的缓存未命中,从而导致性能下降.

(另请参阅wiki文章假共享),它指的是导致缓存抖动的使用模式)

(另见:Dr.Dobb的文章消除虚假分享)

为什么会在您的代码中发生:

从中返回的分配块callocmalloc尽可能小的分配块并紧密地组合在一起.也就是说,不同的分配块可以共享相同的高速缓存行(也参见man 3 calloc例如FreeBSD手册页).

C++的new运算符不会有所不同.

现在,我们通常不能假设从共享缓存行返回callocmalloc不共享内存块,您的代码可能会受到缓存抖动的影响,因为从不同线程同时访问的mystruct实例可能共享一个公共缓存行.

如何避免"缓存抖动":

基本上,您可以通过正确对齐数据来确保这一点(请参阅Wiki文章数据结构对齐).

有许多方法可以确保您的数据(mystruct)正确缓存对齐,例如:

  • 使用malloccalloc舍入分配请求,最多可达缓存行大小的倍数.

  • 利用posix函数:posix_memalign,在标题中声明<stdlib.h>(参见opengroup posix_memalign)

  • 在C++ 11中,您可以使用std::aligned_storage(请参阅此处的定义)

    std::aligned_storage提供type适合用作未初始化存储的地方,您可以存储您的对象.

    例如,定义缓存行对齐的存储,它是N个实例的数组:

    struct mystruct { ... };
    
    const std::size_t cache_line_size = 64;
    
    typename std::aligned_storage<sizeof(mystruct), cache_line_size>::type storage[N];
    
    Run Code Online (Sandbox Code Playgroud)

    有了它,您现在可以定义一个包含N个缓存行对齐mystruct的数组的类,它还提供了方便的访问器函数来修改和检索基础数组mystruct中位置i的值.IMO,这种方法比使用循环和calloc实例化mystruct阵列存储的容易出错的方法更受欢迎.

    请参阅此处的示例,稍作修改 - 完全符合您的需求.