C++之无锁编程

1. 什么是无锁编程

无锁编程(Lock-Free Programming)是并发编程的一种重要技术,它避免了传统的锁机制(例如互斥锁、自旋锁等),并通过院子操作的方式确保线程的安全。无锁编程能够在多线程环境中显著提升程序的并发性和可伸缩性,减少线程之间的竞争、阻塞和上下文切换开销。

1.1 特性

无锁编程和核心目标是减少多线程并发执行时间的竞争和同步开销。详细如下:

  • 提高并发性: 减少线程等待和阻塞的时间,提高程序的并发执行能力;
  • 避免死锁: 传统的锁机制(互斥锁、自旋锁等)可能会导致死锁,影响程序稳定性。无锁编程避免了死锁问题;
  • 减少上下文切换开销: 互斥锁会导致线程阻塞,从而触发上下文切换,而无锁编程可以减少这总开销;
  • 降低延迟: 无锁编程能降低线程在共享资源上的竞争,提供响应速度。

1.2 原理

无锁编程,其实是依赖原子操作来确保线程的安全性,而不是使用锁。原子操作是在并发环境中不可分割的操作,确保了操作的完整性。常见的院子操作有:
原子加减法: 对共享变量进行加法或减法操作,保证在多个线程并发执行时,操作是不可分割。
CAS(Compare and Swap,比较并交换): CAS是无锁编程的核心原子操作之一,主要用于条件性更新数据。它会比较内存中的值和期望值,如果相等则更新,否则不做操作。
原子读写: 读取和写入值属于原子操作,确保并发操作的正确性。
利用上面的原子操作,线程可以再不适用锁的情况下,对共享的数据进行修改和同步,从而避免了锁带来的性能瓶颈。

1.3 应用场景

无锁编程广泛用于高并发、低延迟、低开销的系统中,那么它都有哪些应用场景呢?

1.3.1 高性能队列和栈

无锁队列和栈广泛应用于任务队列、消息队列等并发场景,它们能够确保多个线程并能够并发地操作队列和栈,且不需要加锁。

1.3.2 并发计数器

无锁计数器可以高效地实现多线程环境下的计数操作,避免线程之间的竞争和阻塞。

std::atomic<int> a;
 a.fetch_add(1);
1.3.3 线程池

无锁线程池有效地调度和管理线程,减少线程间的阻塞,提高任务分发的效率。

1.3.4 内存管理

无锁内存分配器和垃圾回收器能够高效管理内存,避免锁操作带来的性能瓶颈。常见的无锁内存分配器如TLSF(Two-Level Segregate Fit)。

1.3.5 高并发数据结构

无锁编程用于构建高效的并发数据结构,如无锁哈希表、链表、树等。这些数据结构能够在多线程环境下并发操作,提升系统的吞吐量。

2. 无锁编程的实现

2.1 无锁队列

无锁队列,使用std::atomic进行实现,如下所示:

#include <iostream>
#include <atomic>
#include <thread>
#include <vector>
#include <chrono>

template<typename T>
class LockFreeQueue{
public:

    struct Node{
        std::atomic<Node*> next;
        T   value;

        Node(T obj): next(nullptr), value(obj){}
        Node(): next(nullptr), value(0){}
    };

public:

    LockFreeQueue(){
        Node* node = new Node(T());

        head.store(node);
        tail.store(node);
    }

    ~LockFreeQueue(){
        while (Node* current = head.load()) {
            Node* next = current->next.load();
            delete current;
            head.store(next);
        }
    }

    void enqueue(T obj){
        Node* node = new Node(obj);

        auto  current = tail.load();
        // CAS operation
        while(!tail.compare_exchange_weak(current, node)){
            current = tail.load();
        }

        // insert node to queue
        current->next.store(node);

    }

    bool dequeue(T& value){

        Node* current = head.load();
        Node* next = current->next.load();

        if(nullptr == next){
            return false;
        }

        // CAS operation
        value = next->value;
        if (current == head.load()){
            while(!head.compare_exchange_weak(current, next)){
                current = head.load();
                next = current->next.load();
                break;
            }
        }

        head.store(next);
        delete current;
        return true;
    }

private:
    std::atomic<Node*> head;
    std::atomic<Node*> tail;
};

无锁队列的测试代码(包含单线程、多线程的测试),如下所示:

