C++编程实践——无锁数据结构

一、无锁编程和无锁数据结构

无锁编程,Lock-Free Programming,看上去很好理解,就是不使用锁么。这个在前面已经在多线程开发中分析过,同时在C++中的内存序及相关的CAS的分析中进行过详细的说明。然后也明白了,其实C++中的无锁编程,本质并不是字面上简单的"无锁"。重点在于其不使用传统的锁,或者说将锁的机制进行了下移,使用底层的原子操作(它其实更类似于像硬件传递锁的控制)来保证并行操作中的数据安全。无锁编程在一些场景下,能够显著的提高效率,但也会引起一些新的问题。这就需要开发者自行考虑使用无锁编程是否符合实际编程的条件了。

而在无锁编程中,无锁的数据结构是支持无锁编程的基础,本文将针对其进行具体的展开分析。

二、实现的机制

无锁编程的机制,本质是一种"乐观锁",而传统的Mutx等是一种悲观锁。如果想理解无锁编程的机制,就需要明白三个重要的技术点:

  1. 原子操作
    原子操作,有过数据库、操作系统学习的开发者来说很容易理解。所谓操作子操作就是不可分割的操作,这个操作要么执行完成,要么无法执行,要么执行完成,不会出现中间态。即不被任何线程的切换操作所影响。这也是保证操作安全的基础
  2. 内存模型和内存序
    内存模型是处理内存相关操作的整体规则,它包含了内存位置、数据状态以及happen-before等的一系列具体的问题。而内存序---Memory Order则是内存模型具体实现的手段或方式。它设定了内存操作的可见性和顺序性。从而影响到程序的并发行为和结果并进而影响程序的整体性能。
    众所周知的是,代码到运行会经历编译器的优化(编译器级别的指令重排)、CPU乱序执行和缓存的一致性多重处理
  3. ABA问题
    无锁编程中会遇到一个经典的问题,即ABA问题。这个问题很好说明,就像川剧变脸。如果观众在回头时变了一个脸,然后再观众回头前又变回了最初的脸。观众回头再看时,如果不经过其它人告知,它是不知道脸是否变换过。

这三个技术点是无锁编程的基础技术支撑,如果不了解这三个技术,那么谈无锁编程,就会陷入无的放矢的尴尬境地。

三、C++标准提供的方法

针对上述的三个问题,在C++的标准中提供了相应的解决方法。这些解决方法在前面的专项分析中都有过具体的说明。所以此处重点不是对其进行细节的描述而更多的是重点的整体说明:

  1. 原子操作
    在C++标准中,提供了一系列的原子操作接口和类。主要包括在库中。它包含基础原子变量的定义和原子操作的函数接口。原子变量的定义包含整形、布尔型等等,而原子操作包括,原子赋值、读取和加减以及比较交换等。另外随着新的标准的不断增强,一些新的原子定义或操作可能会被不断引入
  2. 内存序
    在C++中提供了memory_order_seq_cst(严格顺序一致)、memory_order_acq_rel(获取-释放操作)等六种内存序(具体可看前面的内存序文章或查阅相关资料)。一般情况下,在X86平台默认使用的TSO内存模型介于 上面二者之间,所以这对大多数的并行开发来说,相对很友好。这也是在此平台上开发人员在并行编程时很少处理内存序的一个重要原因(ARM平台则需要引起注意)
  3. ABA处理
    解决ABA的问题,其实也有不少现成的方法。典型的包括增加操作的版本号,这在数据库和分布式编程中也经常使用;Linux内核中使用的RCU技术;读操作的预声明方式即Hazard Pointer机制或者使用双CAS技术

在理解了技术基础和C++提供的相关技术支撑后,其实就可以实现一些无锁的数据结构。

四、无锁的数据结构

