如果我在运行时在冲突表达式之间强制执行"发生之前"关系,这是数据竞争吗?



根据cpprreference,

当一个表达式的求值写入一个内存位置,而另一个求值读取或修改同一内存位置时,称表达式冲突。具有两个冲突评估的程序具有数据竞赛,除非:

a) 两个求值都在同一个线程或同一个信号处理程序中执行,或者
b)两个冲突求值都是原子操作(请参阅std::atomic),或者
c)一个冲突求值发生在另一个之前(请参阅td::memory_order)

我对c点有点困惑。根据Anthony Williams的书(c++并发操作),第5.1.2节:

如果对单个内存位置的两次访问之间没有强制排序从单独的线程,这些访问中的一个或两个都不是原子访问,如果其中一个或两者都是原子访问如果是写入,则这是数据争用并导致未定义的行为。根据语言标准,一旦应用程序包含未定义的行为,所有赌注都被取消;整个应用程序的行为现在是未定义,它可以做任何事情

我知道,如果我强制执行排序,比如说使用std::mutex,这样就不可能同时进行多个(冲突表达式的)求值,那么一切都很好,我的程序也会得到很好的定义。我喜欢将其视为"编译时命令强制执行"。

我想了解"运行时订单强制"是否足以消除数据竞争/未定义行为。

举个例子,我设计了一个客户端-服务器系统,如下所示:

服务器规范

  • 它将使用2个线程(线程A和线程B)。

  • 两个线程将共享一个用0初始化的全局int变量。

  • 它将在2个端口(端口A和端口B)上侦听客户端的消息。

  • 线程A将使用端口A,线程B将使用端口B。

  • 线程A/B伪代码:

    while(true) {
    Receive message on the connected socket(port A in case of Thread A and port B for thread B).
    Increment the shared global variable.
    Send an acknowledgement to the client.
    }
    
  • 请注意,在伪代码的第一步和最后一步中进行的网络库/系统调用不会引入线程间同步

客户端规范

  • 它将是单线程

  • 它将与服务器的上述2个端口连接。

  • 它将交替地向服务器的两个端口发送消息,从端口A开始。

  • 只有在接收到前一条消息的确认后,它才会发送下一条消息。

  • 伪代码:

    while(true) {
    Send message to port A of server.
    Receive acknowledgement of the above message.
    Send message to port B of server.
    Receive acknowledgement of the above message.
    }
    

在上面的例子中,服务器的线程A和线程B共享一个全局变量。有两个冲突的表达式(共享变量的增量运算),我没有使用任何语言提供的同步机制。但是,我在运行时强制执行排序。我知道编译器不能知道运行时,但在运行时,因为我确保线程A和线程B永远不能同时访问变量。

所以我不确定这是否属于数据竞赛的范畴。

所以基本上我的问题是:

  1. 是否有必要在编译时而不是运行时强制执行排序以避免数据竞争?上述服务器的代码是否属于具有数据竞争的程序类别?

  2. 在多线程C++程序中,当线程在不使用C++语言的同步结构(互斥体/期货/原子)等的情况下共享数据时,有没有一种方法可以在编译时强制排序?

上述服务器的代码是否属于具有数据竞争的程序类别?

是。

当线程在不使用C++语言的同步结构(互斥/期货/原子)等的情况下共享数据时,有没有一种方法可以在编译时在多线程C++程序中强制排序?

实现可以提供超出标准要求的额外保证。

上述服务器的代码是否属于具有数据竞争的程序类别?

如果等待确认,则为否。但是你走在非常薄的冰上。如果一段时间后,您将两个请求一起发送,甚至可能一个接一个地发送,而不等待确认,该怎么办?KABOOM。内核中的调度程序可以选择它想要执行的任何线程。

此外,

  1. 共享变量是导致多线程代码中各种难以跟踪的崩溃的原因。避免它
  2. 使用消息传递。更干净的方式来做共享变量所能做的一切。更容易推理

编辑:关于强制执行C++同步内置程序以外的顺序的问题,您可以使用正在使用windows、posix等操作系统的同步原语。这将是你将使用的c函数和结构,但它在技术上适用于c++和