使用 4 个线程获取/释放语义

2024-03-16

我目前正在阅读 Anthony Williams 撰写的《C++ Concurrency in Action》。他的清单之一显示了这段代码,他指出以下断言z != 0可以开火。

#include <atomic>
#include <thread>
#include <assert.h>

std::atomic<bool> x,y;
std::atomic<int> z;

void write_x()
{
    x.store(true,std::memory_order_release);
}

void write_y()
{
    y.store(true,std::memory_order_release);
}

void read_x_then_y()
{
    while(!x.load(std::memory_order_acquire));
    if(y.load(std::memory_order_acquire))
        ++z;
}

void read_y_then_x()
{
    while(!y.load(std::memory_order_acquire));
    if(x.load(std::memory_order_acquire))
        ++z;
}

int main()
{
    x=false;
    y=false;
    z=0;
    std::thread a(write_x);
    std::thread b(write_y);
    std::thread c(read_x_then_y);
    std::thread d(read_y_then_x);
    a.join();
    b.join();
    c.join();
    d.join();
    assert(z.load()!=0);
}

所以我能想到的不同的执行路径是这样的:

1)

Thread a (x is now true)
Thread c (fails to increment z)
Thread b (y is now true)
Thread d (increments z) assertion cannot fire

2)

Thread b (y is now true)
Thread d (fails to increment z)
Thread a (x is now true)
Thread c (increments z) assertion cannot fire

3)

Thread a (x is true)
Thread b (y is true)
Thread c (z is incremented) assertion cannot fire
Thread d (z is incremented)

有人可以向我解释一下这个断言是如何触发的吗?

He shows this little graphic: Image

商店不应该y也与负载同步read_x_then_y,以及商店x与负载同步read_y_then_x?我很困惑。

EDIT:

感谢您的回复,我了解原子是如何工作的以及如何使用获取/释放。我只是不明白这个具体的例子。我试图弄清楚如果断言触发,那么每个线程做了什么?如果我们使用顺序一致性,为什么断言永远不会触发。

顺便说一句,我对此的推理是,如果thread a (write_x) 存储到x那么到目前为止它所做的所有工作都会与读取的任何其他线程同步x与获取排序。一次read_x_then_y看到这个,它跳出循环并读取y。现在,可能会发生两件事。在一种选择中,write_y已写信给y,意味着此版本将与 if 语句(加载)含义同步z递增并且断言无法触发。另一种选择是如果write_y还没有运行,意味着 if 条件失败并且 z 没有增加,在这种情况下,仅x是真的并且y仍然是假的。一旦 write_y 运行,read_y_then_x 就会跳出循环,但是两者都x and y是真的并且z增加并且断言不会触发。我想不出任何“运行”或内存排序z永远不会增加。有人可以解释我的推理哪里有缺陷吗?

另外,我知道循环读取将始终在 if 语句读取之前,因为获取会阻止这种重新排序。


您正在考虑顺序一致性,即最强(也是默认)的内存顺序。如果采用这种内存顺序,所有对原子变量的访问构成全序,确实无法触发断言。

然而,在此程序中,使用了较弱的内存顺序(释放存储并获取加载)。根据定义,这意味着您cannot假设操作的总顺序。特别是,您不能假设更改以相同的顺序对其他线程可见。 (仅限总订单每个人变量保证任何原子内存顺序,包括memory_order_relaxed.)

各商店至x and y发生在不同的线程上,它们之间没有同步。的负载x and y发生在不同的线程上,它们之间没有同步。这意味着完全允许线程 c 看到x && ! y线程 d 看到y && ! x。 (我只是在这里缩写了 acquire-loads,不要将此语法视为顺序一致的加载。)

底线:一旦您使用比顺序一致性更弱的内存顺序,您就可以告别所有原子的全局状态的概念,即所有线程之间一致的概念。这正是为什么很多人建议坚持使用顺序一致性,除非您需要性能(顺便说一句,请记住测量它是否更快!)并且确定您在做什么。另外,征求第二意见。

