考虑在x86 CPU上进行单个内存访问(单个读取或单个写入,而不是读取或写入)SSE指令.该指令访问16字节(128位)的存储器,访问的存储器位置对齐为16字节.
文档"英特尔®64架构内存订购白皮书"指出,对于"读取或写入地址在8字节边界上对齐的四字(8字节)的指令",内存操作似乎作为单个内存访问执行,而不管记忆类型.
问题:是否存在Intel/AMD/etc x86 CPU,它们保证读取或写入与16字节边界对齐的16字节(128位)作为单个内存访问执行?是这样,它是哪种特定类型的CPU(Core2/Atom/K8/Phenom/...)?如果您对此问题提供答案(是/否),请同时指定用于确定答案的方法 - PDF文档查找,强力测试,数学证明或您用于确定答案的任何其他方法.
此问题涉及http://research.swtch.com/2010/02/off-to-races.html等问题
更新:
我在C中创建了一个可以在您的计算机上运行的简单测试程序.请在您的Phenom,Athlon,Bobcat,Core2,Atom,Sandy Bridge或您碰巧拥有的任何支持SSE2的CPU上编译并运行它.谢谢.
// Compile with:
// gcc -o a a.c -pthread -msse2 -std=c99 -Wall -O2
//
// Make sure you have at least two physical CPU cores or hyper-threading.
#include <pthread.h>
#include <emmintrin.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
typedef int v4si __attribute__ ((vector_size (16)));
volatile v4si x;
unsigned n1[16] __attribute__((aligned(64)));
unsigned n2[16] __attribute__((aligned(64)));
void* thread1(void *arg) {
for (int i=0; i<100*1000*1000; i++) { …Run Code Online (Sandbox Code Playgroud) 该代码的行为是否定义明确?
#include <stdatomic.h>
const int test = 42;
const int * _Atomic atomic_int_ptr;
atomic_init(&atomic_int_ptr, &test);
const int ** int_ptr_ptr = &atomic_int_ptr;
printf("int = %d\n", **int_ptr_ptr); //prints int = 42
Run Code Online (Sandbox Code Playgroud)
我将原子类型的指针分配给非原子类型的指针(类型相同)。这是我对这个示例的看法:
标准中明确规定的区别const,volatile并restrict从预选赛_Atomic资格赛6.2.5(p27):
只要允许一种类型的原子版本以及该类型的其他合格版本,本标准就明确使用短语“原子,限定或不限定类型”。在没有具体提及原子的情况下,短语“合格或不合格类型”不包括原子类型。
合格类型的兼容性也定义为6.7.3(p10):
为了使两个合格的类型兼容,两个都应具有相同的合格类型的兼容类型;指定符或限定符列表中类型限定符的顺序不会影响指定的类型。
结合以上引用的引言,我得出结论,原子类型和非原子类型是兼容的类型。因此,应用简单分配规则6.5.16.1(p1)(emp.mine):
左操作数具有原子,合格或不合格的指针类型,并且(考虑到左操作数在左值转换后将具有的类型), 两个操作数都是指向 兼容类型的合格或不合格版本的指针,并且由左侧指向的类型具有全部右边指出的类型的限定词;
因此,我得出的结论是,行为已得到很好的定义(即使将原子类型分配给非原子类型也是如此)。
所有这些的问题是,应用上述规则,我们还可以得出结论:将非原子类型简单分配给原子类型也定义得很好,这显然是不正确的,因为我们atomic_store为此拥有专用的泛型函数。
使用当前的 C++ 编译器,您可以获得比 CPU 实际支持更大的原子支持。使用 x64,您可以拥有 16 字节的原子,但 std::atomic 也适用于更大的元组。看这段代码:
#include <iostream>
#include <atomic>
using namespace std;
struct S { size_t a, b, c; };
atomic<S> apss;
int main()
{
auto ref = apss.load( memory_order_relaxed );
apss.compare_exchange_weak( ref, { 123, 456, 789 } );
cout << sizeof ::apss << endl;
}
Run Code Online (Sandbox Code Playgroud)
对于我的平台,上面的 cout 总是打印 32。但是,如果没有互斥体,这些事务实际上是如何工作的呢?我从检查拆卸中没有得到任何线索。
如果我使用 MSVC++ 运行以下代码:
#include <atomic>
#include <thread>
#include <array>
using namespace std;
struct S { size_t a, b, c, d, e; };
atomic<S> apss;
int …Run Code Online (Sandbox Code Playgroud) 我想知道如何才能std::atomic_ref有效地实现std::mutex非原子对象(每个对象一个),因为以下属性似乎很难实施:
相对于通过引用同一对象的任何其他atomic_ref施加的原子操作,通过atomic_ref施加到对象的原子操作是原子的。
特别是以下代码:
void set(std::vector<Big> &objs, size_t i, const Big &val) {
std::atomic_ref RefI{objs[i]};
RefI.store(val);
}
Run Code Online (Sandbox Code Playgroud)
似乎很难实现,因为std::atomic_ref每次都需要以某种方式进行选择std::mutex(除非这是由相同类型的所有对象共享的大主锁)。
我想念什么吗?还是每个对象都有责任实施std::atomic_ref,因此要么是原子的要么携带一个std::mutex?
Boost.Interprocess是一个很棒的库,可以简化不同进程之间共享内存的使用.它提供互斥锁,条件变量和信号量,允许在从共享内存中写入和读取时进行同步.
但是,在某些情况下,这些(相对)性能密集型同步机制不是必需的 - 原子操作足以满足我的用例,并且可能会提供更好的性能.
不幸的是,Boost.Interprocess似乎没有原子.
C++标准库提供了std::atomic类模板,它封装了操作需要是原子的对象,还具有测试原子操作是否无锁的功能.但它也不要求无锁原子也不需要地址:[atomics.lockfree]/4只是鼓励无锁操作无地址,这与cppreference一致.
我想不出为什么会以非地址方式实现无锁原子的任何理由.在我看来,以无地址的方式实现无锁原子相当容易.
因为在使用原子而不是互斥体(来自Boost.Interprocess)时我会获得显着的性能优势,所以在这里折扣标准兼容性并将std::atomic对象存储在共享内存中似乎很诱人.
这个问题分为两部分:
请注意,我必须更改这个问题的标题,因为很多答案似乎对我的问题感到困惑。这不是关于“互斥体对象”的范围(全局或其他)的问题,而是关于互斥体“锁定”变量的范围的问题。
我相信答案是互斥体锁定对所有变量的访问,即;所有全局和局部范围的变量。(这是互斥体阻塞线程执行而不是访问特定内存区域的结果。)
我正在尝试理解互斥体。
我试图了解互斥体将锁定内存的哪些部分,或者等效地,哪些变量。
然而,我从网上阅读的理解是,互斥体不会锁定内存,它们会锁定(或阻止)同时运行的线程,这些线程都是同一进程的成员。(那是对的吗?)
https://mortoray.com/2011/12/16/how-does-a-mutex-work-what-does-it-cost/
所以我的问题就变成了“互斥体是全局的吗?”
...或者它们可能“一般来说是全球性的,但 stackoverflow 社区可以想象一些特殊情况,但它们不是?”
最初考虑我的问题时,我对以下示例所示的内容感兴趣。
// both in global scope, this mutex will lock any global scope variable?
int global_variable;
mutex global_variable_mutex;
int main()
{
// one thread operates here and locks global_variable_mutex
// before reading/writing
{
// local variables in a loop
// launch some threads here, and wait later
int local_variable;
mutex local_variable_mutex;
// wait for launched thread to return
// does the mutex here prevent data …Run Code Online (Sandbox Code Playgroud) 在 static_assert 之后,gcc 和 clang 主干都通过了。
#include<mutex>
int main(){
static_assert(sizeof(std::mutex)==40);
}
Run Code Online (Sandbox Code Playgroud)
由于 x86 CPU 有 64 字节缓存线,我期望互斥锁 sizeof 为 64,因此可以避免错误共享。大小“仅”40 字节是否有原因?
注意:我知道大小也会影响性能,但程序中很少有大量互斥体,因此与错误共享的成本相比,大小开销似乎可以忽略不计。
注意:有一个类似的问题问为什么 std::mutex 这么大,我问为什么它这么小:)
编辑:MSVC 16.7 的大小为 80。
考虑下面的示例是打算等到另一个线程存储42在一个共享变量shared没有锁,无需等待线程终止,为什么会volatile T或std::atomic<T>会要求或建议,以保证并发正确性?
#include <atomic>
#include <cassert>
#include <cstdint>
#include <thread>
int main()
{
int64_t shared = 0;
std::thread thread([&shared]() {
shared = 42;
});
while (shared != 42) {
}
assert(shared == 42);
thread.join();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
使用 GCC 4.8.5 和默认选项,示例按预期工作。
以前,使用Apple LLVM 9.1.0,128 is_lock_free()位结构已经恢复正常.为了得到完全的std::optional支持,我随后升级到了MacPorts gcc 7.3.在我第一次尝试编译时,我遇到了这个臭名昭着的showstopper链接器错误:
Undefined symbols for architecture x86_64:
"___atomic_compare_exchange_16", referenced from:
Run Code Online (Sandbox Code Playgroud)
我知道我可能需要添加-latomic.使用Apple LLVM 9.1.0,我不需要它,我对此非常不满意.如果它是无锁的,您通常不需要链接到任何其他库,单独的编译器就能够处理它.否则,它可能只是基于锁的,需要来自其他库的支持.正如我所担心的那样,添加后-latomic,构建成功,但is_lock_free()返回false.
我认为gcc 7.3及其标准库实现很好.这可能只是我身上的一些配置问题.事实上,我没有做任何配置.我只是安装了MacPorts gcc并完成了.知道我可能缺少什么吗?