简述
多线程编程在绝大多数服务端编程中不可避免,它以并发、高效、能够充分利用多核CPU被广泛使用。
多线程编程的难度不是很大,只要能够注意好线程间资源的互斥,保证数据一致性,基本都能得到一个正确的多线程服务。
然而,要想真正充分发挥出多线程编程的功效,却是需要技术、经验和一定的编程技巧的。
可以说,有很多时候,考虑到保证数据一致性的额外编程开销,多线程服务的运行效率还不如单线程。
本文结合笔者c++编程经验,作一些小结和思考,旨在避免多线程编程的误区,并尽可能在编码阶段注重多线程程序效能的发挥。
错漏之处请不吝指正。
多线程还是单线程
多线程未必一定比单线程更快。
我鼓励多线程编程,但这句话应该时刻记在心中。多线程中的数据同步需要使用锁,关于锁的缺点可以参考锁的缺点。
现今的服务端编程,已经基本都适合多线程编程。因为目前cpu的核心不止一核,恰当的多线程编程能够充分利用硬件资源,把花在硬件上的钱在软件上体现出来。
对于嵌入式编程,可能单线程也比较少见了,除非你还在用51跑流水灯。
还有一点需要指出,随着异步编程的广泛使用,事件驱动机制使得单线程能够异步地处理许多事情,所以,多线程编程与异步编程都是需要掌握的基本技术。
可以说,目前大部分的服务器程序从总体上看是多线程,从局部上看是异步编程。
无论如何,正确是基础、性能是核心,把握住这两点,再逐步提升。
首先保证程序正确
多线程是为了获得更好的性能,但别舍本逐末,首先要确保程序正确。只有程序是正确的,所有提高性能的付出才是值得的。
正确的前提是:数据同步。
数据同步的一般性技术是使用锁。目前有多种锁可供选择,在c++中可以使用lock_guard/unique_lock,正确使用锁并且避免死锁。
也有一些无锁的组件可供选择,包括std::atomic,第三方的thread_queue。虽然它们的性能可能优于现场编码,但其内部或多或少地使用了CAS。特别说明,对于第三方的组件,建议对于性能充分了解后再使用,以免投入大量精力定位性能问题。
有一些组件是非线程安全的,当然也会有相应的技术来应对,举例如下:
- Boost Asio库的socket是非线程安全的。建议使用strand。
- RabbitMq的channel是非线程安全的。建议使用channel与thread绑定的方式。
总之,在不会得出正确结果的程序上谈性能就是在耍流氓。
多少个线程
既然是讨论多线程编程,那么应用启动多少个线程才是合适的呢?
这个问题没有统一的标准答案。它随着业务需求、数据流量、技术组件、硬件平台等因素发生变化。
先说确定的一点:操作系统一般会对线程的总量有一个上限的限制,可以修改,但一般不会修改。
关于cpu核心数量与可启动的线程数量之间的关系,可以参考:Cores vs Threads: How many threads should I run on this machine?。
大意为:线程数量 = Socket(s) * Core(s) per socket * Thread(s) per core。这些值通过lscpu
查看。
当然,查看不同的文章可能得出不同的结论,建议做这方面深入优化的朋友自己测试一下。
有一点应该不用证明,如果你有8个cpu核心,那么1个线程最多达到cpu使用率100%,对于高cpu使用率的应用,8个线程才能使cpu得到尽可能充分应用。
事实上,不同的项目要根据实际业务需求、数据量、计算量等指标进行设计和优化,这个应该不会成为性能的瓶颈。
线程池
考虑到创建线程时的资源消耗,不建议使用时创建,而应该使用线程池。
线程池是一种经过验证有效的技术,Java对它的支持是优秀的。c++则可以从Boost中找到范例。
使用线程池使得处理业务更加及时,且不会造成过多的空间消耗,是使用空间换取时间的不错选择。
鉴于主流的编程语言都有优秀的实践,不需要重复造轮子,尽管享受编程就好了。
异步编程
异步编程意味着非阻塞,非阻塞意味着单线程可以做很多事情。
事件驱动无疑是异步编程的根基。libev/libevent/libuv/boost asio等异步事件驱动库已经非常成熟,可以用来做很多简单且高效的事情。
还记得那个经典的c10k的并发问题么?对,epool也是基于事件驱动的。使用Boost Asio编写跨平台的网络程序时,异步编程对于解决复杂问题也大有裨益。
如果多线程中的大部分线程都在阻塞,它为什么不使用异步编程,且异步编程可以方便地扩充到多线程,如Boost的io_service。
总之,异步编程只使用简单的代码就能完成同步编程耗费大量代码完成的任务,何乐而不为呢?
小结
总之,使用多线程,并达到敲下那行代码时想要达到的性能,请先考虑如下几点:
- 单线程有时比多线程更快
- 正确的程序才有提高性能的必要
- 使用线程池获得更高的运行时效率
- 异步编程值得拥有
开心地使用多线程吧!