jjzjj

c++ - 多核CPU上32bit读的原子性

coder 2024-01-31 原文

(注意:我根据我认为可能会提供帮助的人的位置为这个问题添加了标签,所以请不要大声喊叫:))

在我的 VS 2017 64 位项目中,我有一个 32 位长值 m_lClosed。当我想更新它时,我使用了 Interlocked 函数系列之一。

考虑这段代码,在线程 #1 上执行

LONG lRet = InterlockedCompareExchange(&m_lClosed, 1, 0);   // Set m_lClosed to 1 provided it's currently 0

现在考虑这段代码,在线程 #2 上执行:

if (m_lClosed) // Do something

我知道在单个 CPU 上,这不会成为问题,因为更新是原子的,读取也是原子的(参见 MSDN),因此线程抢占不能使变量处于部分更新状态。但是在多核 CPU 上,如果每个线程都在不同的 CPU 上,我们真的可以让这两段代码并行执行。在这个例子中,我认为这不是问题,但测试可能正在更新的东西仍然感觉不对。

This webpage告诉我多个 CPU 上的原子性是通过 LOCK 汇编指令实现的,防止其他 CPU 访问该内存。这听起来像我需要的,但是为上面的 if 测试生成的汇编语言仅仅是

cmp   dword ptr [l],0  

...看不到LOCK指令。

在这样的事件中,我们应该如何确保读取的原子性?

编辑 24/4/18

首先感谢大家对这个问题的兴趣。我在下面显示实际代码;我故意保持简单,以专注于所有内容的原子性,但显然,如果我从第一分钟开始就展示所有内容,效果会更好。

其次,实际代码所在的项目是一个VS2005项目; 因此无法访问 C++11 原子。这就是为什么我没有在问题中添加 C++11 标签的原因。我将 VS2017 与“草稿”项目一起使用,以节省每次我在学习时进行更改时都必须构建巨大的 VS2005 项目。另外,它是一个更好的 IDE。

是的,所以实际代码存在于 IOCP 驱动的服务器中,而整个原子性是关于处理关闭的套接字:

class CConnection
{
    //...

    DWORD PostWSARecv()
    {
        if (!m_lClosed)
            return ::WSARecv(...);
        else
            return WSAESHUTDOWN;
    }

    bool SetClosed()
    {
        LONG lRet = InterlockedCompareExchange(&m_lClosed, 1, 0);   // Set m_lClosed to 1 provided it's currently 0
        // If the swap was carried out, the return value is the old value of m_lClosed, which should be 0.
        return lRet == 0;
    }

    SOCKET m_sock;
    LONG m_lClosed;
};

调用者将调用SetClosed();如果它返回 true,它将调用 ::closesocket() 等。请不要质疑为什么会这样,它就是这样:)

考虑如果一个线程关闭套接字而另一个线程尝试发送 WSARecv() 会发生什么。您可能认为 WSARecv() 会失败(毕竟套接字已关闭!);但是,如果使用与我们刚刚关闭的相同的套接字句柄建立新连接 - 那么我们将发布 WSARecv() 这将成功,但这会对我的程序逻辑来说是致命的,因为我们现在将一个完全不同的连接与这个 CConnection 对象相关联。因此,我有 if (!m_lClosed) 测试。你可能会争辩说我不应该在多个线程中处理相同的连接,但这不是这个问题的重点 :)

这就是为什么我需要在调用 WSARecv() 之前测试 m_lClosed

现在,很明显,我只是将 m_lClosed 设置为 1,因此读/写中断并不是真正的问题,但这是我关心的原则。如果我将 m_lClosed 设置为 2147483647 然后测试 2147483647 会怎样?在这种情况下,读取/写入撕裂会带来更多问题。

最佳答案

这实际上取决于您的编译器和您运行的 CPU。

x86 CPU 将在没有 LOCK 的情况下自动读取 32 位值如果内存地址正确对齐,则为前缀。但是,您很可能需要某种 memory barrier如果变量被用作一些其他相关数据的锁/计数,则控制 CPU 的乱序执行。未对齐的数据可能无法以原子方式读取,尤其是当值跨越页面边界时。

如果您不是手工编码汇编,您还需要担心 compilers reordering optimizations .

标记为 volatile 的任何变量当 compiling with Visual C++ 时,编译器(以及可能生成的机器代码)将具有排序约束:

The _ReadBarrier, _WriteBarrier, and _ReadWriteBarrier compiler intrinsics prevent compiler re-ordering only. With Visual Studio 2003, volatile to volatile references are ordered; the compiler will not re-order volatile variable access. With Visual Studio 2005, the compiler also uses acquire semantics for read operations on volatile variables and release semantics for write operations on volatile variables (when supported by the CPU).

Microsoft specific volatile keyword enhancements :

When the /volatile:ms compiler option is used—by default when architectures other than ARM are targeted—the compiler generates extra code to maintain ordering among references to volatile objects in addition to maintaining ordering to references to other global objects. In particular:

  • A write to a volatile object (also known as volatile write) has Release semantics; that is, a reference to a global or static object that occurs before a write to a volatile object in the instruction sequence will occur before that volatile write in the compiled binary.

  • A read of a volatile object (also known as volatile read) has Acquire semantics; that is, a reference to a global or static object that occurs after a read of volatile memory in the instruction sequence will occur after that volatile read in the compiled binary.