从理论上讲,所有的数据结构都可以无锁处理,但在实际应用中,大多还是体现在常见的数据结构,特别是下面两种类型当中。


  1. 这是最经典的数据结构,后进先出。栈的无锁编程和栈的数据结构本身相关,栈的操作基本都在栈顶,所以无锁编程控制的位置也相对简单,处理好出栈和入栈即可。具体的代码参看示例
  2. 队列
    队列也是在并行编程中很常见的一种数据结构,先进先出。当然它也存在着很多的变种,如双头队列等。无锁队列一般使用链表来实现,重点控制的就是出队和入队两个操作。也就是说,无锁重点就在这两个函数中。代码看示例

需要提醒的是,无锁并不代表效率必然高,性能一定会好。还是要与实际的应用场景匹配,一定要明白这一点,否则会事倍功半,出力不讨好。

五、需要注意的问题

无锁编程需要注意的问题很多。除了前面提到的性能问题。它主要还存在以下一些细节需要注意:

  1. 平台兼容性
    在前面的分析也提到了,不同硬件平台或OS以及相关的编译器等,都有可能对内存模型和原子操作处理等有所不同,而这都影响着无锁编程的实现细节
  2. 标准兼容性
    即使具体到编程语言,不同的语言,甚至同是C++编程,不同的标准下,也可能会有一些细节的不同,这都需要开发者自行进行控制
  3. 优化的特性
    和有锁编程相比,无锁编程有一些优化的方式是相同的,如对内存的优化以及伪共享的处理等。但它也有自己的一些特定的优化方式如特定硬件平台的指令优化等

开发者可以根据自己的情况在进行适当的兼容性处理,通过不同手段如条件编译、架构设计等来满足不同的平台的可控扩展,但不要试图兼容最大化。

六、例程

下面看一个具体的例程:

c++ 复制代码
#include <atomic>
#include <chrono>
#include <iostream>
#include <memory>
#include <optional>
#include <thread>
#include <utility>
#include <vector>

template <typename T> class LockFreeQueue {
private:
  struct Node {
    std::optional<T> value;
    std::atomic<Node *> next;

    Node() : value(std::nullopt), next(nullptr) {}
    explicit Node(T v) : value(std::move(v)), next(nullptr) {}
  };

  std::atomic<Node *> m_head;
  std::atomic<Node *> m_tail;

public:
  LockFreeQueue() {
    Node *dummy = new Node();
    m_head.store(dummy);
    m_tail.store(dummy);
  }

  ~LockFreeQueue() {
    //仅在无并发操作时安全(测试环境)
    Node *node = m_head.load();
    while (node) {
      Node *next = node->next.load();
      delete node;
      node = next;
    }
  }

  void enqueue(T value) {
    Node *newNode = new Node(std::move(value));

    while (true) {
      Node *last = m_tail.load(std::memory_order_acquire);
      Node *next = last->next.load(std::memory_order_acquire);

      // 检查tail是否仍然指向last
      if (m_tail.load(std::memory_order_acquire) != last) {
        continue; // tail已被其他线程更新,重试
      }

      if (next == nullptr) {
        // 尝试将新节点链接到末尾
        if (last->next.compare_exchange_weak(next, newNode, std::memory_order_release, std::memory_order_relaxed)) {
          // 链接成功,尝试推进tail,允许失败
          m_tail.compare_exchange_weak(last, newNode, std::memory_order_release, std::memory_order_relaxed);
          return;
        }
      } else {
        // 尾节点不是真正的尾,帮助推进tail
        m_tail.compare_exchange_weak(last, next, std::memory_order_release, std::memory_order_relaxed);
      }
    }
  }

  std::optional<T> dequeue() {
    while (true) {
      Node *first = m_head.load(std::memory_order_acquire);
      Node *last = m_tail.load(std::memory_order_acquire);
      Node *next = first->next.load(std::memory_order_acquire);

      // 检查head是否仍然指向 first
      if (m_head.load(std::memory_order_acquire) != first) {
        continue;
      }

      if (next == nullptr) {
        return std::nullopt; // 队列为空
      }

      if (first == last) {
        // tail滞后于head,帮助推进tail
        m_tail.compare_exchange_weak(last, next, std::memory_order_release, std::memory_order_relaxed);
        continue;
      }

      T result = std::move(*next->value);

      // 尝试弹出first(出队)
      if (m_head.compare_exchange_weak(first, next, std::memory_order_release, std::memory_order_relaxed)) {
        // 生产环境不能简单delete,需使用风险指针等
        delete first;
        return result;
      }
    }
  }
};

