根据cppreference,
当一个表达式的计算写入一个内存位置而另一个计算读取或修改相同的内存位置时,这些表达式被称为冲突。具有两个冲突评估的程序存在数据竞争,除非:
a) 两个评估在同一线程或同一信号处理程序中执行,或
b) 两个冲突评估都是原子操作(参见 std::atomic),或
c) 冲突评估之一发生在另一个之前(参见 std::记忆顺序)
我对c点有点困惑。根据 Anthony Williams 的书(C++ concurrency in action),第 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 共享一个全局变量。有 2 个冲突的表达式(共享变量的增量操作),我没有使用任何语言提供的同步机制。但是,我在运行时强制执行排序。我知道编译器无法知道运行时但在运行时,因为我确保线程 A 和线程 B 永远不能同时访问该变量。
所以我不确定这是否属于数据竞赛的范畴。
所以基本上我的查询是:
是否有必要在编译时而不是运行时强制排序以避免数据竞争?上述服务器的代码是否属于具有数据竞争的程序类别?
当线程在不使用 C++ 语言的同步构造(互斥/期货/原子)等的情况下共享数据时,有没有办法在多线程 C++ 程序中在编译时强制排序?