int main(int argc, char** argv){
    LockFreeQueue<int> queue;

    int result;
    auto enqueue_fun = [&](int value){
        queue.enqueue(value);
    };


    auto dequeue_fun = [&](){
        int value;
        if (queue.dequeue(value)){
            std::cout << "value: " << value << std::endl;
        }
    };

    std::cout << "1. Single thread" << std::endl;
    queue.enqueue(1);
    queue.enqueue(2);
    queue.enqueue(3);

    while(queue.dequeue(result)){
        std::cout << "result: " << result << std::endl;
    }

    std::cout << "\n\n\n" << std::endl;


    std::cout << "************************************************************************" << std::endl;
    std::cout << "2. Multi thread enqueue" << std::endl;

    std::vector<std::thread> threads;
    for (int i =1; i < 10; ++i){
        threads.push_back(std::thread(enqueue_fun, i));
    }

    std::this_thread::sleep_for(std::chrono::milliseconds(100));

    while(queue.dequeue(result)){
        std::cout << "result: " << result << std::endl;
    }

    for(auto& th: threads){
        if (th.joinable()){
            th.join();
        }
    }
    threads.clear();


    std::cout << "\n\n\n" << std::endl;


    std::cout << "************************************************************************" << std::endl;
    std::cout << "3. Multi thread dequeue" << std::endl;
    std::vector<std::thread> dethreads;
    for (int i =1; i < 10; i++){
        queue.enqueue(i);
    }

    for (int i =1; i < 10; ++i){
        dethreads.push_back(std::thread(dequeue_fun));
    }

    for(auto& th: dethreads){
        if (th.joinable()){
            th.join();
        }
    }
    dethreads.clear();


    return 0;
}

2.2 无锁循环buffer

无锁循环buffer,使用std::atomic进行实现,如下所示

#include <iostream>
#include <atomic>
#include <thread>
#include <vector>
#include <chrono>
#include <string.h>

#define BUFFER_SIZE (128)

template<typename T>
class LockFreeBuffer{
public:
    struct CircularBuffer{
        T buffer[BUFFER_SIZE];
        std::atomic_size_t read_index;
        std::atomic_size_t write_index;
    };

public:

    LockFreeBuffer(){
        memset(buffer.buffer, 0, BUFFER_SIZE * sizeof(T));
        buffer.read_index.store(0);
        buffer.write_index.store(0);
    }

    ~LockFreeBuffer(){
        memset(buffer.buffer, 0, BUFFER_SIZE * sizeof(T));
        buffer.read_index.store(0);
        buffer.write_index.store(0);
    }

    bool reset(){
        memset(buffer.buffer, 0, BUFFER_SIZE * sizeof(T));
        buffer.read_index.store(0);
        buffer.write_index.store(0);
        return true;
    }

    size_t length(){
        if (buffer.read_index.load() < buffer.write_index.load()){
            return buffer.write_index.load() - buffer.read_index.load();
        } else if (buffer.read_index.load() > buffer.write_index.load()) {
            return (BUFFER_SIZE - buffer.read_index.load() + buffer.write_index.load());
        } else {
            return 0;
        }
    }

    bool enqueue(T data){
        while(true){
            size_t write_pos = buffer.write_index.load();
            size_t next_pos = (write_pos + 1) % BUFFER_SIZE;
            
            if(next_pos == buffer.read_index.load()) {
                return false;
            }

            if (buffer.write_index.compare_exchange_weak(write_pos, next_pos)){
                buffer.buffer[write_pos] = data;
                return true;
            }
        }
    }

    bool dequeue(T& value){
        while(true){
            size_t read_pos = buffer.read_index.load();
            // std::cout << read_pos << ":" << buffer.write_index.load() << std::endl;
            if (read_pos == buffer.write_index.load()){
                return false;
            }

            value = buffer.buffer[read_pos];
            if (buffer.read_index.compare_exchange_weak(read_pos, (read_pos + 1)%BUFFER_SIZE)){
                return true;
            }
        }
    }

private:
    CircularBuffer buffer;

}; // class LockFreeBuffer

无锁循环buffer的测试代码(包含单线程、多线程的测试),如下所示:

