我认为描述我的问题的简单方法是在代码中展示它,所以这里有一个人为的例子来强调我有兴趣回答的问题:
// 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 :(得分:2)
这不符合C11的原子模型,并且有充分的理由。 _Atomic
在语法上只是一个限定符,语义上_Atomic
是一个新类型。这反映在标准允许这种类型的大小和对齐与基础大小不同的事实上。
在广义原子类型的情况下,原子类型的允许实现是向用作锁的struct
添加隐藏字段。通常,此类类型实现为“无锁定”,具有一些控制对数据访问的隐藏状态(在struct
内或单独)。
标准只能通过将访问模型拼接在一起来保证您的无竞争力。如果你要求你的整个数据是原子可访问的(在整个数据的同时不可分割的操作意义上),模型只允许你这样做。
访问原子对象的各个字段具有未定义的行为。这意味着如果您的平台具有特定属性,则可以允许您访问各个字段。您必须阅读平台的文档并希望获得最佳,特别是它们不会从一个版本(编译器,处理器......)更改为另一个版本。