首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何编写可观察的指令重排序示例?

如何编写可观察的指令重排序示例?
EN

Stack Overflow用户
提问于 2019-07-16 14:14:04
回答 1查看 175关注 0票数 8

举个例子:

代码语言:javascript
复制
#include <thread>
#include <iostream>

int main() {
    int a = 0;
    volatile int flag = 0;

    std::thread t1([&]() {
        while (flag != 1);

        int b = a;
        std::cout << "b = " << b << std::endl;
    });

    std::thread t2([&]() {
        a = 5;
        flag = 1;
    });

    t1.join();
    t2.join();
    return 0;
}

在概念上可以理解,flag = 1;可以在a = 5;之前重新排序和执行,因此b的结果可能是5或0。

但是,实际上,我不能在我的机器上产生输出0的结果。我们如何保证行为或指令可重复地重新排序?如何具体更改代码示例?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2019-07-16 15:08:19

首先,您是在UB的土地上,因为有一个竞争条件:flaga都是从不同的线程中写入和读取的,没有适当的同步--这始终是一个数据竞赛。当您为实现提供这样的程序时,C++标准的不会强加任何要求

因此,没有办法“保证”特定的行为.

但是,我们可以查看程序集输出,以确定给定的已编译程序可以或不能做什么。我没有成功地单独使用重新排序来展示volatile作为同步机制的问题,但下面是一个使用相关优化的演示。

下面是一个没有数据竞争的程序的例子:

代码语言:javascript
复制
std::atomic<int> a = 0;
std::atomic<int> flag = 0;

std::thread t1([&]() {
    while (flag != 1);

    int b = a;
    std::cout << "b = " << b << std::endl;
});

std::thread t2([&]() {
    a = 5;
    int x = 1000000;
    while (x-- > 1) flag = 0;
    flag = 1;
    x = 1000000;
    while (x-- > 1) flag = 1;
    flag = 0;
    a = 0;
});

t1.join();
t2.join();

https://wandbox.org/permlink/J1aw4rJP7P9o1h7h

实际上,这个程序的通常输出是b = 5 (其他输出是可能的,或者程序可能根本不会因为“不幸”的调度而终止,但是没有UB)。

如果我们使用不正确的同步,我们可以在程序集中看到这个输出不再是可能的(考虑到x86平台的保证):

代码语言:javascript
复制
int a = 0;
volatile int flag = 0;

std::thread t1([&]() {
    while (flag != 1);

    int b = a;
    std::cout << "b = " << b << std::endl;
});

std::thread t2([&]() {
    a = 5;
    int x = 1000000;
    while (x-- > 1) flag = 0;
    flag = 1;
    x = 1000000;
    while (x-- > 1) flag = 1;
    flag = 0;
    a = 0;
});

t1.join();
t2.join();

第二个线程体的程序集,如https://godbolt.org/z/qsjca1所示

代码语言:javascript
复制
std::thread::_State_impl<std::thread::_Invoker<std::tuple<main::{lambda()#2}> > >::_M_run():
        mov     rcx, QWORD PTR [rdi+8]
        mov     rdx, QWORD PTR [rdi+16]
        mov     eax, 999999
.L4:
        mov     DWORD PTR [rdx], 0
        sub     eax, 1
        jne     .L4
        mov     DWORD PTR [rdx], 1
        mov     eax, 999999
.L5:
        mov     DWORD PTR [rdx], 1
        sub     eax, 1
        jne     .L5
        mov     DWORD PTR [rdx], 0
        mov     DWORD PTR [rcx], 0
        ret

注意a = 5;是如何被完全优化的。在编译的程序中,a没有机会获得5值。

正如您在https://wandbox.org/permlink/Pnbh38QpyqKzIClY中所看到的,程序总是输出0(或不终止),尽管线程2的原始C++代码--在“天真”的解释中--总是有a == 5而不是flag == 1

当然,while循环会“消耗时间”,并给其他线程一个交织的机会-- sleep或其他系统调用通常会构成编译器的内存屏障,并可能破坏第二个片段的效果。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/57059332

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档