• 2022-08-22 C++并发编程(十四)



    前言

    通过类似函数式编程的方式,将各个线程的数据传递由 future 完成,而非共享数据,可简化代码,捋顺程序逻辑。


    一、利用future进行类函数式编程

    函数式编程是一种编程风格,有些语言天然就是函数式的,比如 LISP。C++是多范式语言,自然也可以进行函数式编程。

    函数在C++中不仅可以有返回值,也可以无返回值,但会产生副作用。函数式编程,更倾向于纯函数,即不会产生除返回值外的其他作用的函数。

    依赖于 future 对象在线程间传递,一个任务只依赖另一个任务结果,不必通过共享数据。

    以下示例是基于快速排序思想的函数式实现,分别用单线程和利用future传递中间结果的多线程实现。

    单线程快排 sequentialQuickSort() 基于链表结构而非整块内存数组结构,通过链表的成员函数 splice() 将原链表头元素转移到结果链表 result,作为快排的基准元素,然后通过 stl 算法 std::partition,将原链表元素分成大于基准元素和小于基准元素两部分,并将小于基准元素的部分传给新链表 lowerPart,递归调用 sequentialQuickSort(),进行两部分链表的排列,最后合并会 result。

    当然,这段程序和真正的函数式还是有很大区别。

    多线程快排的实现,仅仅是将递归调用放到 std::future newLower 对象中。

    明眼人一看便知,多线程并发快排的问题,用了太多的线程,线程一旦多于内核,恐怕不一定有利于算力提升,这也是多线程的一个坑,有时候辛苦搞的复杂多线程程序较单线程慢,就是这个道理,不迷信多线程,适度的使用,很有必要。

    #include 
    #include 
    #include 
    #include 
    #include 
    
    template <typename T>
    auto sequentialQuickSort(std::list<T> input) -> std::list<T>
    {
        if (input.empty())
        {
            return input;
        }
    
        std::list<T> result;
    
        result.splice(result.begin(), input, input.begin());
    
        const T &pivot = *result.begin();
    
        auto dividePoint = std::partition(input.begin(), input.end(),
                                          [&](const T &t) { return t < pivot; });
    
        std::list<T> lowerPart;
    
        lowerPart.splice(lowerPart.end(), input, input.begin(), dividePoint);
    
        auto newLower(sequentialQuickSort(std::move(lowerPart)));
    
        auto newHigher(sequentialQuickSort(std::move(input)));
    
        result.splice(result.end(), newHigher);
    
        result.splice(result.begin(), newLower);
    
        return result;
    }
    
    template <typename T>
    auto parallelQuickSort(std::list<T> input) -> std::list<T>
    {
        if (input.empty())
        {
            return input;
        }
    
        std::list<T> result;
    
        result.splice(result.begin(), input, input.begin());
    
        const T &pivot = *result.begin();
    
        auto dividePoint = std::partition(input.begin(), input.end(),
                                          [&](const T &t) { return t < pivot; });
    
        std::list<T> lowerPart;
    
        lowerPart.splice(lowerPart.end(), input, input.begin(), dividePoint);
    
        std::future<std::list<T>> newLower(
            std::async(&parallelQuickSort<T>, std::move(lowerPart)));
    
        auto newHigher(parallelQuickSort(std::move(input)));
    
        result.splice(result.end(), newHigher);
    
        result.splice(result.begin(), newLower.get());
    
        return result;
    }
    
    int main()
    {
        std::list<int> intList;
        std::vector<int> intVec;
        for (int i = 100; i != 0; --i)
        {
            intVec.push_back(i);
        }
    
        std::shuffle(intVec.begin(), intVec.end(),
                     std::mt19937(std::random_device()()));
    
        for (const auto &i : intVec)
        {
            intList.push_back(i);
        }
    
        auto res = sequentialQuickSort(intList);
    
        auto res2 = parallelQuickSort(intList);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94

    总结

    我们用蹩脚的函数式编程,完成了多线程的程序设计,虽然不一定如我们所愿能大幅提高效率,但作为示例,足够了。

    如何合理的设计程序,是否真的有必要启用多线程,可能更值得程序设计者优先考虑。

    参考文献:C++并发编程实战(第2版)[英] 安东尼•威廉姆斯(Anthony Williams)

  • 相关阅读:
    Python 进阶语法:JSON
    JUC-不得不说的Java“锁”事
    【面试系列】C++ 高频面试题
    Pandas数据分析2-数据分组、Apply函数、合并
    Python基础入门篇【36】-python初识异常及常见的异常类型
    【算法修炼】二分查找最接近元素(最接近下标)
    史上最全,接口测试-用例设计总结(案例分析实例)一篇策底打通...
    【数学建模】图论模型(基础理论+最大流与最小费用流问题)
    机器学习(18)---朴素贝叶斯
    java计算机毕业设计web家教信息服务平台设计与实现源码+mysql数据库+系统+lw文档+部署
  • 原文地址:https://blog.csdn.net/m0_54206076/article/details/126459787