class LockFreeStack {
public:
  struct Node {
    int value = 0;
    Node *next = nullptr;
  };

  void push(Node *node) {
    node->next = m_head.load(std::memory_order_relaxed);
    while (!m_head.compare_exchange_weak(node->next, node, std::memory_order_release, std::memory_order_relaxed)) {
      // 失败则node->next自动更新为当前m_head,继续重试
    }
  }

  Node *pop() {
    Node *oldHead = m_head.load(std::memory_order_acquire);
    while (oldHead && !m_head.compare_exchange_weak(oldHead, oldHead->next, std::memory_order_acquire, std::memory_order_relaxed)) {
      // 失败则oldHead自动更新为当前m_head
    }
    return oldHead; // 可能为nullptr
  }

private:
  std::atomic<Node *> m_head{nullptr};
};

int main() {
  constexpr int producerCount = 4;
  constexpr int consumerCount = 4;
  constexpr int itemsPerProducer = 250000;
  constexpr int totalItems = producerCount * itemsPerProducer;

  LockFreeStack stack;

  // 预分配节点,每个节点只push一次pop一次,避免ABA和并发delete的复杂问题
  std::vector<std::unique_ptr<LockFreeStack::Node>> nodes;
  nodes.reserve(totalItems);
  for (int i = 0; i < totalItems; ++i) {
    auto node = std::make_unique<LockFreeStack::Node>();
    node->value = i + 1;
    nodes.push_back(std::move(node));
  }

  std::atomic<int> producedCount{0};
  std::atomic<int> consumedCount{0};
  std::atomic<long long> consumedSum{0};

  const long long expectedSum = static_cast<long long>(totalItems) * (totalItems + 1) / 2;

  auto startedAt = std::chrono::steady_clock::now();

  // 启动生产者
  std::vector<std::thread> producers;
  producers.reserve(producerCount);
  for (int producerId = 0; producerId < producerCount; ++producerId) {
    producers.emplace_back([&, producerId] {
      const int start = producerId * itemsPerProducer;
      const int end = start + itemsPerProducer;
      for (int i = start; i < end; ++i) {
        stack.push(nodes[i].get());
        producedCount.fetch_add(1, std::memory_order_relaxed);
      }
    });
  }

  // 启动消费者
  std::vector<std::thread> consumers;
  consumers.reserve(consumerCount);
  for (int consumerId = 0; consumerId < consumerCount; ++consumerId) {
    consumers.emplace_back([&] {
      while (consumedCount.load(std::memory_order_relaxed) < totalItems) {
        LockFreeStack::Node *node = stack.pop();
        if (!node) {
          std::this_thread::yield();
          continue;
        }
        consumedSum.fetch_add(node->value, std::memory_order_relaxed);
        consumedCount.fetch_add(1, std::memory_order_relaxed);
      }
    });
  }

  for (auto &t : producers)
    t.join();
  for (auto &t : consumers)
    t.join();

  auto finishedAt = std::chrono::steady_clock::now();
  auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(finishedAt - startedAt).count();

  int produced = producedCount.load(std::memory_order_relaxed);
  int consumed = consumedCount.load(std::memory_order_relaxed);
  long long actualSum = consumedSum.load(std::memory_order_relaxed);

  std::cout << "start Lock-free stack demo!"<<std::endl;
  std::cout << "all producers: " << producerCount << std::endl;
  std::cout << "all consumers: " << consumerCount << std::endl;
  std::cout << "cur produced:  " << produced << std::endl;
  std::cout << "cur consumed:  " << consumed << std::endl;
  std::cout << "sum:       " << actualSum << std::endl;
  std::cout << "expected:  " << expectedSum << std::endl;
  std::cout << "time elapsed:   " << elapsedMs << " ms"<<std::endl;

  if (produced == totalItems && consumed == totalItems && actualSum == expectedSum) {
    std::cout << "success!"<<std::endl;
    return 0;
  } else {
    std::cout << "error!"<<std::endl;
    return 1;
  }
}

