C++并发编程实战:简化代码的艺术

C++并发编程实战:简化代码的艺术

函数化编程与并发

在并发编程中,函数化编程(FP)范式提供了一种简化代码的优雅方式。FP的核心思想是纯函数——函数的输出仅依赖于输入参数,不修改任何外部状态。这种特性使得FP天然适合并发环境,因为它消除了共享数据带来的竞争条件问题。

快速排序的FP实现

让我们通过快速排序算法来展示FP在并发编程中的应用。传统的快速排序是命令式风格,而FP版本则通过递归和不可变数据结构实现:

template<typename T>
std::list<T> sequential_quick_sort(std::list<T> input) {
    if(input.empty()) return input;
    
    std::list<T> result;
    result.splice(result.begin(), input, input.begin());
    T const& pivot = *result.begin();
    
    auto divide_point = std::partition(input.begin(), input.end(),
        [&](T const& t){ return t < pivot; });
        
    std::list<T> lower_part;
    lower_part.splice(lower_part.end(), input, input.begin(), divide_point);
    
    auto new_lower = sequential_quick_sort(std::move(lower_part));
    auto new_higher = sequential_quick_sort(std::move(input));
    
    result.splice(result.end(), new_higher);
    result.splice(result.begin(), new_lower);
    return result;
}

这个实现的关键特点:

  1. 不修改原始输入,而是创建新列表
  2. 使用递归而非循环
  3. 通过移动语义提高效率

并行化FP快速排序

将FP版本的快速排序并行化非常简单,只需将递归调用改为异步执行:

template<typename T>
std::list<T> parallel_quick_sort(std::list<T> input) {
    if(input.empty()) return input;
    
    std::list<T> result;
    result.splice(result.begin(), input, input.begin());
    T const& pivot = *result.begin();
    
    auto divide_point = std::partition(input.begin(), input.end(),
        [&](T const& t){ return t < pivot; });
        
    std::list<T> lower_part;
    lower_part.splice(lower_part.end(), input, input.begin(), divide_point);
    
    std::future<std::list<T>> new_lower(
        std::async(&parallel_quick_sort<T>, std::move(lower_part)));
    
    auto new_higher = parallel_quick_sort(std::move(input));
    
    result.splice(result.end(), new_higher);
    result.splice(result.begin(), new_lower.get());
    return result;
}

这种并行化方式利用了硬件并发能力,每个递归分支都可以在独立线程中执行。注意使用std::async会自动管理线程资源,避免创建过多线程。

基于消息传递的并发模型

另一种简化并发编程的方式是消息传递模型,也称为参与者模式(Actor Model)。在这种模型中:

  1. 每个线程是独立的"参与者"
  2. 参与者之间通过消息通信,不共享数据
  3. 每个参与者维护自己的状态机

ATM状态机示例

考虑一个ATM机的简化实现,我们可以将其逻辑建模为状态机:

struct card_inserted {
    std::string account;
};

class atm {
    messaging::receiver incoming;
    messaging::sender bank;
    messaging::sender interface_hardware;
    void (atm::*state)();
    
    std::string account;
    std::string pin;
    
    void waiting_for_card() {
        interface_hardware.send(display_enter_card());
        incoming.wait()
            .handle<card_inserted>([&](card_inserted const& msg) {
                account = msg.account;
                pin = "";
                interface_hardware.send(display_enter_pin());
                state = &atm::getting_pin;
            });
    }
    
    void getting_pin() {
        incoming.wait()
            .handle<digit_pressed>([&](digit_pressed const& msg) {
                pin += msg.digit;
                if(pin.length() == 4) {
                    bank.send(verify_pin(account, pin, incoming));
                    state = &atm::verifying_pin;
                }
            })
            .handle<clear_last_pressed>([&](clear_last_pressed const& msg) {
                if(!pin.empty()) pin.resize(pin.length()-1);
            })
            .handle<cancel_pressed>([&](cancel_pressed const& msg) {
                state = &atm::done_processing;
            });
    }
    
    // 其他状态函数...
    
public:
    void run() {
        state = &atm::waiting_for_card;
        try {
            for(;;) {
                (this->*state)();
            }
        } catch(messaging::close_queue const&) {}
    }
};

这种设计模式的优点:

  • 明确的线程职责划分
  • 无共享数据,避免竞争条件
  • 状态转换清晰可见
  • 易于测试和维护

持续性并发

C++并发技术扩展规范引入了持续性(continuations)概念,允许我们以更优雅的方式处理异步操作的结果。

基本持续性

持续性通过.then()方法附加到future上,当future就绪时自动执行:

std::experimental::future<int> find_the_answer;
auto fut = find_the_answer();
auto fut2 = fut.then(find_the_question);

用户登录示例

考虑一个用户登录流程,传统异步实现可能如下:

std::future<void> process_login(std::string const& username, 
                               std::string const& password) {
    return std::async(std::launch::async, [=](){
        try {
            user_id const id = backend.authenticate_user(username, password);
            user_data const info = backend.request_current_info(id);
            update_display(info);
        } catch(std::exception& e) {
            display_error(e);
        }
    });
}

使用持续性可以将其改写为更清晰的链式调用:

std::experimental::future<void> process_login(
    std::string const& username, std::string const& password) {
    return backend.async_authenticate_user(username, password)
        .then([](std::experimental::future<user_id> id) {
            return backend.async_request_current_info(id.get());
        })
        .then([](std::experimental::future<user_data> info_to_display) {
            try {
                update_display(info_to_display.get());
            } catch(std::exception& e) {
                display_error(e);
            }
        });
}

这种方式的优势:

  1. 避免回调地狱
  2. 异常传播更自然
  3. 代码逻辑更线性化

多future等待

当需要等待多个future完成时,可以使用when_all

std::experimental::future<FinalResult> process_data(std::vector<MyData>& vec) {
    std::vector<std::experimental::future<ChunkResult>> results;
    // 启动多个异步任务...
    return std::experimental::when_all(results.begin(), results.end())
        .then([](std::future<std::vector<std::experimental::future<ChunkResult>>> ready_results) {
            std::vector<ChunkResult> v;
            for(auto& f: ready_results.get()) {
                v.push_back(f.get());
            }
            return gather_results(v);
        });
}

这种方式比轮询多个future更高效,资源利用率更高。

总结

通过函数化编程、消息传递和持续性这三种技术,我们可以显著简化并发代码:

  1. 函数化编程:通过纯函数和不可变数据消除共享状态
  2. 消息传递:通过明确的通信渠道协调线程活动
  3. 持续性:通过链式调用组织异步操作

这些方法共同降低了并发编程的复杂度,使代码更易于理解、维护和扩展。在实际项目中,根据具体需求选择合适的模式或组合使用这些技术,可以构建出既高效又可靠的并发系统。

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

褚知茉Jade

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值