算法优化之c++多线程优化:思考与总结

最近的项目中要用多线程来对代码进行优化,期间查阅了一些资料,主要是踩过一些坑,在此记录一下,给自己提个醒。

1.什么是多线程优化

首先我们要知道什么是线程,这点没有谁比维基百科说的更好了,直接点击查看线程英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

正因为线程是调度的最小单位,控制好了线程,也就相当于控制好了计算资源。之所以叫计算资源,是因为我们不确定程序运行在什么类型的硬件设备上,多线程优化这个概念,更多的是强调资源的高利用率,而不是低使用率,即每份计算资源都能被充分的利用,这才是优化的本质,而并不是要强行减少资源的使用。多线程优化,就是利用多线程来并行处理多个任务,提高计算资源的利用率,从而提升系统效率。

2.为什么多线程能够提高利用率

刚才我们有了多线程优化的概念,里面提到关键一点是计算资源的高利用率,那么为什么多线程能够提高利用率呢?是不是只要加上多线程,程序就跑得快了呢?显然不是这样的,这里面还涉及到什么样的任务适合多线程,我们留到后面再说,现在只是说说多线程相较于单线程,优化了什么。现在的通用硬件设备大多都是单CPU多处理核心的架构,比如iPhone X的A11芯片的CPU部分为六核心设计,由2颗代号为“季风”(Monsoon)的高性能核心及4颗代号为“西北风”(Mistral)的节能核心。多核心意味着CPU能同时进行多个“最小单位”的调度和处理,这个最小单位就是我们前面提到的线程。换句话说,多核心使得多个线程能够同时运行,达到并行的效果。

举个例子,一个班40个同学春游,如果只有一辆双人车,那么不算司机一次只能运1人,一共需要40次才能运完,如果同时有4辆车同时运,那么只需要10次就能运完。类比到计算任务,运40个人就是总体任务,车就是线程,“双人”是线程的处理逻辑,如果单一线程,那么一共需要40次处理才能完成任务,而我们刚才反复说到线程是最小调度单元,也就是说,对于这个任务,单一线程意味着CPU每时每刻都只有一个核心在工作,其他核心都在睡觉,即使把这个核心累死,也就只能开一辆车,这样效率是不高的。如果有四辆车,也就是同时有4个核心在工作,显然系统的计算资源得到了更好的利用。

例子讲完了,肯定有人会问,为什么一辆车只能坐一个人呢,我为什么不找一辆7座的,一次运6个同学多好。这是一个很好的问题,问题的核心在于,提高单一线程的处理效率,这是在多线程优化之前就应该完成的任务,可以通过更好的逻辑设计以及单指令多数据(SIMD)来进行优化,提高单一线程的处理能力,根据实际情况,可以升级为4座甚至7座车,这点可以参考SIMD相关资料。那还有人会问,为什么我不多叫几辆车,叫40辆双人车,一次运完,岂不更省事?这个想法很直接很美好,但是往往我们找不到这么多车,经费不够!类比到计算设备上,就是CPU核心数不会太多,受限于材料、工艺和能耗,CPU的核心数会有限制,像我们提到的iPhone X,也就6个核心,所以40辆车是不可能了。那6辆车呢?不是6个核心吗,6辆车,不是刚好吗?这里的确存在一个问题,6核心运行6线程是否比6核心运行4线程好。这个答案是不确定的,与计算设备的状态有关,因为我们知道,计算设备不可能同时只处理一个任务,除了你安排的任务,还有其他人安排的任务,以及它为了维护自身正常运行所进行的任务,所以线程的数量并不是越多越好,要根据实际情况进行测试得到最优结果。

对于典型可分离的任务,如春游运同学,多线程能够使得CPU的多个核心同时处理任务,从而提高CPU的利用率,来提升性能。

3.什么任务适合多线程

下雨了,双人车不挡雨,同学都需要雨衣,否则没法出去,刚才提到的40个同学里面,只有39件雨衣,剩下一个倒霉蛋李明没有,但是,韩梅梅愿意和李明共享一件雨衣,解决出行难题,一共10里路,他们每走一段,就换一个人穿雨衣,剩下的那个就只能等。本来很快的一段行程,因为他们俩需要中途等雨衣,导致他们比其他同学迟到了一个小时。他们俩都说,下次如果再下雨,我就等你到了再出发,也比这样中途换雨衣方便。同样的任务,因为存在耦合以及数据竞争,导致任务执行的效率下降,例子中的雨衣就是两个任务都要访问的全局变量,如果俩人同时抢雨衣,那么雨衣很可能会被撕坏,系统会出现各种未定义的行为,主要以段错误和内存错误为主,但实际上是因为抢雨衣导致的。这种耦合任务,多线程执行效率可能比单线程只想两次还要低。

任务间耦合度高、独立性低的,不适合多线程。

那是不是说这种情况就没有办法优化了呢?也不是。李明和韩梅梅的雨衣比较高级,可以分成两半,而且还可以再接起来,再加上他俩感情深厚,于是决定每人只要一半,哪怕会淋点与,但是也无所谓,到了目的地再把雨衣拼成完好的一件,并不影响结果。在多线程里,如果一个全局变量被每条线程都访问,那么可以用局部变量来代替全部变量作为参数传入多线程中,在计算完成之后,统一恢复到全局变量中。

第二次春游,学校统一组织,400人,找了个很近的地方,双人车过去只要1分钟,还不如上下车换人的时间长,还是之前的车,还是之前的人。但是这次春游的组织却让大家都很头疼,因为时间全浪费在上下车换人上了,每个人在车上只坐一分钟,而上下车换人可能都不止这点时间,不停的在上下人,导致现场非常拥挤。