七、第三方框架或库

在这种无锁编程的实现中,自己实现当然可以体现开发的实力。但为了能够更安全更快速的工程实现,最好还是引入成熟的第三方框架或库。不同的语言都有自己的适合的库。对于C++来说,一般推荐的有:

  1. Boost.lockfree
    这个不用细说,老牌的库。使用和维护相对要方便,怎么也是号称STL的预备库呢
  2. Folly
    Meta的C++库,其中有很好的无锁支持如MPMCQueue、AtomicHashMap等
  3. moodycamel::ConcurrentQueue
    一个工业级的高性能无锁队列,适合单生产-单消费者情况
  4. CDS
    一个专门的无锁数据结构库,在学术和工业生产中都可以使用

当然,其它的一些大公司如谷歌的Abeil以及国内的百度等也都有类似的实现机制,大家可以根据实际情况来进行具体的选择

八、总结

无锁编程是一种通过底层指令CAS来替代传统同步锁的一种技术。它可以在特定场合下实现极高的吞吐量并且不会产生死锁(高并发交易等)。但一定要知道,任何一种技术都是双刃剑,在无锁编程引入巨大优势的前提下,也要看到其带来的开发维护的复杂度和难度以及内存控制的成本。

正如前面所说,不要妄图靠一两种技术实现包打天下的想法,最合适的才是最好的。

C++编程实践------无锁数据结构

一、无锁编程和无锁数据结构

无锁编程,Lock-Free Programming,看上去很好理解,就是不使用锁么。这个在前面已经在多线程开发中分析过,同时在C++中的内存序及相关的CAS的分析中进行过详细的说明。然后也明白了,其实C++中的无锁编程,本质并不是字面上简单的"无锁"。重点在于其不使用传统的锁,或者说将锁的机制进行了下移,使用底层的原子操作(它其实更类似于像硬件传递锁的控制)来保证并行操作中的数据安全。无锁编程在一些场景下,能够显著的提高效率,但也会引起一些新的问题。这就需要开发者自行考虑使用无锁编程是否符合实际编程的条件了。

而在无锁编程中,无锁的数据结构是支持无锁编程的基础,本文将针对其进行具体的展开分析。

二、实现的机制

无锁编程的机制,本质是一种"乐观锁",而传统的Mutx等是一种悲观锁。如果想理解无锁编程的机制,就需要明白三个重要的技术点:

  1. 原子操作
    原子操作,有过数据库、操作系统学习的开发者来说很容易理解。所谓操作子操作就是不可分割的操作,这个操作要么执行完成,要么无法执行,要么执行完成,不会出现中间态。即不被任何线程的切换操作所影响。这也是保证操作安全的基础
  2. 内存模型和内存序
    内存模型是处理内存相关操作的整体规则,它包含了内存位置、数据状态以及happen-before等的一系列具体的问题。而内存序---Memory Order则是内存模型具体实现的手段或方式。它设定了内存操作的可见性和顺序性。从而影响到程序的并发行为和结果并进而影响程序的整体性能。
    众所周知的是,代码到运行会经历编译器的优化(编译器级别的指令重排)、CPU乱序执行和缓存的一致性多重处理
  3. ABA问题
    无锁编程中会遇到一个经典的问题,即ABA问题。这个问题很好说明,就像川剧变脸。如果观众在回头时变了一个脸,然后再观众回头前又变回了最初的脸。观众回头再看时,如果不经过其它人告知,它是不知道脸是否变换过。

