2017-02-21 12 views
1

我认为描述我的问题的简单方法是在代码中演示它,所以这里是C中一个人为的例子,以突出我有兴趣回答的问题:对复杂的C11原子类型和非原子读取优化的部分更新

// Just some complex user defined type 
typedef struct { 
    ... 
} state_t; 

typedef struct { 
    state_t states[16]; 
} state_list_t; 

static _Atomic state_list_t s_stateList; 
// For non-atomic reads 
static state_t * const s_pCurrent = &s_stateList.states[0]; 

// Called from external threads 
void get_state(state_list_t * pStateList) 
{ 
    *pStateList = atomic_load(&s_stateList); 
} 

// Only called by 'this' thread 
static void update_state(struct state_data_t const * pData) 
{ 
    state_list_t stateList = atomic_load(&s_stateList); 
    for (int i = 0; i < 16; i++) 
    { 
     // Do some updating on the data 
     do_transition(&stateList[i], pData); 
    } 
    atomic_store(&s_stateList, stateList); 
} 

// Only called by 'this' thread 
static void apply_state(state_t const * pState) 
{ 
    atomic_store(&s_stateList[0], *pState); 
} 

// Only called by this thread 
static bool check_state() 
{ 
    // Check (read) some values in the current state 
    return isOkay(s_pCurrent); 
} 

首先,我表示歉意任何语法错误,但是这应该让整个点...

前两个函数是C11原子公司的非常简单的使用,即一个线程读取另一个人正在写作的价值。我的具体问题实际上是关于最后两个函数apply_state和check_state,而这真的只是归结为这些是否可行。

在apply_state中,您可以看到它只是以原子方式更新了部分结构,特别是数组的第一个元素。我的理解是,基本上_Atomic的s_stateList的每个元素都被认为是原子的(非常像volatile),所以编译器在调用atomic_store时没有问题,但是当另一个线程从对象读取时(原子地)读取(即在get_state ),还是同步实质上等同于每次调用中的锁定/解锁相同的互斥锁?我可以看到它是如何可能的,因为它基本上是一个不同的变量(好吧,同样的地址,但如果我使用状态[1]?),它可能会导致使用不同的互斥锁。另外,如果state_t碰巧没有锁定,会发生什么?

我更加确信check_state函数在这里是可以做的事情,因为它只执行一个只能被同一个线程修改的对象的读取,但我想知道是否我错过了这里有什么。我刚刚发现,直接访问一个原子变量(我认为通过赋值或函数参数)被视为完全像对atomic_load()或atomic_store()的调用,所以我想知道是否为非原子读取保留私有引用是一种有价值的优化,或者如果编译器本身足够聪明,可以自行完成类似的优化。

编辑:当将非原子指针取消引用原子值时,结果未定义。

+0

'static state_t * const s_pCurrent =&s_stateList [0];'这是违反约束的。指针不仅是不兼容的,而且是一个非原子指针,指向一个原子类型。 – 2501

+0

修正了在任何情况下,当语法正确时,当我将原子值引用分配给非原子指针(否则为正确类型)时,我从编译器中得不到任何抱怨。 –

+0

编译器不指定语言,不应将其用作真值的来源。在6.5.16.1§1的第三段中:*和类型指向 在左边具有由右侧指向的所有类型的限定符; *因此,左指针必须具有右指针所具有的所有限定符。 – 2501

回答

2

不,这不适合C11的原子模型,并有很好的理由。 _Atomic在语法上只是一个限定词,在语义上是一种新类型。这一点反映在这个标准允许这种类型的尺寸和对齐与基地的不同。

对于宽原子类型的情况,允许的原子类型实现是向作为锁的struct添加隐藏字段。通常,这种类型被实现为“不锁定”,其具有控制对数据的访问的一些隐藏状态(在struct内或单独地)。

该标准只能通过将访问模型拼接在一起来保证您的自由度。如果您要求您的整个数据是可访问的原子数据(即对整个数据一次不可分割的操作),那么该模型只能完全实现这一点。

访问原子对象的各个字段具有未定义的行为。这意味着如果您的平台具有特定的属性,则可以允许您访问各个字段。你必须阅读你的平台的文档,并希望得到最好的结果,特别是他们不会从一个版本(编译器,处理器......)改变为另一个版本。

+0

Scott有一个有趣的问题。如果你访问一个非_Atomic指针,指向一个,比方说,_Atomic类型的对象,会发生什么。指针本身的赋值需要一个强制转换,因为6.5.16.1§1,但标准没有提及任何有关访问的内容。除6.5§7以外,不允许访问(它关于别名)非原子类型和原子类型。由于int和_Atomic int不兼容,6.5§7没有任何例外。它是否正确? (好答案btw。) – 2501

+0

通过非原子左值访问原子未定义。当你这样做的时候,究竟会出现什么问题是很大的平台依赖,但是你可以确定这是不可移植的。特别是可以让你微妙的竞争条件发生很少,很难追踪。一般来说,使用C语言并不是一个好主意,好的代码可以没有,不好的代码通常有很多。 –

+0

感谢您的答案。我一直在用一些微不足道(无锁)情况下的原子锁来取代一些锁,并且正在考虑更换大数据结构周围的锁。这是一些可疑的问题。很高兴我问过! –