我感觉这可能是一种非常普遍和常见的情况,对此有一个众所周知的无锁解决方案。
简而言之,我希望有一种像读取器/写入器锁这样的方法,但这不需要读取器获取锁,因此可以获得更好的平均性能。
相反,对于读取器来说会有一些原子操作(128 位 CAS),对于写入器来说会有一个互斥锁。我有数据结构的两个副本,一个用于正常成功查询的只读副本,以及一个要在互斥锁保护下更新的相同副本。将数据插入可写副本后,我们将其设为新的可读副本。一旦所有待处理的读取器都读完它,旧的可读副本就会被依次插入,写入器会旋转剩余的读取器数量,直到其为零,然后依次修改它,最后释放互斥体。
或类似的东西。
存在这样的东西吗?
我正在阅读The Art of Multiprocessor Programming, 2ed并且我注意到以下模式用于读取多个Atomic*字段:
while (true) {
var v1 = atomic1.get();
var v2 = atomic2.get();
...
var vN = atomicN.get();
if (v1 == atomic1.get()) {
// do work
}
}
Run Code Online (Sandbox Code Playgroud)
这个建设的目的是什么?
我在书中找到的唯一解释是:
... 检查读取的值是否一致 ...
我不明白这个解释。
这是LockFreeQueue,它使用了书中的这种模式:
public class LockFreeQueue<T> {
AtomicReference<Node> head, tail;
public LockFreeQueue() {
Node node = new Node(null);
head = new AtomicReference(node);
tail = new AtomicReference(node);
}
public void enq(T value) {
Node node = new Node(value);
while …Run Code Online (Sandbox Code Playgroud) 我正在尝试实现一个缓存,该缓存必须(显然)针对获取和回收项目进行优化。
但存在一个挑战:缓存可能会不时失效。
从架构的角度来看,到目前为止我所做的是实现包含池的缓存。该池本身实际上是一个无锁对象池,经过充分测试并且可以正常工作。
一个问题是允许池本身与更新版本进行交换。我认为我已经进行了交换(参考:两个 unique_ptr<T> 的无锁交换)。
但是如何使用可以在操作期间交换的池(分别是下面的 getPreparedDefaultRule() 和 recyclePreparedDefaultRule())来处理无锁 get 和 set 呢?
我几乎认为这是不可能的。尽管我很高兴被证明是错误的。
template <typename Data, typename Delegate>
class DefaultRulePoolCache {
public:
DefaultRulePoolCache() : _atomicDefaultRulePool(nullptr), _defaultRulePool(nullptr) {
}
std::unique_ptr<detail::PreparedRule<Data, Delegate>> getPreparedDefaultRule() {
auto atomicPool = _atomicDefaultRulePool.load();
if (!atomicPool) {
return nullptr;
}
return atomicPool->getPreparedRule();
}
void recyclePreparedDefaultRule(std::unique_ptr<detail::PreparedRulePool<Data, Delegate>> preparedRule) {
auto atomicPool = _atomicDefaultRulePool.load();
if (!atomicPool) {
return;
}
hdmCheck(preparedRule) << "Cannot recycle null rule.";
// we return the prepared rule only to the pool in …Run Code Online (Sandbox Code Playgroud) 无锁堆栈可以实现为单链表。这看起来很简单,直到我们必须考虑在弹出节点后如何处理它们。一种策略是简单地将它们移动到每个堆栈的 LIFO 空闲列表(后续的推送操作可以重用该节点),直到最终所有线程都完成了堆栈,此时单个线程会销毁堆栈中的所有节点和所有节点。空闲列表中的节点。Boost.Lockfree使用这种策略。Chris Wellons 的 C11 实现也是如此。我将参考后者,因为它更容易阅读,而且细节基本相同,因为 C11 原子与 C++11 原子非常相似。
在 Wellons 的实现中(可以在 GitHub 上找到),所有lstack_node对象都是非原子的。特别是,这意味着对对象next成员的所有访问lstack_node都是非原子的。我无法理解的是:为什么此类访问从不相互竞争?
该成员在lstack.c:30 处next读取。它写在lstack.c:39。如果这两行可以在同一个对象上同时执行,则该程序包含竞争。这可能吗?对我来说似乎有可能:lstack_node
lstack_pop,它调用pop. 它以原子方式将头节点的值加载到局部变量中orig。现在,orig.node是指向刚刚位于堆栈顶部的节点的指针。(请注意,到目前为止,仅修改了局部变量,因此线程 1 迄今为止所做的任何操作都不可能导致任何其他线程中的 CAS 失败。)同时...lstack_pop. pop成功并返回node,指向刚刚从堆栈中删除的节点的指针;这与线程 1 中指向的节点相同。orig.node然后它开始调用push以添加node到空闲列表中。自由列表头节点以原子方式加载,并node->next设置为指向自由列表中的第一个节点。orig.node->next。难道 Wellons 的实施根本就是错误的吗?我对此表示怀疑。如果他的实现是错误的,那么 Boost …
存在三种不同类型的“无锁”算法。并发实践中给出的定义是:
\n\n\n非正式地,“无锁”\xe2\x89\x88“不使用互斥体”==其中任何一个。
\n
我不明白为什么基于锁的算法不能落入上面给出的无锁定义。这是一个简单的基于锁的程序:
\n#include <iostream>\n#include <mutex>\n#include <thread>\n\nstd::mutex x_mut;\n\nvoid print(int& x) {\n std::lock_guard<std::mutex> lk(x_mut);\n std::cout << x;\n}\n\nvoid add(int& x, int y) {\n std::lock_guard<std::mutex> lk(x_mut);\n x += y;\n}\n\nint main() {\n\n int i = 3;\n\n std::thread thread1{print, std::ref(i)};\n\n std::thread thread2(add, std::ref(i), 4);\n\n thread1.join();\n\n thread2.join();\n\n}\nRun Code Online (Sandbox Code Playgroud)\n如果这两个线程都在运行,那么在有限数量的步骤之后,其中一个必须完成。为什么我的程序不满足“无锁”的定义?
\n我有一个许多线程正在写入的数组。然而,每个线程都有一个可以写入的预先分配的索引范围。此外,在所有线程完成之前,不会从数组中读取任何内容。
到目前为止,线程安全。当我需要扩展数组时,问题就出现了,这当然是指将其交换为复制第一个数组的更大数组。这只是偶尔完成(类似于 ArrayList)。
目前,我正在为数组的每次写入获取锁。尽管不需要锁定来保持数组一致,但我必须锁定以防当前正在复制/交换数组。
由于有很多写入,我不想要求它们锁定。我同意这样的解决方案:仅在复制和交换数组时才需要锁定编写器线程,因为这种情况并不常见。
但我不能只在复制/交换正在进行时才施加写锁,因为线程可能已经在向旧数组提交写入。
我认为我需要某种屏障来等待所有写入完成,然后在复制/交换数组时暂停线程。但是 CyclicBarrier 需要我确切地知道当前有多少线程处于活动状态,这并不简单,并且可能容易受到边缘情况的影响,在这种情况下,屏障最终会永远等待,或者过早地降低自身。特别是,我不确定如何在屏障已经启动时处理进入的新线程,或者如何处理当前正在轮询作业队列的线程,因此在没有屏障的情况下永远不会减少屏障计数新工作。
我可能必须实现一些(原子地)计算活动线程并尝试抢占所有边缘情况的东西。
但这很可能是一个我不知道的“已解决”问题,所以我希望可能有一个比循环屏障/线程计数更简单(因此更好)的解决方案。理想情况下,它使用现有的实用程序类。
顺便说一句,我考虑过 CopyOnWriteArray。这对我来说没有用,因为它会为每次写入(很多次)进行复制,而不仅仅是数组扩展。
另请注意,写入的结构几乎必须是数组或基于数组的结构。
谢谢
我已经阅读了博客,但我不确定他的结论是否正确:
http://www.javacodegeeks.com/2010/09/java-best-practices-queue-battle-and.html#ixzz1seaiSLwp
他说:从提供的性能结果可以看出,LinkedBlockingQueue实现了最佳的组合(添加和删除元素)性能结果,应该是实现生产者 - 消费者schenarios的头号候选者.
我想知道,如果我不在我的代码中使用锁定,那么它会不会更快?
那么为什么LinkedBlockingQueue比无锁队列(ConcurrentLinkedQueue)更快?
谢谢 !
我使用std::atomicC++ 11中的new生成了一个简单的无锁(lockfree)队列实现.我无法看到我在这里做错了什么.
#include <atomic>
template<typename T>
class lockless_queue
{
public:
template<typename DataType>
struct node
{
node(const DataType& data)
: data(data), next(nullptr) {}
DataType data;
node* next;
};
lockless_queue()
: head_(nullptr) {}
void produce(const T &data)
{
node<T>* new_node = new node<T>(data);
// put the current value of head into new_node->next
new_node->next = head_.load(std::memory_order_relaxed);
// now make new_node the new head, but if the head
// is no longer what's stored in new_node->next
// (some other thread must have …Run Code Online (Sandbox Code Playgroud) 我正在考虑实现一个无锁的圆形数组.一个问题是以无锁的方式保持头部和尾部指针.我想到的代码是:
int circularIncrementAndGet(AtomicInteger i) {
i.compareAndSet(array.length - 1, -1);
return i.incrementAndGet();
}
Run Code Online (Sandbox Code Playgroud)
然后我会做类似的事情:
void add(double value) {
int idx = circularIncrementAndGet(tail);
array[idx] = value;
}
Run Code Online (Sandbox Code Playgroud)
(注意,如果数组是完整的旧值将被覆盖,我很好).
有没有人发现这个设计有问题?我怀疑可能存在我没有看到的竞争状况.
该站点介绍了C ++ 11原子fetch_mult,并提供了默认std::atomic<T>类型未提供的原子操作的示例实现:
#include <atomic>
#include <iostream>
template <typename T>
T fetch_mult(std::atomic<T>& shared, T mult){
T oldValue= shared.load();
// 1
while (!shared.compare_exchange_strong(oldValue, oldValue * mult));
return oldValue;
}
int main(){
std::atomic<int> myInt{5};
std::cout << myInt << std::endl;
fetch_mult(myInt,5);
std::cout << myInt << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
我在理解此功能时遇到麻烦。如果fetch_mult在点中断// 1另一个线程还呼吁fetch_mult,也不会一个线程死锁,因为compare_exchange_strong永远不会返回true,除非任何mult==1或者另一个线程组值回oldValue?
例如(T1和T2是各自的线程):
oldValue = 5;oldValue = 5;compare_exchange_strong成功将值设置为25compare_exchange_strong …