【问题标题】:Does a thread synchronize with with the previous thread with same id?线程是否与具有相同 id 的前一个线程同步?
【发布时间】:2019-05-28 22:20:35
【问题描述】:

在 C++11 中有 std::this_thread::get_id() 我可以用来获取我的线程的 std::thread::id 标识符。标准说:

30.3.1.1

  1. thread::id 类型的对象为每个执行线程提供一个唯一标识符,并为所有线程对象提供一个不同的值 不代表执行线程(30.3.1)。的每个线程 执行有一个关联的 thread::id 对象不等于 thread::id 任何其他执行线程的对象,但不是 等于任何不存在的 std::thread 对象的 thread::id 对象 表示执行线程。
  2. thread::id 应该是一个可简单复制的类(第 9 条)。库可以重用已终止线程的 thread::id 的值 无法再加入。

我的问题正是关于新线程 B 与旧线程 A 具有相同 id 的情况:线程 B 会“看到”线程 A 所做的更改吗?

更具体地考虑这种情况:

线程 A 会:

owner.store(std::this_thread::get_id()); //store A's 'id'
...some work...
owner.store(std::thread::id(),std::memory_order_relaxed); // clear

线程 B 会:

assert(owner.load(std::memory_order_relaxed) != std::this_thread::get_id());

这个断言会成立吗?

线程 A 中的 owner.load 和线程 B 中的最后一个 owner.store 是故意“放松”的,因此两者之间没有明显的“同步”关系,除了我的问题中假设的关系。

【问题讨论】:

  • 我不知道你为什么会这样认为?
  • 考虑到标准中写的内容,并假设唯一性是考虑物理时间概念化的,即在给定时间没有两个线程id可以相同,那么为了确保标准要求,系统必须确保A 的结束发生在 B 的开始之前。
  • @Oliv,是的,我对整个主题的怀疑实际上归结为标准定义“执行线程”集的“相对时空参考框架”到底是什么。英语(可能是大多数语言)不足以谈论在不同观察者看来可能不同的事物。

标签: c++ multithreading c++11


【解决方案1】:

[1] 考虑到标准中写的内容,并假设唯一性是考虑物理时间概念化的,即在给定时间没有两个线程 id 可以相同,那么为了确保标准要求,系统必须确保A 的结束发生在 B 的开始之前。

[2] 线程的结束发生在执行此线程开始时启动的函数体之后,而线程的开始发生在执行此启动函数的主体之前。如 [basic.exec]/11 中所述:

调用函数时(无论函数是否内联),与任何参数表达式或指定被调用函数的后缀表达式相关的每个值计算和副作用,都在执行每个表达式或语句之前排序被调用函数的主体。对于每个函数调用 F,对于在 F 中发生的每个评估 A 和每个不在 F 中发生但在同一线程上评估并作为同一信号处理程序(如果有)的一部分的评估 B,任一 A 在 B 之前排序或 B 在 A 之前排序。

[1] 和 [2] 意味着线程 A 中的每个计算都发生在线程 B 中的任何计算之前。

【讨论】:

    【解决方案2】:

    我的问题正是关于 一个新线程 B 的情况 与旧线程 A 相同的 id :线程 B 是否会“看到所做的更改 由“线程A?

    - 所以,您的规定正是关于线程A 肯定终止(即不能再加入)并且线程B 已启动,可能重用线程A 的ID 的情况。

    在这种情况下(线程A 和B 是后续的,不是并发的)断言将保持 - 即使它的 ID 可能相同,当线程 B 启动时,所有者的存储肯定会被清空(保证线程A的终止)

    更新:感谢@Yakk - Adam Nevraumont 的评论——这是我对写操作的忽视——>relaxed order 下的读操作——上面的陈述是错误的!即线程A 的最后一次操作中的内存提交将与线程B 的读取异步,因此断言可能不成立! (很抱歉造成混淆)

    否则(线程 A 和 B 是并发的)它们永远不会获得相同的 ID,因此断言也将成立。

    【讨论】:

    • 您能否为您的声明提供任何来自 C++ 内存模型的引用?即,(2) 意味着旧线程中的所有事情都正式发生——在另一个线程中的所有事情之前,有一个重用的 id?我明白你为什么希望这是真的,但我没有看到证据。
    • @Yakk - Adam Nevraumont,哦,你是对的!不知何故,我假设(不要问我为什么)这里原子操作的修改顺序是一致的(例如,即使在 relaxed order 下连续写入的情况下),这当然不是这里的情况 - 首先是 write ,第二个是read,它不能保证结果的顺序(内存提交确实可能发生在在此处读取之后)。我会更新我的帖子,谢谢你让我诚实!
    • 我什至不确定 removig std::memory_order_relaxed 是否会大大改变我的问题:AFAIU 这只会意味着在“..some work..”中发生的其他事情也会变得可见 IF 线程B 读取owner 的清除值。但是 AFAIU 并没有改变 B 中的读取是否保证在 A 的第二次写入之后发生的基本问题。毕竟,A 写入id,B 读取id,A 清除它的顺序是一致的。标准是否说明线程死亡也是一致排序的一部分?
    • @qbolec:我相信这很重要,看:relaxed order 线程中的最后一个 store 操作 A 可能不会提交实际更改(即所有者的内存可能不会反映清除的线程 ID)但线程 A 可能已经终止(因此它的 ID 可能被重用)。一旦线程B 启动,它可能已经重新使用线程A ID,但在load 操作中,有可能读取过时的信息(即线程A 的ID - 因为内存提交尚未完成了)。因此,不能保证断言会成立。
    • @Dmitry 我同意你对memory_order_relaxed 的描述。我想知道如果最后一个商店没有memory_order_relaxed怎么办?当然,在 gcc 上的 x86 上,这将导致 mfence 并且必须发布数据。但也许在另一个平台上,我唯一的保证是在 A 的第二个商店和 B 的负载之间有 some 总订单。是否有任何保证“A”的位置是什么?在这个顺序中终止”,还是“存储”和“加载”的相对顺序?
    猜你喜欢
    • 2018-03-10
    • 1970-01-01
    • 1970-01-01
    • 2023-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-18
    相关资源
    最近更新 更多