根据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永远不能同时访问变量。
所以我不确定这是否属于数据竞赛的范畴。
所以基本上我的问题是:
-
是否有必要在编译时而不是运行时强制执行排序以避免数据竞争?上述服务器的代码是否属于具有数据竞争的程序类别?
-
在多线程C++程序中,当线程在不使用C++语言的同步结构(互斥体/期货/原子)等的情况下共享数据时,有没有一种方法可以在编译时强制排序?
上述服务器的代码是否属于具有数据竞争的程序类别?
是。
当线程在不使用C++语言的同步结构(互斥/期货/原子)等的情况下共享数据时,有没有一种方法可以在编译时在多线程C++程序中强制排序?
实现可以提供超出标准要求的额外保证。
上述服务器的代码是否属于具有数据竞争的程序类别?
如果等待确认,则为否。但是你走在非常薄的冰上。如果一段时间后,您将两个请求一起发送,甚至可能一个接一个地发送,而不等待确认,该怎么办?KABOOM。内核中的调度程序可以选择它想要执行的任何线程。
此外,
- 共享变量是导致多线程代码中各种难以跟踪的崩溃的原因。避免它
- 使用消息传递。更干净的方式来做共享变量所能做的一切。更容易推理
编辑:关于强制执行C++同步内置程序以外的顺序的问题,您可以使用正在使用windows、posix等操作系统的同步原语。这将是你将使用的c函数和结构,但它在技术上适用于c++和