如果已知访问顺序是安全的,如何在没有互斥锁的情况下同步线程/CPU
How to synchronize threads/CPUs without mutexes if sequence of access is known to be safe?
请考虑以下事项:
// these services are running on different threads that are started a long time ago
std::vector<io_service&> io_services;
struct A {
std::unique_ptr<Object> u;
} a;
io_services[0].post([&io_services, &a] {
std::unique_ptr<Object> o{new Object};
a.u = std::move(o);
io_services[1].post([&a] {
// as far as I know changes to `u` isn't guaranteed to be seen in this thread
a.u->...;
});
});
实际代码将结构传递给一堆不同的boost::asio::io_service
对象,并且结构的每个字段由不同的服务对象填充(结构永远不会同时从不同的io_service对象/线程访问,它通过引用在服务之间传递,直到该过程完成)。
据我所知,当我在线程之间传递任何东西时,即使没有读/写竞争(如同时访问),我也总是需要某种明确的同步/内存刷新。在这种情况下,正确执行此操作的方法是什么?
请注意,对象不属于我,它不是普通的可复制或可移动的。我可以使用std::atomic<Object*>
(如果我没有错的话),但我宁愿使用智能指针。有没有办法做到这一点?
编辑:似乎 std::atomic_thread_fence 是这项工作的工具,但我无法真正包装"内存模型"概念来安全地编码。我的理解是,此代码需要以下行才能正常工作。真的是这样吗?
// these services are running on different threads that are started a long time ago
std::vector<io_service&> io_services;
struct A {
std::unique_ptr<Object> u;
} a;
io_services[0].post([&io_services, &a] {
std::unique_ptr<Object> o{new Object};
a.u = std::move(o);
std::atomic_thread_fence(std::memory_order_release);
io_services[1].post([&a] {
std::atomic_thread_fence(std::memory_order_acquire);
a.u->...;
});
});
只有在没有同步的情况下才会需要同步。数据争用定义为不同线程的非序列访问。
您没有此类未排序的访问。该t.join()
保证后面的所有语句严格排序在作为 t
的一部分运行的所有语句之后。因此不需要同步。
详细说明:(解释为什么thread::join
具有上述声明的属性)首先,来自标准 [thread.thread.member] 的thread::join
描述:
无效连接();
- 要求:joinable() 为真。
- 效果:阻止直到 由 *this 表示的线程已完成。
- 同步: 完成由 *这与 (1.10) 同步的线程 相应的成功 join() 返回。
以上显示join()
提供了同步(具体来说:由*this表示的线程的完成与调用join()
的外线程同步)。下一页 [介绍.多线程]:
- 一个评估 A 线程间发生在一个评估 B 之前,如果
(13.1) — A 与 B 同步,或 ...
这表明,由于 a),我们有线程间的完成发生在t
join()
调用返回之前。
最后,[介绍多线程]:
- 如果出现以下情况,则两个操作可能并发
(23.1) — 它们被执行 通过不同的线程,或
(23.2) — 它们是未排序的,至少 一个由信号处理程序执行。
程序的执行 包含数据争用(如果它包含两个潜在的并发) 冲突操作,其中至少一个不是原子的,也不是 发生在另一个之前...
以上描述了数据竞争所需的条件。t.join()
的情况不符合这些条件,因为如图所示,t
的完成实际上确实发生在join()
返回之前。
因此,没有数据竞争,并且所有数据访问都保证了明确定义的行为。
(我想说的是,自从@Smeeheey回答问题以来,你似乎已经以某种重要的方式改变了你的问题;基本上,他回答了你原来措辞的问题,但由于你问了两个不同的问题,所以无法获得信任。这是糟糕的形式 - 将来,请发布一个新问题,以便原始答案者可以按时获得学分。
如果多个线程读取/写入一个变量,即使您知道该变量是按定义的顺序访问的,您仍然必须通知编译器。执行此操作的正确方法必然涉及同步、原子学或记录以执行先验本身之一(例如std::thread::join
)。假设同步路由在实现中既明显又不可取..:
用原子学解决这个问题可能只是由std::atomic_thread_fence
组成;然而,C++中的获取围栏不能单独与释放围栏同步,必须修改实际的原子对象。因此,如果要单独使用围栏,则需要指定std::memory_order_seq_cst
;完成后,您的代码将按其他方式工作。
如果你想坚持发布/获取语义,幸运的是,即使是最简单的原子也可以 - std::atomic_flag
:
std::vector<io_service&> io_services;
struct A {
std::unique_ptr<Object> u;
} a;
std::atomic_flag a_initialized = ATOMIC_FLAG_INIT;
io_services[0].post([&io_services, &a, &a_initialized] {
std::unique_ptr<Object> o{new Object};
a_initialized.clear(std::memory_order_release); // initiates release sequence (RS)
a.u = std::move(o);
a_initialized.test_and_set(std::memory_order_relaxed); // continues RS
io_services[1].post([&a, &a_initialized] {
while (!a_initialized.test_and_set(std::memory_order_acquire)) ; // completes RS
a.u->...;
});
});
有关发布顺序的信息,请参阅此处。
- 在没有太多条件句的情况下,我如何避免被零除
- 为什么在没有显式默认构造函数的情况下,将另一个结构封装在联合中作为成员的结构不能编译
- 在未初始化映射的情况下,将值插入到映射的映射中
- 是默认情况下分配给char数组常量的值
- 为什么我不能在不创建字符串变量的情况下使用函数的字符串输出
- 如何在不产生任何垃圾的情况下获得C中的像素
- 在已经使用Git的情况下减少编译时间
- 为什么在Windows上的VS 2019和Clang 9中"size_t"在没有标题的情况下工作
- 如何在没有信号的情况下从C++执行QML插槽
- 如何在不知道向量大小的情况下输入向量内部的向量?
- 为什么在某些情况下不写入此文件?
- 为什么Mat类的两个对象可以在不重载运算符+的情况下添加
- 在没有Xcode的情况下在Mac捆绑包中嵌入框架
- UE4-如何在给定4个屏幕坐标的情况下缩放纹理或材质
- 为什么需要复制构造函数,在哪些情况下它们非常有用
- 在C++中如何在没有pow的情况下进行基础计算
- 松弛原子与无同步情况下的记忆连贯性
- 在 Windows 上,是否可以让 dll 在不使用 PATH 环境变量的情况下在另一个文件夹中查找依赖项?
- 在内存不增加的情况下逐渐提高cpu使用率.的想法
- 如果已知访问顺序是安全的,如何在没有互斥锁的情况下同步线程/CPU