这三个技术点是无锁编程的基础技术支撑,如果不了解这三个技术,那么谈无锁编程,就会陷入无的放矢的尴尬境地。

三、C++标准提供的方法

针对上述的三个问题,在C++的标准中提供了相应的解决方法。这些解决方法在前面的专项分析中都有过具体的说明。所以此处重点不是对其进行细节的描述而更多的是重点的整体说明:

  1. 原子操作
    在C++标准中,提供了一系列的原子操作接口和类。主要包括在库中。它包含基础原子变量的定义和原子操作的函数接口。原子变量的定义包含整形、布尔型等等,而原子操作包括,原子赋值、读取和加减以及比较交换等。另外随着新的标准的不断增强,一些新的原子定义或操作可能会被不断引入
  2. 内存序
    在C++中提供了memory_order_seq_cst(严格顺序一致)、memory_order_acq_rel(获取-释放操作)等六种内存序(具体可看前面的内存序文章或查阅相关资料)。一般情况下,在X86平台默认使用的TSO内存模型介于 上面二者之间,所以这对大多数的并行开发来说,相对很友好。这也是在此平台上开发人员在并行编程时很少处理内存序的一个重要原因(ARM平台则需要引起注意)
  3. ABA处理
    解决ABA的问题,其实也有不少现成的方法。典型的包括增加操作的版本号,这在数据库和分布式编程中也经常使用;Linux内核中使用的RCU技术;读操作的预声明方式即Hazard Pointer机制或者使用双CAS技术

在理解了技术基础和C++提供的相关技术支撑后,其实就可以实现一些无锁的数据结构。

四、无锁的数据结构

从理论上讲,所有的数据结构都可以无锁处理,但在实际应用中,大多还是体现在常见的数据结构,特别是下面两种类型当中。


  1. 这是最经典的数据结构,后进先出。栈的无锁编程和栈的数据结构本身相关,栈的操作基本都在栈顶,所以无锁编程控制的位置也相对简单,处理好出栈和入栈即可。具体的代码参看示例
  2. 队列
    队列也是在并行编程中很常见的一种数据结构,先进先出。当然它也存在着很多的变种,如双头队列等。无锁队列一般使用链表来实现,重点控制的就是出队和入队两个操作。也就是说,无锁重点就在这两个函数中。代码看示例

需要提醒的是,无锁并不代表效率必然高,性能一定会好。还是要与实际的应用场景匹配,一定要明白这一点,否则会事倍功半,出力不讨好。

五、需要注意的问题

无锁编程需要注意的问题很多。除了前面提到的性能问题。它主要还存在以下一些细节需要注意:

  1. 平台兼容性
    在前面的分析也提到了,不同硬件平台或OS以及相关的编译器等,都有可能对内存模型和原子操作处理等有所不同,而这都影响着无锁编程的实现细节
  2. 标准兼容性
    即使具体到编程语言,不同的语言,甚至同是C++编程,不同的标准下,也可能会有一些细节的不同,这都需要开发者自行进行控制
  3. 优化的特性
    和有锁编程相比,无锁编程有一些优化的方式是相同的,如对内存的优化以及伪共享的处理等。但它也有自己的一些特定的优化方式如特定硬件平台的指令优化等

开发者可以根据自己的情况在进行适当的兼容性处理,通过不同手段如条件编译、架构设计等来满足不同的平台的可控扩展,但不要试图兼容最大化。

六、例程

下面看一个具体的例程:

c 复制代码
#include <atomic>
#include <chrono>
#include <iostream>
#include <memory>
#include <optional>
#include <thread>
#include <utility>
#include <vector>

