快速排序-FP模式版和快速排序 - 和 - FP模式线程强化版

5 篇文章 1 订阅

快速排序-FP模式版

函数化编程(functional programming)是一种编程方式,函数结果只依赖于传入函数的参数。使用相同的参数调用函数,不管多少次都会获得相同的结果。
函数化编程的好处并不限于将“纯粹”作为默认方式(范型)的语言。

在PF(函数化)中使用future。我们实现一个简单的快排算法
快排算法思想:给一个数据列表,每次选取其中一个数为中间值,然后将数据列表中的其他数值分为2组,比他小的在前 大的在后

FP-模式的递归排序

下面代码中的是FP-模式的串行实现,需要传入列表,并且返回一个列表,与 std::sort() 做同样的事情不
同。 (译者: std::sort() 是无返回值的,因为参数接收的是迭代器,所以其可以对原始列表直进行修改与排序。可参考sort())

// 快速排序——串行版
#include <list>
#include <algorithm>
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;
}

选择了FP模式的接口,要使用递归对两部分排序,所以需要创建两个列表。可以用splice()来完成,将
input列表小于divided_point的值移动到新列表lower_part中,其他数继续留在input列表中。而后,可以递归调用自己,对两个列表进行排序。显式使用 std::move() 将列表传递到函数中后,可以再次使用splice(),将result中的结果以正确的顺序进行拼接。new_higher指向的值放在“中间”值的后面,new_lower指向的值放在“中间”值的前面。

快速排序——FP模式线程强化

使用纯函数模式容易转化为并行版本。

快速排序——并行版

#include <list>
#include <algorithm>
#include <future>
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() 会使用另一线程对列表进行排序。大于部分如同之前一样,使用递归进行排序。通过递归调用parallel_quick_sort(),可以使用硬件并发。 std::async() 会启动一个新线程,这样当递归三次时,就会有八个线程在运行了。当递归十次(对于大约
有1000个元素的列表),如果硬件能处理这十次递归调用,将会创建1024个执行线程。当运行库认为产生了太多的任务时(也许是因为数量超过了硬件并发的最大值),可能会同步的切换新产生的任务。当任务过多时(已影响性能),为了避免任务传递的开销,这些任务应该在使用get()获取结果的线程上运行,而不是在新线程上运行。这也符合 std::async 的行为,为每一个任务启动一个线程(甚至是在任务超额时,也就是在 std::launch::deferred 没有明确规定的情况下),或为了同步执行所有任务( std::launch::async 有明确规定的情况下)。当运行库自动裁剪线程时,建议去查看一下运行库的实现文档,了解一下将会有怎样的行为表现。
比起使用 std::async() ,可以写一个spawn_task()函数对 std::packaged_task 和 std::thread 做一下包装。需要为函数结果创建一个 std::packaged_task 对象, 并从这个对象中获取future,或在线程中返回future。其本身并没有太多优势(事实上会造成大规模的超额任务),但可为转型成一个更复杂的实现进行铺垫,实现会向队列添加任务,而后使用线程池的方式来运行。 std::async 更适合于已知所有任务的情况,并且要能完全控制线程池中构建或执行过任务的线程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值