标签: lock-free

读者/写者锁...没有读者锁?

我感觉这可能是一种非常普遍和常见的情况,对此有一个众所周知的无锁解决方案。

简而言之,我希望有一种像读取器/写入器锁这样的方法,但这不需要读取器获取锁,因此可以获得更好的平均性能。

相反,对于读取器来说会有一些原子操作(128 位 CAS),对于写入器来说会有一个互斥锁。我有数据结构的两个副本,一个用于正常成功查询的只读副本,以及一个要在互斥锁保护下更新的相同副本。将数据插入可写副本后,我们将其设为新的可读副本。一旦所有待处理的读取器都读完它,旧的可读副本就会被依次插入,写入器会旋转剩余的读取器数量,直到其为零,然后依次修改它,最后释放互斥体。

或类似的东西。

存在这样的东西吗?

c++ concurrency lock-free lockless stdatomic

6
推荐指数
2
解决办法
2740
查看次数

无锁队列:为什么读取 `Atomic*` 两次?

我正在阅读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)

java multithreading atomic lock-free

6
推荐指数
1
解决办法
139
查看次数

可交换对象池的无锁缓存

我正在尝试实现一个缓存,该缓存必须(显然)针对获取和回收项目进行优化。

但存在一个挑战:缓存可能会不时失效。

从架构的角度来看,到目前为止我所做的是实现包含池的缓存。该池本身实际上是一个无锁对象池,经过充分测试并且可以正常工作。

一个问题是允许池本身与更新版本进行交换。我认为我已经进行了交换(参考:两个 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)

c++ lock-free

6
推荐指数
0
解决办法
207
查看次数

带有空闲列表的无锁堆栈:为什么下一个指针不需要是原子的?

无锁堆栈可以实现为单链表。这看起来很简单,直到我们必须考虑在弹出节点后如何处理它们。一种策略是简单地将它们移动到每个堆栈的 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

  • 线程 1 调用lstack_pop,它调用pop. 它以原子方式将头节点的值加载到局部变量中orig。现在,orig.node是指向刚刚位于堆栈顶部的节点的指针。(请注意,到目前为止,仅修改了局部变量,因此线程 1 迄今为止所做的任何操作都不可能导致任何其他线程中的 CAS 失败。)同时...
  • 线程 2 调用lstack_pop. pop成功并返回node,指向刚刚从堆栈中删除的节点的指针;这与线程 1 中指向的节点相同。orig.node然后它开始调用push以添加node到空闲列表中。自由列表头节点以原子方式加载,并node->next设置为指向自由列表中的第一个节点。
  • 哎呀。这与线程 1 中的读取竞争orig.node->next

难道 Wellons 的实施根本就是错误的吗?我对此表示怀疑。如果他的实现是错误的,那么 Boost …

c c++ boost atomic lock-free

6
推荐指数
2
解决办法
819
查看次数

无锁的定义

存在三种不同类型的“无锁”算法。并发实践中给出的定义是:

\n
    \n
  1. 无阻碍:如果所有其他线程都暂停,则任何给定的\n线程都将在有限的步骤中完成其操作。
  2. \n
  3. 无锁:如果多个线程正在对一个数据结构进行操作,那么在有限数量的步骤之后,其中一个线程将完成其操作。
  4. \n
  5. 无等待:每个操作数据结构的线程都将在有限的步骤中完成其操作,即使其他线程也在操作该数据结构。
  6. \n
\n

Herb Sutter 在他的演讲《无锁编程》中说道:

\n
\n

非正式地,“无锁”\xe2\x89\x88“不使用互斥体”==其中任何一个。

\n
\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}\n
Run Code Online (Sandbox Code Playgroud)\n

如果这两个线程都在运行,那么在有限数量的步骤之后,其中一个必须完成。为什么我的程序不满足“无锁”的定义?

\n

c++ multithreading computer-science lock-free lockless

6
推荐指数
1
解决办法
1383
查看次数

Java中的无锁数组扩展

我有一个许多线程正在写入的数组。然而,每个线程都有一个可以写入的预先分配的索引范围。此外,在所有线程完成之前,不会从数组中读取任何内容。

到目前为止,线程安全。当我需要扩展数组时,问题就出现了,这当然是指将其交换为复制第一个数组的更大数组。这只是偶尔完成(类似于 ArrayList)。

目前,我正在为数组的每次写入获取锁。尽管不需要锁定来保持数组一致,但我必须锁定以防当前正在复制/交换数组。

由于有很多写入,我不想要求它们锁定。我同意这样的解决方案:仅在复制和交换数组时才需要锁定编写器线程,因为这种情况并不常见。

但我不能只在复制/交换正在进行时才施加写锁,因为线程可能已经在向旧数组提交写入。

我认为我需要某种屏障来等待所有写入完成,然后在复制/交换数组时暂停线程。但是 CyclicBarrier 需要我确切地知道当前有多少线程处于活动状态,这并不简单,并且可能容易受到边缘情况的影响,在这种情况下,屏障最终会永远等待,或者过早地降低自身。特别是,我不确定如何在屏障已经启动时处理进入的新线程,或者如何处理当前正在轮询作业队列的线程,因此在没有屏障的情况下永远不会减少屏障计数新工作。

我可能必须实现一些(原子地)计算活动线程并尝试抢占所有边缘情况的东西。

但这很可能是一个我不知道的“已解决”问题,所以我希望可能有一个比循环屏障/线程计数更简单(因此更好)的解决方案。理想情况下,它使用现有的实用程序类。

顺便说一句,我考虑过 CopyOnWriteArray。这对我来说没有用,因为它会为每次写入(很多次)进行复制,而不仅仅是数组扩展。

另请注意,写入的结构几乎必须是数组或基于数组的结构。

谢谢

java multithreading lock-free

6
推荐指数
1
解决办法
186
查看次数

LinkedBlockingQueue和ConcurrentLinkedQueue有什么不同?

我已经阅读了博客,但我不确定他的结论是否正确:

http://www.javacodegeeks.com/2010/09/java-best-practices-queue-battle-and.html#ixzz1seaiSLwp

他说:从提供的性能结果可以看出,LinkedBlockingQueue实现了最佳的组合(添加和删除元素)性能结果,应该是实现生产者 - 消费者schenarios的头号候选者.

我想知道,如果我不在我的代码中使用锁定,那么它会不会更快?

那么为什么LinkedBlockingQueue比无锁队列(ConcurrentLinkedQueue)更快?

谢谢 !

java lock-free java.util.concurrent concurrent-programming

5
推荐指数
1
解决办法
6651
查看次数

使用std :: atomic的C++ 11无锁队列(多编写器,单个用户)

我使用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)

c++ multithreading atomic lock-free c++11

5
推荐指数
1
解决办法
6980
查看次数

无锁圆阵

我正在考虑实现一个无锁的圆形数组.一个问题是以无锁的方式保持头部和尾部指针.我想到的代码是:

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)

(注意,如果数组是完整的旧值将被覆盖,我很好).

有没有人发现这个设计有问题?我怀疑可能存在我没有看到的竞争状况.

java circular-buffer lock-free

5
推荐指数
1
解决办法
1840
查看次数

fetch_mult的这种原子实现是否正确?

该站点介绍了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是各自的线程):

  • T1: oldValue = 5;
  • T2: oldValue = 5;
  • T2:compare_exchange_strong成功将值设置为25
  • T1:compare_exchange_strong …

c++ thread-safety lock-free atomicity stdatomic

5
推荐指数
1
解决办法
77
查看次数