template <typename T> class LockFreeQueue {
private:
  struct Node {
    std::optional<T> value;
    std::atomic<Node *> next;

    Node() : value(std::nullopt), next(nullptr) {}
    explicit Node(T v) : value(std::move(v)), next(nullptr) {}
  };

  std::atomic<Node *> m_head;
  std::atomic<Node *> m_tail;

public:
  LockFreeQueue() {
    Node *dummy = new Node();
    m_head.store(dummy);
    m_tail.store(dummy);
  }

  ~LockFreeQueue() {
    //仅在无并发操作时安全(测试环境)
    Node *node = m_head.load();
    while (node) {
      Node *next = node->next.load();
      delete node;
      node = next;
    }
  }

  void enqueue(T value) {
    Node *newNode = new Node(std::move(value));

    while (true) {
      Node *last = m_tail.load(std::memory_order_acquire);
      Node *next = last->next.load(std::memory_order_acquire);

      // 检查tail是否仍然指向last
      if (m_tail.load(std::memory_order_acquire) != last) {
        continue; // tail已被其他线程更新,重试
      }

      if (next == nullptr) {
        // 尝试将新节点链接到末尾
        if (last->next.compare_exchange_weak(next, newNode, std::memory_order_release, std::memory_order_relaxed)) {
          // 链接成功,尝试推进tail,允许失败
          m_tail.compare_exchange_weak(last, newNode, std::memory_order_release, std::memory_order_relaxed);
          return;
        }
      } else {
        // 尾节点不是真正的尾,帮助推进tail
        m_tail.compare_exchange_weak(last, next, std::memory_order_release, std::memory_order_relaxed);
      }
    }
  }

  std::optional<T> dequeue() {
    while (true) {
      Node *first = m_head.load(std::memory_order_acquire);
      Node *last = m_tail.load(std::memory_order_acquire);
      Node *next = first->next.load(std::memory_order_acquire);

      // 检查head是否仍然指向 first
      if (m_head.load(std::memory_order_acquire) != first) {
        continue;
      }

      if (next == nullptr) {
        return std::nullopt; // 队列为空
      }

      if (first == last) {
        // tail滞后于head,帮助推进tail
        m_tail.compare_exchange_weak(last, next, std::memory_order_release, std::memory_order_relaxed);
        continue;
      }

      T result = std::move(*next->value);

      // 尝试弹出first(出队)
      if (m_head.compare_exchange_weak(first, next, std::memory_order_release, std::memory_order_relaxed)) {
        // 生产环境不能简单delete,需使用风险指针等
        delete first;
        return result;
      }
    }
  }
};

class LockFreeStack {
public:
  struct Node {
    int value = 0;
    Node *next = nullptr;
  };

  void push(Node *node) {
    node->next = m_head.load(std::memory_order_relaxed);
    while (!m_head.compare_exchange_weak(node->next, node, std::memory_order_release, std::memory_order_relaxed)) {
      // 失败则node->next自动更新为当前m_head,继续重试
    }
  }

  Node *pop() {
    Node *oldHead = m_head.load(std::memory_order_acquire);
    while (oldHead && !m_head.compare_exchange_weak(oldHead, oldHead->next, std::memory_order_acquire, std::memory_order_relaxed)) {
      // 失败则oldHead自动更新为当前m_head
    }
    return oldHead; // 可能为nullptr
  }

private:
  std::atomic<Node *> m_head{nullptr};
};