现在,你是否会因此而受伤,是一个不同的问题。该标准只是根据用于描述标准要求的抽象机允许断言失败的场景。但是,您的编译器和/或 CPU 可能会出于某种原因而无法利用这一限额。因此,对于给定的编译器和 CPU,实际上您可能永远不会看到断言被触发。请记住,编译器或 CPU 可能始终使用stricter内存顺序比您要求的顺序要好,因为这永远不会违反标准的最低要求。它可能只会损失一些性能——但这并不包含在标准中。

更新回应评论:该标准没有定义一个线程看到另一个线程对原子的更改所需的时间的硬性上限。建议实施者价值观应该变得可见最终.

测序保证,但与您的示例相关的保证不会阻止断言的触发。基本的获取-释放保证是如果:

  • 线程 e 对原子变量执行释放存储x
  • 线程 f 从同一原子变量执行获取加载
  • Then iff 读取的值是 e 存储的值,e 中的存储与 f 中的加载同步。这意味着 e 中的任何(原子和非原子)存储是,在这个线程中,在给定存储之前排序x,对于 f 中的任何操作都是可见的,即在这个线程中,在给定负载之后排序。 [请注意,对于除这两个线程之外的线程,不提供任何保证!]

因此,不能保证 fwill读取 e 存储的值,而不是 e.g.的一些旧值x. If it doesn't读取更新后的值,然后负载也会读取not与存储同步,并且对于上述任何相关操作都没有顺序保证。

我将原子比作与相对论一致的、具有较少内存顺序的原子,其中有没有同时性的全局概念 https://en.wikipedia.org/wiki/Ladder_paradox.

