jav*_*red 15 c++ multithreading low-latency
我有一个非常简单的问题.我有简单的类型变量(如int).我有一个进程,一个编写器线程,几个"只读"线程.我该如何声明变量?
volatile intstd::atomic<int>int我希望当"writer"线程修改值时,所有"读者"线程应该尽快看到新的值.
可以同时读取和写入变量,但我希望读者获得旧值或新值,而不是某些"中间"值.
我正在使用单CPU Xeon E5 v3机器.我不需要是可移植的,我只在这台服务器上运行代码,我编译-march=native -mtune=native.性能非常重要,所以除非绝对需要,否则我不想添加"同步开销".
如果我只是使用int并且一个线程写入值,那么在另一个线程中我可能暂时没有看到"新鲜"值吗?
如果您对具有一个或多个编写器的变量具有不同步的访问权限,那么您的程序具有未定义的行为.一些如何保证在写入发生时不会发生其他写入或读取.这称为同步.如何实现此同步取决于应用程序.
对于这样的事情,我们有一个编写器和几个读者,并使用TriviallyCopyable数据类型,然后一个std::atomic<>将工作.原子变量将确保只有一个线程可以同时访问变量.
如果你没有TriviallyCopyable类型或者你不想使用a std::atomic你也可以使用常规std::mutex和a std::lock_guard来控制访问
{ // enter locking scope
std::lock_guard lock(mutx); // create lock guard which locks the mutex
some_variable = some_value; // do work
} // end scope lock is destroyed and mutx is released
Run Code Online (Sandbox Code Playgroud)
使用这种方法时要记住的一件重要事情是,您希望保持该// do work部分尽可能短,因为当互斥锁被锁定时,其他线程无法进入该部分.
另一个选择是使用std::shared_timed_mutex(C++ 14)或std::shared_mutex(C++ 17)允许多个读者共享互斥锁,但是当你需要编写时,你仍然可以查看互斥锁并写入数据.
您不希望在此答案中使用jalf状态volatile来控制同步:
对于对共享数据的线程安全访问,我们需要保证:
- 读/写实际发生(编译器不会仅将值存储在寄存器中,而是延迟更新主存储器直到很久以后)
- 没有重新排序.假设我们使用
volatile变量作为标志来指示是否准备好读取某些数据.在我们的代码中,我们只是在准备数据后设置标志,所以看起来都很好.但是如果指令被重新排序以便首先设置标志呢?
volatile确保第一点.它还保证在不同的volatile读/写之间不会发生重新排序.所有volatile内存访问都将按照指定的顺序进行.这就是我们所需要的所有内容volatile:操作I/O寄存器或内存映射硬件,但它对多线程代码没有帮助,其中volatile对象通常仅用于同步对非易失性数据的访问.这些访问仍然可以相对于那些访问重新排序volatile.
与往常一样,如果您测量性能并且性能不足,那么您可以尝试不同的解决方案,但确保在更改后重新测量和比较.
最后香草萨特有极好的表现,他在做C++和2012年之后被称为原子武器是:
这是一个由两部分组成的演讲,内容涵盖C++内存模型,锁和原子和围栏如何交互并映射到硬件等等.即使我们谈论C++,其中大部分也适用于具有类似内存模型的Java和.NET,但不适用于C++的所有功能(例如轻松原子).
我有简单类型变量(如 int)。我有一个进程,一个编写器线程,几个“只读”线程。我应该如何声明变量?
易失性 int std::atomic int
使用 std::atomic 和 memory_order_relaxed 进行存储和加载
它很快,而且从你对问题的描述来看,很安全。例如
void func_fast()
{
std::atomic<int> a;
a.store(1, std::memory_order_relaxed);
}
Run Code Online (Sandbox Code Playgroud)
编译为:
func_fast():
movl $1, -24(%rsp)
ret
Run Code Online (Sandbox Code Playgroud)
这假设您不需要保证在更新整数之前看到任何其他数据被写入,因此不需要更慢且更复杂的同步。
如果你像这样天真地使用原子:
void func_slow()
{
std::atomic<int> b;
b = 1;
}
Run Code Online (Sandbox Code Playgroud)
您得到的 MFENCE 指令没有 memory_order* 规范,速度要慢得多(多了 100 个周期,而裸 MOV 仅需要 1 或 2 个周期)。
func_slow():
movl $1, -24(%rsp)
mfence
ret
Run Code Online (Sandbox Code Playgroud)
(有趣的是,在 Intel 上,此代码使用 memory_order_release 和 _acquire 会产生相同的汇编语言。Intel 保证在使用标准 MOV 指令时写入和读取按顺序发生)。
| 归档时间: |
|
| 查看次数: |
932 次 |
| 最近记录: |