如果单次任务执行时间非常短,而循环次数又非常多,不适合多线程。

这个时候,可以从逻辑上将任务拆分,拆成几个独立的任务,再使用多线程。

4.多线程使用方法

前面讲了一些小故事,都是瞎扯淡,现在才是干货。

多线程的使用,请直接参考破晓的博客,以及程序员的自我修养系列

5.线程池的使用

又是干货,请参考100行c11线程池,里面有例子,可以直接运行:

#include <iostream>
#include <vector>
#include <chrono>

#include "ThreadPool.h"

int main()
{
    
    ThreadPool pool(4);
    std::vector< std::future<int> > results;

    for(int i = 0; i < 8; ++i) {
        results.emplace_back(
            pool.enqueue([i] {
                std::cout << "hello " << i << std::endl;
                std::this_thread::sleep_for(std::chrono::seconds(1));
                std::cout << "world " << i << std::endl;
                return i*i;
            })
        );
    }

    for(auto && result: results)
        std::cout << result.get() << ' ';
    std::cout << std::endl;
    
    return 0;
}

6.一点思考

1)除了逻辑和语法错误,多线程的错误都与数据竞争与线程安全有关,所以遇到任何莫名其妙的问题,首先静态检查代码,确保线程安全

2)不要害怕加锁和解锁,不管是互斥锁std::mutex,还是std::unique_lock,它们的效率都很高,上万次加锁解锁操作,也不会操作1毫秒

3)不要胡乱加锁,虽然加锁解锁操作本身不耗时,但是锁住的是一个关键数据或者关键过程,那么必然将影响程序性能,比如整个函数就是为了计算残差,而你为了线程安全,把残差的实际计算过程加了锁,整个多线程任务变成了等待锁的单线程,得不偿失

4)如果线程池效率不高,有两点要考虑,1是任务是否适合多线程,2是能不能把锁去掉,比如用局部变量代替全局变量

5)尽可能把线程对象的初始化弄得简单点,不要太频繁的初始化线程然后又释放线程,最好是放在系统的初始化接口中

6)openMP还有tbb还有GCD等多线程框架的实现已经很好,如果能使用,尽量学会使用这些库,一般会比自己管理好一点,如果是编程大牛,请忽略

7)多线程与异步计算的关系密切,一般可以使用异步计算的,都可以用多线程来实现,而多线程加速也依赖于异步计算

### 回答1: 《高效编程:内存与性能优化》是一本非常实用的技术书籍,旨在为程序员提供优化内存使用和程序性能的各种实用技巧和建议。 本书主要分为两部分,一部分探讨了内存管理的各种技术、原理和实际应用,包括操作系统内存管理、动态内存分配、垃圾回收、内存泄露检测、内存分析工具等。另一部分则涵盖了程序性能优化的相关内容,包括算法优化、编译器优化多线程并发控制、IO操作优化等。 本书的亮点在于它不仅提供了各种理论知识,更重要的是提供了大量实际案例和实践经验,实用性非常强。针对某些常见问题,例如内存泄露、内存碎片、多线程死锁等,本书提供了详细的解决方案和案例,可供开发者参考和使用。 另外,本书也介绍了一些常用的开发工具和库,例如Valgrind、GDB、Boost等,这些工具和库可以为程序员提供更好的内存管理和性能优化支持。 总而言之,《高效编程:内存与性能优化》是一本非常实用的技术书籍,适合各类程序开发者参考和学习。它提供了全面的理论知识和实践经验,对于优化程序的内存使用和性能表现至关重要。阅读本书可以帮助程序员更好地把握程序开发中的重要技术点,提高程序的可靠性和性能表现,是一本非常值得推荐的技术参考书。 ### 回答2: 《高效编程:内存与性能优化》是一本针对程序员提高代码效率的教材,主要讲解如何优化内存使用和提高程序性能。本书内容涵盖了很多实用的技巧和方法,非常有针对性和实用性。 在内存方面,本书从一开始介绍了内存的基本概念,然后针对常见的内存问题,如内存泄漏、内存碎片、内存分配等等进行了详细的讲解。此外,还提供了一些实用的工具和方法,如内存分析工具、内存池等等,帮助读者提高内存使用的效率。 在性能方面,本书介绍了一些常见的性能瓶颈,如CPU、IO、网络等等,然后提供了一些相应的优化方法,如缓存、异步IO、多线程等等,让程序员可以更好地利用计算机资源,提高程序的执行效率。 总的来说,《高效编程:内存与性能优化》是一本非常实用的书籍,可以帮助程序员更好地理解和应用内存和性能优化的方法和工具,进而提高代码效率,开发出更加优秀的软件产品。 ### 回答3: 本书主要是以C语言为主题,讲解如何进行高效编程,优化内存与性能。首先,作者详细介绍了内存管理方面的知识,例如从内存的角度解释了变量的本质,指针数组的特点,动态内存分配的方式,以及内存泄漏等问题。其次,本书针对性能进行了多方面的优化,例如从一些小的方法入手,如缓存算法、位运算等,到牵涉更复杂的框架进行优化,比如线程、异步编程等。最后,本书通过大量的案例学习,将讲解内容与实际应用相结合,以帮助读者更加深入地理解和熟悉C语言的高效编程方式,使读者可以更好的应用于实际项目中。 总之,本书旨在帮助读者掌握C语言编程中的关键技能,提高代码的内存使用和性能优化水平,深度挖掘C语言的内在原理,让读者能够编写出复杂而高效的程序。对于对编程有兴趣的学生和工程师,本书是一个不错的选择。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值