PS:也就是说,原子加载不能只读取任意旧值。例如,如果一个线程执行某个线程的周期性增量(例如,按释放顺序)atomic<unsigned>变量,初始化为 0,并且另一个线程定期从该变量加载(例如,使用获取顺序),那么,除了最终包装之外,后一个线程看到的值必须单调递增。但这是根据给定的排序规则得出的:一旦后一个线程读取了 5,则在从 4 增加到 5 之前发生的任何事情都处于读取 5 之后的任何事情的相对过去。事实上,除了换行之外的减少是甚至不允许memory_order_relaxed, but此内存顺序并不对访问其他变量的相对顺序(如果有)做出任何承诺。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 4 个线程获取/释放语义 的相关文章

  • C# 创建函数队列

    我写了一个名为 QueueManager 的类 class QueueManager Queue functionsQueue public bool IsEmpty get if functionsQueue Count 0 return
  • Paradox 表 - Oledb 异常:外部表不是预期的格式

    我正在使用 Oledb 从 Paradox 表中读取一些数据 我遇到的问题是 当我将代码复制到控制台应用程序时 代码可以工作 但在 WinForms 中却不行 两者都以 x86 进行调试 我实际上只是复制代码 在 WinForms 应用程序
  • 是否有像 gccxml 这样的用于生成包装器的 C 标头解析器工具?

    我需要为一种新的编程语言编写一些 C 标头包装器 并且想要类似 gccxml 的东西 但不完全依赖 gcc 以及它在 Windows 系统上带来的问题 只需要读C而不是C 只要有完整的文档记录 任何格式的输出都可以 Linux Solari
  • 存储过程上的 OdbcCommand - 输出参数上出现“未提供参数”错误

    我正在尝试执行存储过程 通过 ODBC 驱动程序针对 SQL Server 2005 但收到以下错误 过程或函数 GetNodeID 需要参数 ID 但未提供该参数 ID 是我的过程的 OUTPUT 参数 在存储过程中指定了一个输入 mac
  • 序列化和反序列化 Visual Studio 解决方案文件 - 或以编程方式编辑?

    我想以编程方式添加和删除项目 解决方案文件夹和其他项目 例如解决方案的资源文件 但我不确定最好的方法是什么 对于那些不知道的人 高度简化 解决方案文件 sln 通常如下所示 Microsoft Visual Studio Solution
  • 在 MATLAB 中创建共享库

    一位研究人员在 MATLAB 中创建了一个小型仿真 我们希望其他人也能使用它 我的计划是进行模拟 清理一些东西并将其变成一组函数 然后我打算将其编译成C库并使用SWIG https en wikipedia org wiki SWIG创建一
  • C# datagridview 列转入数组

    我正在用 C 构建一个程序 并在其中包含一个 datagridview 组件 datagridview 有固定数量的列 2 我想将其保存到两个单独的数组中 但行数确实发生了变化 我怎么能这样做呢 假设一个名为 dataGridView1 的
  • 指向字节数组的指针

    由于 Misra C 的要求 我的一位同事想要使用指针声明 但我遇到了一些问题 Misra 安全关键指南 不会让我们纯粹的程序员使用指针 但会让我们对数组字节进行操作 他打算获取一个指向字节数组的指针 因此我们不会在堆栈上传递实际的数组 T
  • 您可以在一个 Windows Azure 实例上部署多个 Web 应用程序吗?

    是否可以在一个 windows azure 小型计算实例中运行一堆 Web 应用程序 我正在考虑使用 Azure 作为放置一堆处于开发和非生产状态的项目 Web 应用程序 的地方 有些实际上已经被封存了 但我想在某个地方有一个活跃的实例 我
  • C#中Enum中定义的value__是什么

    What value 可能在这里 value MSN ICQ YahooChat GoogleTalk 我运行的代码很简单 namespace EnumReflection enum Messengers MSN ICQ YahooChat
  • 允许使用什么类型的内容作为 C 预处理器宏的参数?

    老实说 我很了解 C 编程语言的语法 但对 C 预处理器的语法几乎一无所知 尽管我有时在编程实践中使用它 所以问题来了 假设我们有一个简单的宏 它扩展为空 define macro param 可以放入宏调用构造中的语法有哪些限制 调用宏时
  • 从事务范围调用 WCF 服务方法

    我有这样的代码 using TransactionScope scope TransactionScopeFactory CreateTransactionScope some methodes calls for which scope
  • 使用 foreach 循环和 XmlNodeList C# 将新节点附加到节点列表

    目前我处理的是这样的XML类型 XML FILE http 20drive google com open id 0By5BxgNi9eGcRldxcEZNU0FDTzQ 参考XML文件 我想检查一个节点 如果找不到该节点 我必须将该节点附
  • 相当于 C# 中 Java 的“ByteBuffer.putType()”

    我正在尝试通过从 Java 移植代码来格式化 C 中的字节数组 在 Java 中 使用方法 buf putInt value buf putShort buf putDouble 等等 但我不知道如何将其移植到 C 我尝试过 MemoryS
  • 从 C 线程调用 Python 代码

    我对从 C 或 C 线程调用 Python 代码时如何确保线程安全感到非常困惑 The Python 文档 http docs python org c api init html non python created threads似乎是
  • 使用 Chrome 和 Selenium 设置 LocalStorage

    我正在尝试使用 OpenQA Selenium 和 Chrome 设置本地存储键和值 我认为这相当微不足道 但我似乎无法让它发挥作用 我对 C 很陌生 所以我可能错过了一些东西 无论如何 我有这个功能 public static void
  • 如何设置 CMake 与 clang 交叉编译 Windows 上的 ARM 嵌入式系统?

    我正在尝试生成 Ninja makefile 以使用 Clang 为 ARM Cortex A5 CPU 交叉编译 C 项目 我为 CMake 创建了一个工具链文件 但似乎存在错误或缺少一些我无法找到的东西 当使用下面的工具链文件调用 CM
  • C# 粘贴到文本框时检查剪贴板中的字符

    有没有一些方法可以在粘贴到文本框 C 之前仅检查剪贴板中的字符 Ctrl V 和右键单击 gt 粘贴 但不使用 MaskedTextbox 在文本框文本更改中添加规则以仅接受数字 例如 private string value privat
  • Android进程调度

    我试图更好地理解 以便在创建 Android 应用程序 服务时确定潜在的互操作性问题对可靠性的影响 我想弄清楚进程优先级是如何确定的 服务和活动之间优先级的差异以及调度程序是否以不同方式对待它们的优先级 基本上 我试图深入了解某个活动或服务
  • 新的 .NET 6 控制台模板中的 C# 函数重载不起作用

    我在尝试重载该函数时遇到错误Print object in the 新的 NET 6 C 控制台应用程序模板 https learn microsoft com en us dotnet core tutorials top level t

随机推荐