This enables volatile objects to be used for memory locks and releases in multithreaded applications.


For architectures other than ARM, if no /volatile compiler option is specified, the compiler performs as if /volatile:ms were specified; therefore, for architectures other than ARM we strongly recommend that you specify /volatile:iso, and use explicit synchronization primitives and compiler intrinsics when you are dealing with memory that is shared across threads.

Microsoft 提供 compiler intrinsics对于大多数 Interlocked* 函数,它们将编译为类似 LOCK XADD ... 的内容而不是函数调用。

“最近”之前,C/C++ 通常不支持原子操作或线程,但这在 C11/C++11 中发生了变化,其中添加了原子支持。使用 <atomic> header 及其类型/函数/类将对齐和重新排序的责任移交给了编译器,因此您不必担心。您仍然必须就内存屏障做出选择,这决定了编译器生成的机器码。在宽松的内存顺序下,load原子操作最有可能以简单的 MOV 结束。 x86 上的说明。更严格的内存顺序可以添加一个栅栏,并且可能添加 LOCK。如果编译器确定目标平台需要前缀。

关于c++ - 多核CPU上32bit读的原子性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49989123/

有关c++ - 多核CPU上32bit读的原子性的更多相关文章

  1. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  2. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  3. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  4. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

  5. arrays - Ruby 数组 += vs 推送 - 2

    我有一个数组数组,想将元素附加到子数组。+=做我想做的,但我想了解为什么push不做。我期望的行为(并与+=一起工作):b=Array.new(3,[])b[0]+=["apple"]b[1]+=["orange"]b[2]+=["frog"]b=>[["苹果"],["橙子"],["Frog"]]通过推送,我将推送的元素附加到每个子数组(为什么?):a=Array.new(3,[])a[0].push("apple")a[1].push("orange")a[2].push("frog")a=>[[“苹果”、“橙子”、“Frog”]、[“苹果”、“橙子”、“Frog”]、[“苹果”、“

  6. += 的 Ruby 方法 - 2

    有没有办法让Ruby能够做这样的事情?classPlane@moved=0@x=0defx+=(v)#thisiserror@x+=v@moved+=1enddefto_s"moved#{@moved}times,currentxis#{@x}"endendplane=Plane.newplane.x+=5plane.x+=10putsplane.to_s#moved2times,currentxis15 最佳答案 您不能在Ruby中覆盖复合赋值运算符。任务在内部处理。您应该覆盖+,而不是+=。plane.a+=b与plane.a=

  7. ruby - Sinatra + Heroku + Datamapper 使用 dm-sqlite-adapter 部署问题 - 2

    出于某种原因,heroku尝试要求dm-sqlite-adapter,即使它应该在这里使用Postgres。请注意,这发生在我打开任何URL时-而不是在gitpush本身期间。我构建了一个默认的Facebook应用程序。gem文件:source:gemcuttergem"foreman"gem"sinatra"gem"mogli"gem"json"gem"httparty"gem"thin"gem"data_mapper"gem"heroku"group:productiondogem"pg"gem"dm-postgres-adapter"endgroup:development,:t

  8. ruby - Ruby 中字符串运算符 + 和 << 的区别 - 2

    我是Ruby和这个网站的新手。下面两个函数是不同的,一个在函数外修改变量,一个不修改。defm1(x)x我想确保我理解正确-当调用m1时,对str的引用被复制并传递给将其视为x的函数。运算符当调用m2时,对str的引用被复制并传递给将其视为x的函数。运算符+创建一个新字符串,赋值x=x+"4"只是将x重定向到新字符串,而原始str变量保持不变。对吧?谢谢 最佳答案 String#+::str+other_str→new_strConcatenation—ReturnsanewStringcontainingother_strconc

  9. ruby - rails 3.2.2(或 3.2.1)+ Postgresql 9.1.3 + Ubuntu 11.10 连接错误 - 2

    我正在使用PostgreSQL9.1.3(x86_64-pc-linux-gnu上的PostgreSQL9.1.3,由gcc-4.6.real(Ubuntu/Linaro4.6.1-9ubuntu3)4.6.1,64位编译)和在ubuntu11.10上运行3.2.2或3.2.1。现在,我可以使用以下命令连接PostgreSQLsupostgres输入密码我可以看到postgres=#我将以下详细信息放在我的config/database.yml中并执行“railsdb”,它工作正常。开发:adapter:postgresqlencoding:utf8reconnect:falsedat

  10. ruby - 在 Ruby + Chef 中检查现有目录失败 - 2

    这是我在ChefRecipe中的一blockRuby:#ifdatadirdoesn'texist,moveoverthedefaultoneif!File.exist?("/vol/postgres/data")execute"mv/var/lib/postgresql/9.1/main/vol/postgres/data"end结果是:Executingmv/var/lib/postgresql/9.1/main/vol/postgres/datamv:inter-devicemovefailed:`/var/lib/postgresql/9.1/main'to`/vol/post

随机推荐