int main(int argc, char** argv){
    LockFreeBuffer<int> buffer;
    int result;

    auto enqueue_fun = [&](int data){
        if (!buffer.enqueue(data)){
            std::cout << "enqueue buffer failed: " << data << std::endl;
        }
    };

    auto dequeue_fun = [&](){
        int data;
        if (buffer.dequeue(data)){
            std::cout << "result: " << data << std::endl;
        }
    };

    std::cout << "1. Single thread" << std::endl;
    for(int i = 0; i < 10; i++){
        if (!buffer.enqueue(i)){
            std::cout << "enqueue buffer failed: " << i << std::endl;
        }
    }


    while(buffer.dequeue(result)){
        std::cout << "result: " << result << std::endl;
    }


    std::cout << "\n\n\n" << std::endl;
    std::cout << "************************************************************************" << std::endl;


    std::cout << "2. Multi thread enqueue" << std::endl;
    buffer.reset();

    std::vector<std::thread> threads_v1;
    for(int i = 0; i < 10; i++){
        threads_v1.push_back(std::thread(enqueue_fun, i));
    }
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::cout << "buffer size:" << buffer.length() << std::endl;

    while(buffer.dequeue(result)){
        std::cout << "result: " << result << std::endl;
    }

    for(auto& th: threads_v1){
        if (th.joinable()){
            th.join();
        }
    }
    threads_v1.clear();

    std::cout << "\n\n\n" << std::endl;
    std::cout << "************************************************************************" << std::endl;


    std::cout << "3. Multi thread dequeue" << std::endl;
    buffer.reset();
    for(int i = 0; i < 10; i++){
        if (!buffer.enqueue(i)){
            std::cout << "enqueue buffer failed: " << i << std::endl;
        }
    }

    std::vector<std::thread> threads_v2;
    for(int i = 0; i < 10; i++){
        threads_v2.push_back(std::thread(dequeue_fun));
    }

    for(auto& th: threads_v2){
        if (th.joinable()){
            th.join();
        }
    }
    threads_v2.clear();


    std::cout << "\n\n\n" << std::endl;
    std::cout << "************************************************************************" << std::endl;


    std::cout << "4. Multi thread dequeue & enqueue" << std::endl;
    std::vector<std::thread> threads_v3;
    for(int i = 0; i < 10; i++){
        threads_v3.push_back(std::thread(enqueue_fun, i));
    }
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::cout << "buffer size:" << buffer.length() << std::endl;


    for(int i = 0; i < 10; i++){
        threads_v3.push_back(std::thread(dequeue_fun));
    }

    for(auto& th: threads_v3){
        if (th.joinable()){
            th.join();
        }
    }
    threads_v3.clear();

    return 0;
}

3. 无锁编程的不足之处

无锁编程能显著提高并发的性能,但是它也有不足之处,会带来新的挑战:

3.1 ABA问题

ABA问题是在并发编程中常见的一个问题,特别是在使用无锁数据结构(如CAS,Compare-And-Swap)时。ABA问题指的是一个变量的值从A变为B,然后又变回A,虽然最终值仍然是A,但在这期间可能发生了其他操作,导致程序逻辑出错。

// ABA问题示例
struct Node {
    int value;
    Node* next;
};

bool CAS(Node** ptr, Node* old_value, Node* new_value) {
    // 可能发生ABA问题
    return atomic_compare_exchange_strong(ptr, &old_value, new_value);
}

在 CAS 操作中,ABA 问题指的是一个值在 CAS 检查和更新之间发生了变化,最终 CAS 仍然会认为值没有改变。为了解决 ABA 问题,通常可以通过引入版本号或者使用指针标记技术来标识值的变化。

// 引入版本号的方式
#include <stdint.h>
#include <stdatomic.h>
#include <stdio.h>

typedef struct {
    void* ptr;
    uint16_t version;
} ABAQuestion;

ABAQuestion global_ptr = { NULL, 0 };

void* atomic_update(void* new_ptr) {
    ABAQuestion old_val, new_val;
    do {
        old_val = global_ptr;
        new_val.ptr = new_ptr;
        new_val.version = old_val.version + 1;
    } while (!atomic_compare_exchange_strong(&global_ptr, &old_val, new_val));
    return old_val.ptr;
}

int main() {
    int x = 10;
    atomic_update(&x);
    printf("Updated pointer with new version\n");
    return 0;
}

3.2 设计复杂性

无锁编程的设计和实现比传统锁算法复杂得多。编写无锁数据结构和算法需要对并发性、内存模型、原子操作等有深入的理解。特别是在多核或多处理器的环境中,内存一致性和原子性问题更加复杂。
此外,由于无锁编程的并发行为是难以预测的,其调试难度和验证无锁程序的正确性可见是非常困难的。竞争条件、ABA问题等并发问题可能难以复现和诊断。

3.3 可扩展性限制

扩展性限制主要由两个方面,即硬件限制和算法限制:
硬件限制,无锁编程的性能优势依赖于硬件的支持。在某些硬件架构上,原子操作的开销可能较高,导致无锁编程的性能优势不明显。
算法限制,并非所有的并发算法都适合无锁编程。某些算法可能无法有效地转换为无锁形式,或者无锁实现会导致性能下降。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值