我已经看到了堆栈的一些无锁实现...我的问题是关于可见性,而不是原子性。例如,无锁堆栈的元素(不是指针)必须最多为64位?我是这么认为的,因为你无法保证知名度。真实示例:可以安全地插入此结构并从无锁容器中删除
struct person
{
string name;
uint32_t age;
}
编辑:有些人对这个问题感到困惑。稍微解释一下:如果作者在堆栈上推送人,读者得到它,是否保证读者看到(内存可见性)正确的人的内容。
答案 0 :(得分:3)
我可能错了,但我认为问题不对。
原子指令通常处理单指针长度数据;最多,有两个指针长度的数据。
典型的结构不能被原子操纵,因为它太大了。
所以无锁堆栈将只会操纵指向元素的指针(AFAIK需要在指针长度边界上对齐 - 我知道没有平台,但事实并非如此)。
答案 1 :(得分:2)
我必须承认自己对这个问题感到有些困惑......
通过操纵指针(机器大小和对齐)到节点来存在无锁数据结构。然后,这些节点包含无锁数据结构的真实“内容”。该内容可以具有您想要的任意大小,因此您的结构可以放在那里。无锁数据结构的常用节点类似于:
struct Node {ATOMIC_PTR next;内容; }
其中内容可以是您想要的内容,指向包含内容的内存的指针或直接包含某些内容的内存。 可见性在这里不是问题,因为在修改无锁数据结构时,首先要分配一个新节点,然后用所需内容填充它,最后使用原子操作来设置各种相关指针。因为这是你做的最后一件事,而这些操作通常都涉及内存屏障,以保证可见性和顺序,你没事。
答案 2 :(得分:1)
根据其他QnA关于SO,
每个x86互锁指令(包括CAS)都意味着一个完整的内存屏障。所以,至少在x86上,我们不需要关心无锁容器元素的可见性(假设它们使用CAS。)
答案 3 :(得分:0)
是的,可以使用结构。因为无锁数据结构所需要的只是一种原子地更新表示结构内部的单个值的方法。元素或有效负载的大小不会对其无锁性质产生任何影响。
据我了解,无锁数据结构的工作原理如下:
因此,只要第三步可以原子方式执行,一切都很顺利。
使元素本身具有原子可更新性不会给您任何好处,因为容器必须作为一个组集体管理它们。
答案 4 :(得分:0)
注意:请仅在您实际测试此方法时将此答案标记为正确。
关于您的问题是否可以从无锁容器中安全地插入和删除以下结构:
struct person
{
string name;
uint32_t age;
}
如果使用冗余编码,可以从无锁容器中安全地插入/删除任意长度的多字节序列。让我们假设我们已经拥有一次操作4个字节(32位)的原子指令。在这种情况下,我们可以对uint32_t age
字段进行编码,如下所示:
struct age_t
{
uint32_t age_low;
uint32_t age_high;
}
字段age_low
存储32位uint32_t age
的低位(例如,低16位)。字段age_high
存储剩余的高位。的概念强>:
struct age_t
{
uint16_t age_low;
uint16_t id_low;
uint16_t age_high;
uint16_t id_high;
}
字段id_low
和id_high
应包含标识作者的ID。
读取实现为两个原子32位读取,如果所有id_
部分彼此等效,则读取成功。如果失败,则需要重新启动读取操作。
写入实现为两个原子32位写入,然后读取整个age_t
值。如果成功,则写入成功:上一句中提到的读取成功,读取的ID等同于写入的ID。
关于string
值:原理是一样的。您只需要弄清楚如何分割其二进制值,类似于age
值的分割方式。在读/写整个person
结构方面也是如此。
答案 5 :(得分:0)
线程安全容器(无锁或带锁)解决了列表/容器的线程安全问题,而不是放入容器的项目的线程安全性。 因此,无锁堆栈将确保push和pop是线程安全且无锁定但是如果你有两个不同的线程保存指向同一结构实例的指针(例如,推入堆栈的线程仍然保持ponter和另一个线程弹出堆栈)您必须使用其他一些线程安全措施来确保结构一致性
答案 6 :(得分:0)
如果元素不必以任何特定顺序存储在堆栈或队列中,并且如果使用线程安全队列来保存,则可以为任意大小的数据元素实现出列的线程安全实现未分配项目的索引,以及用于保存包含排队/堆叠项目的数据槽索引的线程安全出列。将项目写入出队将需要从“未分配的时隙”队列中提取数字,将所需数据写入该时隙,然后将该数字的索引排入“主”出队。获取一个项目需要从“主”出队中提取其号码,将其复制到其他地方,然后将该号码排入“未分配的时隙”队列中。
这种方法的一个警告是,虽然它可能是“无锁”的,因为一个停止的线程不能任意延迟其他线程的进度,一个线程在它从一个获取一个槽索引之间得到了一个队列以及将其存储在另一个队列中的时间可能会导致阵列插槽在任意长度的时间内无法使用。相比之下,一些与较小数据类型一起使用的无等待堆栈或队列实现没有这种限制。在读取或写入期间,线程可能会从存在中消失,并且系统将处于表示读取或写入完成的有效状态,或者处于指示它从未开始的有效状态。