螺纹 - 本地获取/释放同步

Thread-local acquire/release synchronization

本文关键字:释放 同步 获取 螺纹      更新时间:2023-10-16

一般而言,负载量/商店释放同步是C 11内存模型中基于内存订购的同步的最常见形式之一。基本上,这是MUTEX提供内存订购的方式。从不同的观察者线程之间始终同步负载式和释放之间的"临界部分",因为所有观察者线程都会同意获取后和发行前的情况。

> 。

通常,这是通过阅读修改的写入指令(例如比较交换(以及在输入关键部分时的收购障碍,以及在退出关键时带有释放障碍的另一种读取模式写入指令来实现的部分。

但是在某些情况下,您可能在负载式和释放商店之间具有相似的临界部分 [1],但实际上只有一个线程实际修改了同步变量。其他线程可以读取同步变量,但实际上只有一个线程对其进行了修改。在这种情况下,在输入关键部分时,您不需要读取 - 修改写入指令。您只需要一个简单的商店,因为您没有与其他试图修改同步标志的线程一起竞争。(这似乎很奇怪,但是请注意,许多无锁的内存填海递延模式,例如用户空间RCU或基于Epoch的回收,都使用线程 - 本地同步变量,仅通过一个线程编写,但由许多线程读取,但请读取许多线程,因此这不是一个情况。(

所以,当输入关键部分时,您可以做类似的事情:

sync_var.store(true, ...);
.... critical section ....
sync_var.store(false, std::memory_order_release);

没有种族,因为同样,当只有一个线程需要设置/删除关键部分变量时,就不需要读取模式写入。其他线程可以简单地读取载荷范围的关键部分变量。

问题是,当您输入关键部分时,您需要获取操作或围栏。但是您不需要承载,只有一家商店。那么,当您真正需要一家商店时,如何生产订购的好方法是什么呢?我只看到两个真正的选项属于C 内存模型。两者:

  1. 使用exchange代替商店,因此您可以进行sync_var.exchange(true, std::memory_order_acquire)。不利的一面是,当您真正需要的只是一家简单的商店时,Exchange是一个更重量的阅读模型操作。
  2. 插入一个"虚拟"负载 - 类似:

    (void(sync_var.load(std :: memory_order_acquire(;sync_var.store(true,std :: memory_order_relaxed(;

"虚拟"负载量似乎更好。据推测,编译器无法优化未使用的负载,因为它是一种原子指令,具有与sync_var上的释放操作产生"同步"关系的副作用。但这似乎也很刺耳,目的不清楚,没有解释发生了什么的评论。

那么,当我们需要做的就是简单的商店时,生产获取语义的最佳方法是什么?


[1]我松散地使用"关键部分"一词。我不一定是指始终通过相互排除访问的部分。相反,我只是指通过获取释放语义同步内存排序的任何部分。这可能是指mutex,也可能意味着诸如rcu之类的东西,其中关键部分可以由多个读者同时访问。

逻辑中的缺陷是不需要原子RMW,因为临界部分中的数据是由单个线程修改的,而所有其他线程都只有读取。

这不是真的;在阅读和写作之间仍然需要明确定义的顺序。当另一个线程仍在读取它时,您不希望对数据进行修改。因此,每个线程完成访问数据后需要告知其他线程。

仅使用原子存储进入关键部分,就无法建立"同步"关系。获取/释放同步基于运行时关系,在该关系中,收购方知道同步仅在观察原子负载返回的特定值后才完成。单个原子商店永远无法实现这一点,因为一个修改线程可以随时更改原子变量sync_var因此,它无法知道其他线程是否仍在读取数据。

带有"虚拟" load/acquire的选项也无效,因为它无法告知其他线程,因此需要独家访问。您尝试通过使用单个(放松(商店来解决该问题,但是负载和商店是单独的操作,可以被其他线程中断(即同时访问关键区域的多个线程(。

必须使用每个原子RMW使用线程加载特定值,同时更新变量以告知所有其他线程,以告知其现在具有独家访问(无论是阅读还是写作(。

void lock()
{
    while (sync_var.exchange(true, std::memory_order_acquire));
}
void unlock()
{
    sync_var.store(false, std::memory_order_release);
}

可以进行优化,而多个线程同时具有读取访问(例如std::shared_mutex(。