int main() {
  constexpr int producerCount = 4;
  constexpr int consumerCount = 4;
  constexpr int itemsPerProducer = 250000;
  constexpr int totalItems = producerCount * itemsPerProducer;

  LockFreeStack stack;

  // 预分配节点,每个节点只push一次pop一次,避免ABA和并发delete的复杂问题
  std::vector<std::unique_ptr<LockFreeStack::Node>> nodes;
  nodes.reserve(totalItems);
  for (int i = 0; i < totalItems; ++i) {
    auto node = std::make_unique<LockFreeStack::Node>();
    node->value = i + 1;
    nodes.push_back(std::move(node));
  }

  std::atomic<int> producedCount{0};
  std::atomic<int> consumedCount{0};
  std::atomic<long long> consumedSum{0};

  const long long expectedSum = static_cast<long long>(totalItems) * (totalItems + 1) / 2;

  auto startedAt = std::chrono::steady_clock::now();

  // 启动生产者
  std::vector<std::thread> producers;
  producers.reserve(producerCount);
  for (int producerId = 0; producerId < producerCount; ++producerId) {
    producers.emplace_back([&, producerId] {
      const int start = producerId * itemsPerProducer;
      const int end = start + itemsPerProducer;
      for (int i = start; i < end; ++i) {
        stack.push(nodes[i].get());
        producedCount.fetch_add(1, std::memory_order_relaxed);
      }
    });
  }

  // 启动消费者
  std::vector<std::thread> consumers;
  consumers.reserve(consumerCount);
  for (int consumerId = 0; consumerId < consumerCount; ++consumerId) {
    consumers.emplace_back([&] {
      while (consumedCount.load(std::memory_order_relaxed) < totalItems) {
        LockFreeStack::Node *node = stack.pop();
        if (!node) {
          std::this_thread::yield();
          continue;
        }
        consumedSum.fetch_add(node->value, std::memory_order_relaxed);
        consumedCount.fetch_add(1, std::memory_order_relaxed);
      }
    });
  }

  for (auto &t : producers)
    t.join();
  for (auto &t : consumers)
    t.join();

  auto finishedAt = std::chrono::steady_clock::now();
  auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(finishedAt - startedAt).count();

  int produced = producedCount.load(std::memory_order_relaxed);
  int consumed = consumedCount.load(std::memory_order_relaxed);
  long long actualSum = consumedSum.load(std::memory_order_relaxed);

  std::cout << "start Lock-free stack demo!"<<std::endl;
  std::cout << "all producers: " << producerCount << std::endl;
  std::cout << "all consumers: " << consumerCount << std::endl;
  std::cout << "cur produced:  " << produced << std::endl;
  std::cout << "cur consumed:  " << consumed << std::endl;
  std::cout << "sum:       " << actualSum << std::endl;
  std::cout << "expected:  " << expectedSum << std::endl;
  std::cout << "time elapsed:   " << elapsedMs << " ms"<<std::endl;

  if (produced == totalItems && consumed == totalItems && actualSum == expectedSum) {
    std::cout << "success!"<<std::endl;
    return 0;
  } else {
    std::cout << "error!"<<std::endl;
    return 1;
  }
}

七、第三方框架或库

在这种无锁编程的实现中,自己实现当然可以体现开发的实力。但为了能够更安全更快速的工程实现,最好还是引入成熟的第三方框架或库。不同的语言都有自己的适合的库。对于C++来说,一般推荐的有:

  1. Boost.lockfree
    这个不用细说,老牌的库。使用和维护相对要方便,怎么也是号称STL的预备库呢
  2. Folly
    Meta的C++库,其中有很好的无锁支持如MPMCQueue、AtomicHashMap等
  3. moodycamel::ConcurrentQueue
    一个工业级的高性能无锁队列,适合单生产-单消费者情况
  4. CDS
    一个专门的无锁数据结构库,在学术和工业生产中都可以使用

当然,其它的一些大公司如谷歌的Abeil以及国内的百度等也都有类似的实现机制,大家可以根据实际情况来进行具体的选择

八、总结

无锁编程是一种通过底层指令CAS来替代传统同步锁的一种技术。它可以在特定场合下实现极高的吞吐量并且不会产生死锁(高并发交易等)。但一定要知道,任何一种技术都是双刃剑,在无锁编程引入巨大优势的前提下,也要看到其带来的开发维护的复杂度和难度以及内存控制的成本。

正如前面所说,不要妄图靠一两种技术实现包打天下的想法,最合适的才是最好的。